monotone

monotone Mtn Source Tree

Root/src/commands.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2// 2007 Julio M. Merino Vidal <jmmv@NetBSD.org>
3//
4// This program is made available under the GNU GPL version 2.0 or
5// greater. See the accompanying file COPYING for details.
6//
7// This program is distributed WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9// PURPOSE.
10
11#include "base.hh"
12#include "cmd.hh"
13#include "simplestring_xform.hh"
14
15using std::map;
16using std::make_pair;
17using std::set;
18using std::string;
19using std::vector;
20
21// This file defines the logic behind the CMD() family of macros and handles
22// command completion. Note that commands::process is in cmd.cc mainly for
23// better encapsulation of functions not needed in the unit tester.
24
25namespace commands
26{
27 const char * safe_gettext(const char * msgid)
28 {
29 if (strlen(msgid) == 0)
30 return msgid;
31
32 return _(msgid);
33 }
34
35 // This must be a pointer.
36 // It's used by the constructor of other static objects in different
37 // files (cmd_*.cc), and since they're in different files, there's no
38 // guarantee about what order they'll be initialized in. So have this
39 // be something that doesn't get automatic initialization, and initialize
40 // it ourselves the first time we use it.
41 typedef map< command *, command * > relation_map;
42 static relation_map * cmds_relation_map = NULL;
43
44 static void init_children(void)
45 {
46 static bool children_inited = false;
47
48 if (!children_inited)
49 {
50 children_inited = true;
51
52 for (relation_map::iterator iter = cmds_relation_map->begin();
53 iter != cmds_relation_map->end(); iter++)
54 {
55 if ((*iter).second != NULL)
56 (*iter).second->children().insert((*iter).first);
57 }
58 }
59 }
60
61 typedef std::map<command const *, cmdpreset const *> presetter_map_t;
62 presetter_map_t * presetter_map = 0;
63 cmdpreset::~cmdpreset() { }
64 void cmdpreset::register_for(command const * cmd)
65 {
66 if (!presetter_map)
67 presetter_map = new presetter_map_t;
68
69 presetter_map->insert(make_pair(cmd, this));
70 }
71
72 //
73 // Implementation of the commands::command class.
74 //
75 command::command(std::string const & primary_name,
76 std::string const & other_names,
77 command * parent,
78 bool is_group,
79 bool hidden,
80 std::string const & params,
81 std::string const & abstract,
82 std::string const & desc,
83 bool use_workspace_options,
84 options::options_type const & opts,
85 bool _allow_completion)
86 : m_primary_name(utf8(primary_name, origin::internal)),
87 m_parent(parent),
88 m_is_group(is_group),
89 m_hidden(hidden),
90 m_params(utf8(params, origin::internal)),
91 m_abstract(utf8(abstract, origin::internal)),
92 m_desc(utf8(desc, origin::internal)),
93 m_use_workspace_options(use_workspace_options),
94 m_opts(opts),
95 m_allow_completion(_allow_completion)
96 {
97 // A warning about the parent pointer: commands are defined as global
98 // variables, so they are initialized during program startup. As they
99 // are spread over different compilation units, we have no idea of the
100 // order in which they will be initialized. Therefore, accessing
101 // *parent from here is dangerous.
102 //
103 // This is the reason for the cmds_relation_map. We cannot set up
104 // the m_children set until a late stage during program execution.
105
106 if (cmds_relation_map == NULL)
107 cmds_relation_map = new relation_map();
108 (*cmds_relation_map)[this] = m_parent;
109
110 m_names.insert(m_primary_name);
111
112 vector< utf8 > onv = split_into_words(utf8(other_names, origin::internal));
113 m_names.insert(onv.begin(), onv.end());
114 }
115
116 command::~command(void)
117 {
118 }
119
120 bool
121 command::allow_completion() const
122 {
123 return m_allow_completion &&
124 (m_parent?m_parent->allow_completion():true);
125 }
126
127 command_id
128 command::ident(void) const
129 {
130 I(this != CMD_REF(__root__));
131
132 command_id i;
133
134 if (parent() != CMD_REF(__root__))
135 i = parent()->ident();
136 i.push_back(primary_name());
137
138 I(!i.empty());
139 return i;
140 }
141
142 const utf8 &
143 command::primary_name(void) const
144 {
145 return m_primary_name;
146 }
147
148 const command::names_set &
149 command::names(void) const
150 {
151 return m_names;
152 }
153
154 void
155 command::add_alias(const utf8 &new_name)
156 {
157 m_names.insert(new_name);
158 }
159
160
161 command *
162 command::parent(void) const
163 {
164 return m_parent;
165 }
166
167 bool
168 command::is_group(void) const
169 {
170 return m_is_group;
171 }
172
173 bool
174 command::hidden(void) const
175 {
176 return m_hidden;
177 }
178
179 std::string
180 command::params() const
181 {
182 return safe_gettext(m_params().c_str());
183 }
184
185 std::string
186 command::abstract() const
187 {
188 return safe_gettext(m_abstract().c_str());
189 }
190
191 std::string
192 command::desc() const
193 {
194 if (m_desc().empty())
195 return abstract() + ".";
196 return abstract() + ".\n" + safe_gettext(m_desc().c_str());
197 }
198
199 command::names_set
200 command::subcommands(bool hidden) const
201 {
202 names_set set;
203 init_children();
204 for (children_set::const_iterator i = m_children.begin();
205 i != m_children.end(); i++)
206 {
207 if ((*i)->hidden() && !hidden)
208 continue;
209 names_set const & other = (*i)->names();
210 set.insert(other.begin(), other.end());
211 }
212 return set;
213 }
214
215 options::options_type const &
216 command::opts(void) const
217 {
218 return m_opts;
219 }
220
221 bool
222 command::use_workspace_options(void) const
223 {
224 return m_use_workspace_options;
225 }
226
227 void
228 command::preset_options(options & opts) const
229 {
230 I(presetter_map);
231 presetter_map_t::const_iterator i = presetter_map->find(this);
232 if (i != presetter_map->end())
233 i->second->preset(opts);
234 }
235
236 command::children_set &
237 command::children(void)
238 {
239 init_children();
240 return m_children;
241 }
242
243 command::children_set const &
244 command::children(void) const
245 {
246 init_children();
247 return m_children;
248 }
249
250 bool
251 command::is_leaf(void) const
252 {
253 return children().empty();
254 }
255
256 bool
257 command::operator<(command const & cmd) const
258 {
259 // *twitch*
260 return (parent()->primary_name() < cmd.parent()->primary_name() ||
261 ((parent() == cmd.parent()) &&
262 primary_name() < cmd.primary_name()));
263 }
264
265 bool
266 command::has_name(utf8 const & name) const
267 {
268 return names().find(name) != names().end();
269 }
270
271 command const *
272 command::find_command(command_id const & id) const
273 {
274 command const * cmd;
275
276 if (id.empty())
277 cmd = this;
278 else
279 {
280 utf8 component = *(id.begin());
281 command const * match = find_child_by_name(component);
282
283 if (match != NULL)
284 {
285 command_id remaining(id.begin() + 1, id.end());
286 I(remaining.size() == id.size() - 1);
287 cmd = match->find_command(remaining);
288 }
289 else
290 cmd = NULL;
291 }
292
293 return cmd;
294 }
295
296 command *
297 command::find_command(command_id const & id)
298 {
299 command * cmd;
300
301 if (id.empty())
302 cmd = this;
303 else
304 {
305 utf8 component = *(id.begin());
306 command * match = find_child_by_name(component);
307
308 if (match != NULL)
309 {
310 command_id remaining(id.begin() + 1, id.end());
311 I(remaining.size() == id.size() - 1);
312 cmd = match->find_command(remaining);
313 }
314 else
315 cmd = NULL;
316 }
317
318 return cmd;
319 }
320
321 map< command_id, command * >
322 command::find_completions(utf8 const & prefix, command_id const & completed,
323 bool completion_ok)
324 const
325 {
326 map< command_id, command * > matches;
327
328 for (children_set::const_iterator iter = children().begin();
329 iter != children().end(); iter++)
330 {
331 command * child = *iter;
332
333 for (names_set::const_iterator iter2 = child->names().begin();
334 iter2 != child->names().end(); iter2++)
335 {
336 command_id caux = completed;
337 caux.push_back(*iter2);
338
339 // If one of the command names was an exact match,
340 // do not try to find other possible completions.
341 // This would eventually hinder us to ever call a command
342 // whose name is also the prefix for another command in the
343 // same group (f.e. mtn automate cert and mtn automate certs)
344 if (prefix == *iter2)
345 {
346 // since the command children are not sorted, we
347 // need to ensure that no other partial completed
348 // commands matched
349 matches.clear();
350 matches[caux] = child;
351 return matches;
352 }
353
354 // while we list hidden commands with a special option,
355 // we never want to give them as possible completions
356 if (!child->hidden() &&
357 prefix().length() < (*iter2)().length() &&
358 allow_completion() && completion_ok)
359 {
360 string temp((*iter2)(), 0, prefix().length());
361 utf8 p(temp, origin::internal);
362 if (prefix == p)
363 matches[caux] = child;
364 }
365 }
366 }
367
368 return matches;
369 }
370
371 set< command_id >
372 command::complete_command(command_id const & id,
373 command_id completed,
374 bool completion_ok) const
375 {
376 I(this != CMD_REF(__root__) || !id.empty());
377 I(!id.empty());
378
379 set< command_id > matches;
380
381 utf8 component = *(id.begin());
382 command_id remaining(id.begin() + 1, id.end());
383
384 map< command_id, command * >
385 m2 = find_completions(component,
386 completed,
387 allow_completion() && completion_ok);
388 for (map< command_id, command * >::const_iterator iter = m2.begin();
389 iter != m2.end(); iter++)
390 {
391 command_id const & i2 = (*iter).first;
392 command * child = (*iter).second;
393
394 if (child->is_leaf() || remaining.empty())
395 matches.insert(i2);
396 else
397 {
398 I(remaining.size() == id.size() - 1);
399 command_id caux = completed;
400 caux.push_back(i2[i2.size() - 1]);
401 set< command_id > maux = child->complete_command(remaining, caux);
402 if (maux.empty())
403 matches.insert(i2);
404 else
405 matches.insert(maux.begin(), maux.end());
406 }
407 }
408
409 return matches;
410 }
411
412 command *
413 command::find_child_by_name(utf8 const & name) const
414 {
415 I(!name().empty());
416
417 command * cmd = NULL;
418
419 for (children_set::const_iterator iter = children().begin();
420 iter != children().end() && cmd == NULL; iter++)
421 {
422 command * child = *iter;
423
424 if (child->has_name(name))
425 cmd = child;
426 }
427
428 return cmd;
429 }
430};
431
432namespace std
433{
434 template <>
435 struct greater<commands::command *>
436 {
437 bool operator()(commands::command const * a, commands::command const * b)
438 {
439 return *a < *b;
440 }
441 };
442};
443
444namespace commands
445{
446 command_id
447 complete_command(args_vector const & args)
448 {
449 // Handle categories early; no completion allowed.
450 if (CMD_REF(__root__)->find_command(make_command_id(args[0]())) != NULL)
451 return make_command_id(args[0]());
452
453 command_id id;
454 for (args_vector::const_iterator iter = args.begin();
455 iter != args.end(); iter++)
456 id.push_back(*iter);
457
458 set< command_id > matches;
459
460 command::children_set const & cs = CMD_REF(__root__)->children();
461 for (command::children_set::const_iterator iter = cs.begin();
462 iter != cs.end(); iter++)
463 {
464 command const * child = *iter;
465
466 set< command_id > m2 = child->complete_command(id, child->ident());
467 matches.insert(m2.begin(), m2.end());
468 }
469
470 if (matches.size() >= 2)
471 {
472 // If there is an exact match at the lowest level, pick it. Needed
473 // to automatically resolve ambiguities between, e.g., 'drop' and
474 // 'dropkey'.
475 command_id tmp;
476
477 for (set< command_id >::const_iterator iter = matches.begin();
478 iter != matches.end() && tmp.empty(); iter++)
479 {
480 command_id const & id = *iter;
481 I(id.size() >= 2);
482 if (id[id.size() - 1]() == args[id.size() - 2]())
483 tmp = id;
484 }
485
486 if (!tmp.empty())
487 {
488 matches.clear();
489 matches.insert(tmp);
490 }
491 }
492
493 if (matches.empty())
494 {
495 E(false, origin::user,
496 F("unknown command '%s'") % join_words(id));
497 }
498 else if (matches.size() == 1)
499 {
500 id = *matches.begin();
501 }
502 else
503 {
504 I(matches.size() > 1);
505 string err =
506 (F("'%s' is ambiguous; possible completions are:") %
507 join_words(id)()).str();
508 for (set< command_id >::const_iterator iter = matches.begin();
509 iter != matches.end(); iter++)
510 err += '\n' + join_words(*iter)();
511 E(false, origin::user, i18n_format(err));
512 }
513
514 I(!id.empty());
515 return id;
516 }
517
518 command_id make_command_id(std::string const & path)
519 {
520 return split_into_words(utf8(path, origin::user));
521 }
522}
523
524
525// Local Variables:
526// mode: C++
527// fill-column: 76
528// c-file-style: "gnu"
529// indent-tabs-mode: nil
530// End:
531// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status