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