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 
15 using std::map;
16 using std::make_pair;
17 using std::set;
18 using std::string;
19 using 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 
25 namespace commands
26 {
safe_gettext(const char * msgid)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 
init_children(void)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;
~cmdpreset()63   cmdpreset::~cmdpreset() { }
register_for(command const * cmd)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   //
command(std::string const & primary_name,std::string const & other_names,command * parent,bool is_group,bool hidden,std::string const & params,std::string const & abstract,std::string const & desc,bool use_workspace_options,options::options_type const & opts,bool _allow_completion)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 
~command(void)116   command::~command(void)
117   {
118   }
119 
120   bool
allow_completion() const121   command::allow_completion() const
122   {
123     return m_allow_completion &&
124       (m_parent?m_parent->allow_completion():true);
125   }
126 
127   command_id
ident(void) const128   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 &
primary_name(void) const143   command::primary_name(void) const
144   {
145     return m_primary_name;
146   }
147 
148   const command::names_set &
names(void) const149   command::names(void) const
150   {
151     return m_names;
152   }
153 
154   void
add_alias(const utf8 & new_name)155   command::add_alias(const utf8 &new_name)
156   {
157     m_names.insert(new_name);
158   }
159 
160 
161   command *
parent(void) const162   command::parent(void) const
163   {
164     return m_parent;
165   }
166 
167   bool
is_group(void) const168   command::is_group(void) const
169   {
170     return m_is_group;
171   }
172 
173   bool
hidden(void) const174   command::hidden(void) const
175   {
176     return m_hidden;
177   }
178 
179   std::string
params() const180   command::params() const
181   {
182     return safe_gettext(m_params().c_str());
183   }
184 
185   std::string
abstract() const186   command::abstract() const
187   {
188     return safe_gettext(m_abstract().c_str());
189   }
190 
191   std::string
desc() const192   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
subcommands(bool hidden) const200   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 &
opts(void) const216   command::opts(void) const
217   {
218     return m_opts;
219   }
220 
221   bool
use_workspace_options(void) const222   command::use_workspace_options(void) const
223   {
224     return m_use_workspace_options;
225   }
226 
227   void
preset_options(options & opts) const228   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 &
children(void)237   command::children(void)
238   {
239     init_children();
240     return m_children;
241   }
242 
243   command::children_set const &
children(void) const244   command::children(void) const
245   {
246     init_children();
247     return m_children;
248   }
249 
250   bool
is_leaf(void) const251   command::is_leaf(void) const
252   {
253     return children().empty();
254   }
255 
256   bool
operator <(command const & cmd) const257   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
has_name(utf8 const & name) const266   command::has_name(utf8 const & name) const
267   {
268     return names().find(name) != names().end();
269   }
270 
271   command const *
find_command(command_id const & id) const272   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 *
find_command(command_id const & id)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 * >
find_completions(utf8 const & prefix,command_id const & completed,bool completion_ok) const322   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 >
complete_command(command_id const & id,command_id completed,bool completion_ok) const372   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 *
find_child_by_name(utf8 const & name) const413   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 
432 namespace std
433 {
434   template <>
435   struct greater<commands::command *>
436   {
operator ()std::greater437     bool operator()(commands::command const * a, commands::command const * b)
438     {
439       return *a < *b;
440     }
441   };
442 };
443 
444 namespace commands
445 {
446   command_id
complete_command(args_vector const & args)447   complete_command(args_vector const & args)
448   {
449     // Handle categories early; no completion allowed.
450     commands::command* root_cmd = CMD_REF(__root__);
451     if (root_cmd->find_command(make_command_id(args[0]())) != NULL)
452       return make_command_id(args[0]());
453 
454     command_id id;
455     for (args_vector::const_iterator iter = args.begin();
456          iter != args.end(); iter++)
457       id.push_back(*iter);
458 
459     set< command_id > matches;
460 
461     command::children_set const & cs = root_cmd->children();
462     for (command::children_set::const_iterator iter = cs.begin();
463          iter != cs.end(); iter++)
464       {
465         command const * child = *iter;
466 
467         set< command_id > m2 = child->complete_command(id, child->ident());
468         matches.insert(m2.begin(), m2.end());
469       }
470 
471     if (matches.size() >= 2)
472       {
473         // If there is an exact match at the lowest level, pick it.  Needed
474         // to automatically resolve ambiguities between, e.g., 'drop' and
475         // 'dropkey'.
476         command_id tmp;
477 
478         for (set< command_id >::const_iterator iter = matches.begin();
479              iter != matches.end() && tmp.empty(); iter++)
480           {
481             command_id const & id = *iter;
482             I(id.size() >= 2);
483             if (id[id.size() - 1]() == args[id.size() - 2]())
484               tmp = id;
485           }
486 
487         if (!tmp.empty())
488           {
489             matches.clear();
490             matches.insert(tmp);
491           }
492       }
493 
494     if (matches.empty())
495       {
496         E(false, origin::user,
497           F("unknown command '%s'") % join_words(id));
498       }
499     else if (matches.size() == 1)
500       {
501         id = *matches.begin();
502       }
503     else
504       {
505         I(matches.size() > 1);
506         string err =
507           (F("'%s' is ambiguous; possible completions are:") %
508              join_words(id)()).str();
509         for (set< command_id >::const_iterator iter = matches.begin();
510              iter != matches.end(); iter++)
511           err += '\n' + join_words(*iter)();
512         E(false, origin::user, i18n_format(err));
513       }
514 
515     I(!id.empty());
516     return id;
517   }
518 
make_command_id(std::string const & path)519   command_id make_command_id(std::string const & path)
520   {
521     return split_into_words(utf8(path, origin::user));
522   }
523 }
524 
525 
526 // Local Variables:
527 // mode: C++
528 // fill-column: 76
529 // c-file-style: "gnu"
530 // indent-tabs-mode: nil
531 // End:
532 // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
533