# # # patch "cmd.hh" # from [aa8269c18d97811748737eea49e264742e81bc94] # to [d523c2725e70ad02fae167b85a7f964c44a03ff8] # # patch "cmd_db.cc" # from [0a9a11a39e28af5f5488091c12d85341340298c3] # to [7c6ee07a886223cd0a13d0376798befd22dc7e42] # # patch "cmd_key_cert.cc" # from [ee6de8d946fe1205c3857eb6eebbd399b1efef92] # to [a6d2d847b28748f03595fd2b9ea210865171dd5d] # # patch "cmd_packet.cc" # from [03af44ce9be22d3d6b1011eea767c8f761b1deaf] # to [327c443c53e7e17d0837ebe60a0cec79cc36e8aa] # # patch "commands.cc" # from [51c68a4e89f837a1d5d34a37defa2e9ece59020a] # to [0c26aaf6b6178676c821c11587c3caa30ea5c9e7] # # patch "revision.cc" # from [7c2a9fc44fab22eaf5166ac846d96400cf44e872] # to [842aedb1c3fea0703fdf97a4271aafb72c7aaa8e] # # patch "sha1.cc" # from [2417f83de1b98aacb1850b4d3e5221cf40a13cdf] # to [7d08674e126fc1f697c65720580e210e1e5ba3d6] # ============================================================ --- cmd.hh aa8269c18d97811748737eea49e264742e81bc94 +++ cmd.hh d523c2725e70ad02fae167b85a7f964c44a03ff8 @@ -22,7 +22,8 @@ namespace commands namespace commands { - std::string const & hidden_group(); + std::string const & hidden_parent(); + std::string const & root_parent(); struct command { @@ -30,12 +31,13 @@ namespace commands // translate them until after main starts, by which time the // command objects have all been constructed. std::set< std::string > names; - std::string cmdgroup; + std::string parent; std::string params_; std::string abstract_; std::string desc_; bool use_workspace_options; options::options_type opts; + std::set< command * > children; command(std::string const & n, std::string const & aliases, std::string const & g, @@ -46,7 +48,7 @@ namespace commands options::options_type const & o); virtual ~command(); virtual std::string params(); - virtual std::string abstract(); + virtual std::string abstract() const; virtual std::string desc(); virtual options::options_type get_options(std::vector const & args); virtual void exec(app_state & app, @@ -101,11 +103,11 @@ process_commit_message_args(bool & given app_state & app, utf8 message_prefix = utf8("")); -#define CMD(C, aliases, group, params, abstract, desc, opts) \ +#define CMD(C, aliases, parent, params, abstract, desc, opts) \ namespace commands { \ struct cmd_ ## C : public command \ { \ - cmd_ ## C() : command(#C, aliases, group, params, abstract, \ + cmd_ ## C() : command(#C, aliases, parent, params, abstract, \ desc, true, \ options::options_type() | opts) \ {} \ @@ -122,11 +124,11 @@ void commands::cmd_ ## C::exec(app_state // Use this for commands that want to define a params() function // instead of having a static description. (Good for "automate" // and possibly "list".) -#define CMD_WITH_SUBCMDS(C, aliases, group, abstract, desc, opts) \ +#define CMD_WITH_SUBCMDS(C, aliases, parent, abstract, desc, opts) \ namespace commands { \ struct cmd_ ## C : public command \ { \ - cmd_ ## C() : command(#C, aliases, group, "", abstract, desc, \ + cmd_ ## C() : command(#C, aliases, parent, "", abstract, desc, \ true, options::options_type() | opts) \ {} \ virtual void exec(app_state & app, \ @@ -141,14 +143,35 @@ void commands::cmd_ ## C::exec(app_state std::string const & name, \ std::vector const & args) +// XXX Should the 'opts' parameter go away? +#define CMD_GROUP(C, aliases, parent, abstract, desc, opts) \ +namespace commands { \ + struct cmd_ ## C : public command \ + { \ + cmd_ ## C() : command(#C, aliases, parent, "", abstract, desc, \ + true, options::options_type() | opts) \ + {} \ + virtual void exec(app_state & app, \ + std::string const & name, \ + std::vector const & args); \ + }; \ + static cmd_ ## C C ## _cmd; \ +} \ +void commands::cmd_ ## C::exec(app_state & app, \ + std::string const & name, \ + std::vector const & args) \ +{ \ + I(false); \ +} + // Use this for commands that should specifically _not_ look for an // _MTN dir and load options from it. -#define CMD_NO_WORKSPACE(C, aliases, group, params, abstract, desc, opts) \ +#define CMD_NO_WORKSPACE(C, aliases, parent, params, abstract, desc, opts) \ namespace commands { \ struct cmd_ ## C : public command \ { \ - cmd_ ## C() : command(#C, aliases, group, params, abstract, \ + cmd_ ## C() : command(#C, aliases, parent, params, abstract, \ desc, false, \ options::options_type() | opts) \ {} \ ============================================================ --- cmd_db.cc 0a9a11a39e28af5f5488091c12d85341340298c3 +++ cmd_db.cc 7c6ee07a886223cd0a13d0376798befd22dc7e42 @@ -210,7 +210,7 @@ CMD(complete, "", N_("informative"), N_( throw usage(name); } -CMD(test_migration_step, "", hidden_group(), "SCHEMA", +CMD(test_migration_step, "", hidden_parent(), "SCHEMA", N_("Runs one step of migration on the specified database"), N_("This command migrates the given database from the specified schema " "in SCHEMA to its successor."), ============================================================ --- cmd_key_cert.cc ee6de8d946fe1205c3857eb6eebbd399b1efef92 +++ cmd_key_cert.cc a6d2d847b28748f03595fd2b9ea210865171dd5d @@ -29,7 +29,7 @@ using Botan::RSA_PrivateKey; using Botan::Pipe; using Botan::RSA_PrivateKey; -CMD(genkey, "", N_("key and cert"), N_("KEYID"), +CMD(genkey, "", N_("key_and_cert"), N_("KEYID"), N_("Generates an RSA key-pair"), N_(""), options::opts::none) @@ -57,7 +57,7 @@ CMD(genkey, "", N_("key and cert"), N_(" app.keys.put_key_pair(ident, kp); } -CMD(dropkey, "", N_("key and cert"), N_("KEYID"), +CMD(dropkey, "", N_("key_and_cert"), N_("KEYID"), N_("Drops a public and/or private key"), N_(""), options::opts::none) @@ -99,7 +99,7 @@ CMD(dropkey, "", N_("key and cert"), N_( N(key_deleted, fmt % idx(args, 0)()); } -CMD(passphrase, "", N_("key and cert"), N_("KEYID"), +CMD(passphrase, "", N_("key_and_cert"), N_("KEYID"), N_("Changes the passphrase of a private RSA key"), N_(""), options::opts::none) @@ -121,7 +121,7 @@ CMD(passphrase, "", N_("key and cert"), P(F("passphrase changed")); } -CMD(ssh_agent_export, "", N_("key and cert"), +CMD(ssh_agent_export, "", N_("key_and_cert"), N_("[FILENAME]"), N_("Exports a private key for use with ssh-agent"), N_(""), @@ -161,7 +161,7 @@ CMD(ssh_agent_export, "", N_("key and ce } } -CMD(ssh_agent_add, "", N_("key and cert"), "", +CMD(ssh_agent_add, "", N_("key_and_cert"), "", N_("Adds a private key to ssh-agent"), N_(""), options::opts::none) @@ -178,7 +178,7 @@ CMD(ssh_agent_add, "", N_("key and cert" app.agent.add_identity(*priv, id()); } -CMD(cert, "", N_("key and cert"), N_("REVISION CERTNAME [CERTVAL]"), +CMD(cert, "", N_("key_and_cert"), N_("REVISION CERTNAME [CERTVAL]"), N_("Creates a certificate for a revision"), N_(""), options::opts::none) @@ -211,7 +211,7 @@ CMD(cert, "", N_("key and cert"), N_("RE guard.commit(); } -CMD(trusted, "", N_("key and cert"), +CMD(trusted, "", N_("key_and_cert"), N_("REVISION NAME VALUE SIGNER1 [SIGNER2 [...]]"), N_("Tests whether a hypothetical certificate would be trusted"), N_("The current settings are used to run the test."), ============================================================ --- cmd_packet.cc 03af44ce9be22d3d6b1011eea767c8f761b1deaf +++ cmd_packet.cc 327c443c53e7e17d0837ebe60a0cec79cc36e8aa @@ -19,7 +19,7 @@ using std::vector; using std::istringstream; using std::vector; -CMD(pubkey, "", N_("packet i/o"), N_("ID"), +CMD(pubkey, "", N_("packet_io"), N_("ID"), N_("Prints a public key packet"), N_(""), options::opts::none) @@ -49,7 +49,7 @@ CMD(pubkey, "", N_("packet i/o"), N_("ID pw.consume_public_key(ident, key); } -CMD(privkey, "", N_("packet i/o"), N_("ID"), +CMD(privkey, "", N_("packet_io"), N_("ID"), N_("Prints a private key packet"), N_(""), options::opts::none) @@ -130,7 +130,7 @@ namespace } -CMD(read, "", N_("packet i/o"), "[FILE1 [FILE2 [...]]]", +CMD(read, "", N_("packet_io"), "[FILE1 [FILE2 [...]]]", N_("Reads packets from files"), N_("If no files are provided, the standard input is used."), options::opts::none) ============================================================ --- commands.cc 51c68a4e89f837a1d5d34a37defa2e9ece59020a +++ commands.cc 0c26aaf6b6178676c821c11587c3caa30ea5c9e7 @@ -1,4 +1,5 @@ // Copyright (C) 2002 Graydon Hoare +// Copyright (C) 2007 Julio M. Merino Vidal // // This program is made available under the GNU GPL version 2.0 or // greater. See the accompanying file COPYING for details. @@ -39,6 +40,59 @@ using std::vector; using std::strlen; using std::vector; +// +// Definition of top-level commands, used to classify the real commands +// in logical groups. +// +CMD_GROUP(automation, "", root_parent(), + N_("Commands that aid in scripted execution"), + N_(""), + options::opts::none); +CMD_GROUP(database, "", root_parent(), + N_("Commands that manipulate the database"), + N_(""), + options::opts::none); +CMD_GROUP(debug, "", root_parent(), + N_("Commands that aid in program debugging"), + N_(""), + options::opts::none); +CMD_GROUP(informative, "", root_parent(), + N_("Commands for information retrieval"), + N_(""), + options::opts::none); +CMD_GROUP(key_and_cert, "", root_parent(), + N_("Commands to manage keys and certificates"), + N_(""), + options::opts::none); +CMD_GROUP(network, "", root_parent(), + N_("Commands that access the network"), + N_(""), + options::opts::none); +CMD_GROUP(packet_io, "", root_parent(), + N_("Commands for packet reading and writing"), + N_(""), + options::opts::none); +CMD_GROUP(rcs, "", root_parent(), + N_("Commands for interaction with RCS and CVS"), + N_(""), + options::opts::none); +CMD_GROUP(review, "", root_parent(), + N_("Commands to review revisions"), + N_(""), + options::opts::none); +CMD_GROUP(tree, "", root_parent(), + N_("Commands to manipulate the tree"), + N_(""), + options::opts::none); +CMD_GROUP(vars, "", root_parent(), + N_("Commands to manage persistent variables"), + N_(""), + options::opts::none); +CMD_GROUP(workspace, "", root_parent(), + N_("Commands that deal with the workspace"), + N_(""), + options::opts::none); + // this file defines the task-oriented "top level" commands which can be // issued as part of a monotone command line. the command line can only // have one such command on it, followed by a vector of strings which are its @@ -74,7 +128,7 @@ namespace commands string const & d, bool u, options::options_type const & o) - : cmdgroup(g), params_(p), abstract_(a), desc_(d), + : parent(g), params_(p), abstract_(a), desc_(d), use_workspace_options(u), opts(o) { if (cmds == NULL) @@ -94,7 +148,10 @@ namespace commands } command::~command() {} std::string command::params() {return safe_gettext(params_.c_str());} - std::string command::abstract() {return safe_gettext(abstract_.c_str());} + std::string command::abstract() const + { + return safe_gettext(abstract_.c_str()); + } std::string command::desc() { return abstract() + ".\n" + safe_gettext(desc_.c_str()); @@ -104,11 +161,16 @@ namespace commands return opts; } bool operator<(command const & self, command const & other); - std::string const & hidden_group() + std::string const & root_parent() { - static const std::string the_hidden_group(""); - return the_hidden_group; + static const std::string the_root_parent("root"); + return the_root_parent; } + std::string const & hidden_parent() + { + static const std::string the_hidden_parent("hidden"); + return the_hidden_parent; + } }; namespace std @@ -128,34 +190,6 @@ namespace commands using std::greater; using std::ostream; - static map cmdgroups; - - static void init_cmdgroups(void) - { - if (cmdgroups.empty()) - { -#define insert(id, abstract) \ - cmdgroups.insert(map::value_type(id, abstract)); - - insert("automation", N_("Commands that aid in scripted execution")); - insert("database", N_("Commands that manipulate the database")); - insert("debug", N_("Commands that aid in program debugging")); - insert("informative", N_("Commands for information retrieval")); - insert("key and cert", N_("Commands to manage keys and certificates")); - insert("network", N_("Commands that access the network")); - insert("packet i/o", N_("Commands for packet reading and writing")); - insert("rcs", N_("Commands for interaction with RCS and CVS")); - insert("review", N_("Commands to review revisions")); - insert("tree", N_("Commands to manipulate the tree")); - insert("vars", N_("Commands to manage persistent variables")); - insert("workspace", N_("Commands that deal with the workspace")); - -#undef insert - } - - assert(!cmdgroups.empty()); - } - bool operator<(command const & self, command const & other) { // These two get the "minor" names of each command, as the 'names' @@ -163,8 +197,8 @@ namespace commands string const & selfname = *(self.names.begin()); string const & othername = *(other.names.begin()); // *twitch* - return ((string(_(self.cmdgroup.c_str())) < string(_(other.cmdgroup.c_str()))) - || ((self.cmdgroup == other.cmdgroup) + return ((string(_(self.parent.c_str())) < string(_(other.parent.c_str()))) + || ((self.parent == other.parent) && (string(_(selfname.c_str())) < (string(_(othername.c_str())))))); } @@ -174,76 +208,103 @@ namespace commands return iter == (*cmds).end() ? NULL : (*iter).second; } - string complete_command(string const & cmd) + static void init_children(void) { - if (cmd.length() == 0 || find_command(cmd) != NULL) - return cmd; + static bool children_inited = false; - L(FL("expanding command '%s'") % cmd); - - set matched; - - for (map::const_iterator i = (*cmds).begin(); - i != (*cmds).end(); ++i) + if (!children_inited) { - set< string > const & names = i->second->names; - - for (set< string >::const_iterator i2 = names.begin(); - i2 != names.end(); i2++) + for (map< string, command * >::iterator iter = (*cmds).begin(); + iter != (*cmds).end(); iter++) { - string const & name = *i2; + command * cmd = (*iter).second; - if (cmd.length() < name.length()) + if (cmd->parent != root_parent() && + cmd->parent != hidden_parent()) { - string prefix(name, 0, cmd.length()); - if (cmd == prefix) matched.insert(name); + command * cmdparent = find_command(cmd->parent); + assert(cmdparent != NULL); + cmdparent->children.insert(cmd); } } + + children_inited = true; } + } - // no matched commands - if (matched.size() == 0) - return ""; + set< command * > find_root_commands(void) + { + static set< command * > roots; - // one matched command - if (matched.size() == 1) + if (roots.empty()) { - string completed = *matched.begin(); - L(FL("expanded command to '%s'") % completed); - return completed; + for (map< string, command * >::const_iterator iter = (*cmds).begin(); + iter != (*cmds).end(); iter++) + { + command * cmd = (*iter).second; + + if (cmd->parent == root_parent()) + roots.insert(cmd); + } } - // more than one matched command - string err = (F("command '%s' has multiple ambiguous expansions:") % cmd).str(); - for (set::iterator i = matched.begin(); - i != matched.end(); ++i) - err += ('\n' + *i); - W(i18n_format(err)); - return ""; + return roots; } - string complete_command_group(string const & cmdgroup) + static void complete_names(string const & name, + set< string > const & names, + set< string > & matched) { - init_cmdgroups(); + for (set< string >::const_iterator iter = names.begin(); + iter != names.end(); iter++) + { + if (name.length() < (*iter).length()) + { + string prefix(*iter, 0, name.length()); + if (name == prefix) + matched.insert(*iter); + } + } + } - if (cmdgroup.length() == 0 || cmdgroups.find(cmdgroup) != cmdgroups.end()) - return cmdgroup; + static void complete_command_aux(string const & cmdname, + command const * curcmd, + int const maxlevel, + set< string > & matched) + { + I(!cmdname.empty()); + I(curcmd != NULL); + I(maxlevel >= 0); - L(FL("expanding command group '%s'") % cmdgroup); + complete_names(cmdname, curcmd->names, matched); - vector matched; - - for (map::const_iterator i = cmdgroups.begin(); - i != cmdgroups.end(); ++i) + if (maxlevel > 0) { - if (cmdgroup.length() < i->first.length()) + for (set< command * >::const_iterator iter = curcmd->children.begin(); + iter != curcmd->children.end(); iter++) { - string prefix(i->first, 0, cmdgroup.length()); - if (cmdgroup == prefix) - matched.push_back(i->first); + complete_names(cmdname, (*iter)->names, matched); + complete_command_aux(cmdname, *iter, maxlevel - 1, matched); } } + } + string complete_command(string const & cmd) + { + init_children(); + + if (cmd.length() == 0 || find_command(cmd) != NULL) + return cmd; + + L(FL("expanding command '%s'") % cmd); + + set< string > matched; + + set< command * > roots = find_root_commands(); + for (set< command * >::const_iterator iter = roots.begin(); + iter != roots.end(); iter++) + complete_command_aux(cmd, *iter, 2 /* XXX */, matched); + // no matched commands if (matched.size() == 0) return ""; @@ -252,27 +313,43 @@ namespace commands if (matched.size() == 1) { string completed = *matched.begin(); - L(FL("expanded command group to '%s'") % completed); + L(FL("expanded command to '%s'") % completed); return completed; } // more than one matched command - string err = (F("command group '%s' has multiple ambiguous " - "expansions:") % cmdgroup).str(); - for (vector::iterator i = matched.begin(); + string err = (F("command '%s' has multiple ambiguous expansions:") % cmd).str(); + for (set::iterator i = matched.begin(); i != matched.end(); ++i) err += ('\n' + *i); W(i18n_format(err)); + return ""; + } - return cmdgroup; + static string format_command_path(command const * cmd) + { + string path; + + if (cmd->parent == root_parent() || cmd->parent == hidden_parent()) + path = *(cmd->names.begin()); // XXX + else + { + command const * cmdparent = find_command(cmd->parent); + I(cmdparent != NULL); + + string const & name = *(cmd->names.begin()); // XXX + path = format_command_path(cmdparent) + " " + name; + } + + return path; } - // Generates a string of the form (a1, ..., aN), preceded by a space - // for simplicity reasons later on, where a1 through aN are the aliases - // for the command cmd. Returns the empty string if no aliases are - // defined for that command. + // Generates a string of the form "a1, ..., aN" where a1 through aN are + // all the elements of the 'names' set. The input set cannot be empty. static string format_names(set< string > const & names) { + I(names.size() > 0); + string text; set< string >::const_iterator iter = names.begin(); @@ -359,64 +436,29 @@ namespace commands out << std::endl; } - static void explain_cmdgroups(ostream & out ) + static void explain_children(set< command * > const & children, + ostream & out) { - size_t colabstract = 0; - for (map::const_iterator i = cmdgroups.begin(); - i != cmdgroups.end(); i++) - { - string const & name = (*i).first; + I(children.size() > 0); - size_t len = display_width(utf8(name + " ")); - if (colabstract < len) - colabstract = len; - } + vector< command * > sorted; - out << "Command groups:" << std::endl << std::endl; - for (map::const_iterator i = cmdgroups.begin(); - i != cmdgroups.end(); i++) - { - string const & name = (*i).first; - string const & abstract = (*i).second; - - describe(name, abstract, colabstract, out); - } - } - - static void explain_cmdgroup_usage(string const & cmdgroup, ostream & out) - { - init_cmdgroups(); - - map::const_iterator grpi = cmdgroups.find(cmdgroup); - assert(grpi != cmdgroups.end()); - size_t colabstract = 0; - vector sorted; - for (map::const_iterator i = (*cmds).begin(); - i != (*cmds).end(); ++i) + for (set< command * >::const_iterator i = children.begin(); + i != children.end(); i++) { - if (i->second->cmdgroup == cmdgroup) - { - sorted.push_back(i->second); + size_t len = display_width(utf8(format_names((*i)->names) + " ")); + if (colabstract < len) + colabstract = len; - string tag = format_names(i->second->names); - size_t len = display_width(utf8(tag + " ")); - if (colabstract < len) - colabstract = len; - } + sorted.push_back(*i); } - sort(sorted.begin(), sorted.end(), greater()); + sort(sorted.begin(), sorted.end(), greater< command * >()); - out << (*grpi).second << ":" << std::endl; - for (size_t i = 0; i < sorted.size(); ++i) - { - set< string > const & names = idx(sorted, i)->names; - string const & abstract = idx(sorted, i)->abstract(); - - string tag = format_names(names); - describe(tag, abstract, colabstract, out); - } + for (vector< command * >::const_iterator i = sorted.begin(); + i != sorted.end(); i++) + describe(format_names((*i)->names), (*i)->abstract(), colabstract, out); } static void explain_cmd_usage(string const & name, ostream & out) @@ -424,14 +466,29 @@ namespace commands command * cmd = find_command(name); // XXX Should be const. assert(cmd != NULL); - out << F(safe_gettext("Syntax specific to 'mtn %s':")) % name - << std::endl << std::endl; + vector< string > lines; + + // XXX Use ui.prog_name instead of hardcoding 'mtn'. + if (cmd->children.size() > 0) + out << F(safe_gettext("Subcommands for 'mtn %s':")) % + format_command_path(cmd) << std::endl << std::endl; + else + out << F(safe_gettext("Syntax specific to 'mtn %s':")) % + format_command_path(cmd) << std::endl << std::endl; + + // Print command parameters. string params = cmd->params(); - vector lines; split_into_lines(params, lines); for (vector::const_iterator j = lines.begin(); j != lines.end(); ++j) - out << " " << name << ' ' << *j << std::endl; + out << " " << name << ' ' << *j << std::endl << std::endl; + + if (cmd->children.size() > 0) + { + explain_children(cmd->children, out); + out << std::endl; + } + split_into_lines(cmd->desc(), lines); for (vector::const_iterator j = lines.begin(); j != lines.end(); ++j) @@ -439,6 +496,7 @@ namespace commands describe("", *j, 4, out); out << std::endl; } + if (cmd->names.size() > 1) { set< string > othernames = cmd->names; @@ -450,32 +508,22 @@ namespace commands void explain_usage(string const & name, ostream & out) { - init_cmdgroups(); + init_children(); - map::const_iterator cmdgroupiter; - - cmdgroupiter = cmdgroups.find(name); - if (find_command(name) != NULL) explain_cmd_usage(name, out); - else if (cmdgroupiter != cmdgroups.end()) - { - explain_cmdgroup_usage(name, out); - out << std::endl; - out << "For information on a specific command, type " - "'mtn help '." << std::endl; - out << std::endl; - } else { - assert(name.empty()); + I(name.empty()); - explain_cmdgroups(out); + // TODO Wrap long lines in these messages. + out << "Top-level commands:" << std::endl << std::endl; + explain_children(find_root_commands(), out); out << std::endl; - out << "To see what commands are available in a group, type " - "'mtn help '." << std::endl; out << "For information on a specific command, type " "'mtn help '." << std::endl; + out << "Note that you can always abbreviate a command name as " + "long as it does not conflict with other names." << std::endl; out << std::endl; } } @@ -546,28 +594,22 @@ CMD(help, "", N_("informative"), N_("com } string full_cmd = complete_command(idx(args, 0)()); - string full_cmdgroup = complete_command_group(idx(args, 0)()); - if (cmdgroups.find(full_cmdgroup) != cmdgroups.end()) + if (find_command(full_cmd) != NULL) { app.opts.help = true; - throw usage(full_cmdgroup); - } - else if (find_command(full_cmd) != NULL) - { - app.opts.help = true; throw usage(full_cmd); } else { // No matched commands or command groups - N(!full_cmd.empty() && full_cmdgroup.empty(), - F("unknown command or command group '%s'") % idx(args, 0)()); + N(!full_cmd.empty(), + F("unknown command '%s'") % idx(args, 0)()); throw usage(""); } } -CMD(crash, "", hidden_group(), "{ N | E | I | exception | signal }", +CMD(crash, "", hidden_parent(), "{ N | E | I | exception | signal }", N_("Triggers the specified kind of crash"), N_(""), options::opts::none) @@ -780,14 +822,18 @@ process_commit_message_args(bool & given #ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" -CMD(__test1, "", hidden_group(), "", "", "", options::opts::none) {} +CMD(__test1, "", hidden_parent(), "", "", "", options::opts::none) {} CMD(__test2, "__test2.1", - hidden_group(), "", "", "", options::opts::none) {} + hidden_parent(), "", "", "", options::opts::none) {} CMD(__test3, "__test3.1 __test3.2", - hidden_group(), "", "", "", options::opts::none) {} + hidden_parent(), "", "", "", options::opts::none) {} +CMD_GROUP(__group, "", root_parent(), "", "", options::opts::none); +CMD(__child1, "", "__group", "", "", "", options::opts::none) {} +CMD(__child2, "", "__group", "", "", "", options::opts::none) {} + UNIT_TEST(commands, find_command) { using namespace commands; @@ -804,8 +850,25 @@ UNIT_TEST(commands, find_command) BOOST_CHECK(find_command("__test2.1") != NULL); BOOST_CHECK(find_command("__test3.1") != NULL); BOOST_CHECK(find_command("__test3.2") != NULL); + + // Lookup a top-level group command. + BOOST_CHECK(find_command("__group") != NULL); + BOOST_CHECK(find_command("__group_ne") == NULL); + + // Lookup command that are one level deep in the tree. + BOOST_CHECK(find_command("__child1") != NULL); + BOOST_CHECK(find_command("__child2") != NULL); } +UNIT_TEST(commands, find_root_commands) +{ + using namespace commands; + + set< command * > roots = find_root_commands(); + BOOST_CHECK(roots.find(find_command("__group")) != roots.end()); + BOOST_CHECK(roots.find(find_command("__child1")) == roots.end()); +} + UNIT_TEST(commands, format_names) { using namespace commands; ============================================================ --- revision.cc 7c2a9fc44fab22eaf5166ac846d96400cf44e872 +++ revision.cc 842aedb1c3fea0703fdf97a4271aafb72c7aaa8e @@ -1804,7 +1804,7 @@ regenerate_caches(app_state & app) P(F("finished regenerating cached rosters and heights")); } -CMD(rev_height, "", hidden_group(), N_("REV"), +CMD(rev_height, "", hidden_parent(), N_("REV"), N_("Shows a revision's height"), N_(""), options::opts::none) ============================================================ --- sha1.cc 2417f83de1b98aacb1850b4d3e5221cf40a13cdf +++ sha1.cc 7d08674e126fc1f697c65720580e210e1e5ba3d6 @@ -92,7 +92,7 @@ void hook_botan_sha1() Botan::global_state().add_engine(new Monotone_SHA1_Engine); } -CMD(benchmark_sha1, "", hidden_group(), "", +CMD(benchmark_sha1, "", hidden_parent(), "", N_("Benchmarks SHA-1 cores"), N_(""), options::opts::none)