# # # patch "asciik.cc" # from [c93f8aa68de5fdde236d1d6ca82924e6aad33f93] # to [d6e2122ca26ce509862799328430a47a56992a2f] # # patch "cmd.hh" # from [035c6902d1e393f75cd1038c48a19f2305640014] # to [d3c3fc52273553c99712772721878277e95ad6dd] # # patch "cmd_automate.cc" # from [21cb7f86d4fc6e33e3f6346bc16521f4317bbe4f] # to [d9f0c96fbf1a28ab130df20d86c0a09d2fd87a28] # # patch "cmd_db.cc" # from [7c6ee07a886223cd0a13d0376798befd22dc7e42] # to [3a960d8829125e7b89c899ad72251e1bf89d5a3a] # # patch "cmd_diff_log.cc" # from [5822504cb86d441755b7b3e29dd7802f32985e86] # to [2efa162a0b8960c8b59fc90511e740a3e874ae36] # # patch "cmd_files.cc" # from [929ca87b0e12c5127f70e01d8f4d1c43d5de294c] # to [9b70ba030e4f2499515a97c75ff1dbcea865a48d] # # patch "cmd_key_cert.cc" # from [a6d2d847b28748f03595fd2b9ea210865171dd5d] # to [8f49c311c06384161e99bdecb3a1fb9269e86c19] # # patch "cmd_list.cc" # from [1524a512fa17638d091937605591bc9d74c56c64] # to [a399752544510ca4a6fb5ce4e96c419b3fef743d] # # patch "cmd_merging.cc" # from [253ec239c75a1acb480398447547d7e346463636] # to [25048165330d8f7eee7f25786ed53d24c5657b26] # # patch "cmd_netsync.cc" # from [112367df549e8baf0dbbb5e62684b6b076093b07] # to [32ea7e1d6bd0d4c41c2818502d679ff297db1fc4] # # patch "cmd_othervcs.cc" # from [9150351d7bd8029429f505f1d08ef5775b80910b] # to [47c3fa7227298e1586f3b2c6d295e52a2af09f60] # # patch "cmd_packet.cc" # from [327c443c53e7e17d0837ebe60a0cec79cc36e8aa] # to [55a90475da7e0540fd7ac210741ea151efabe78c] # # patch "cmd_ws_commit.cc" # from [bf08400c71b6b82ba071b6c4e4033dcad240ab85] # to [782fff198802bd9032a00482e3a9169f39cea6e1] # # patch "commands.cc" # from [38463c25992cc776d9150992e6d31c27509c612b] # to [bac7ee1efa3e53a8ef5335d7f4303cbd0b88e969] # # patch "commands.hh" # from [ca9f64fec576524a9f7ca2cf84c75bb3f6eee05a] # to [80b7579b55e1340285975f0b68f7a4fbb541e908] # # patch "monotone.cc" # from [d0ec43295366c930d574c3852e3efab8596eb15c] # to [dd581646d1e81f9a512e69fd694304c19a5ce4c5] # # patch "revision.cc" # from [842aedb1c3fea0703fdf97a4271aafb72c7aaa8e] # to [41cfd3bfde9445d2301ef32f0f0f022fee83c832] # # patch "sha1.cc" # from [7d08674e126fc1f697c65720580e210e1e5ba3d6] # to [77161d5428c58c00bdb6909f01d5e94c7a87daa7] # # patch "simplestring_xform.cc" # from [cd4834f8b565f622d0a10d75904f5cb196e29d76] # to [5ddea3ab3ab80af864e0d7c283a097e2a5978e4c] # # patch "simplestring_xform.hh" # from [e83fbfd81cd0bd0f6470abc818dc3b26a6a10156] # to [f23bd9bd2bc2a91360f6a0deee4a01db3f881767] # ============================================================ --- asciik.cc c93f8aa68de5fdde236d1d6ca82924e6aad33f93 +++ asciik.cc d6e2122ca26ce509862799328430a47a56992a2f @@ -365,7 +365,7 @@ asciik::print(revision_id const & rev, } } -CMD(asciik, "", N_("debug"), N_("SELECTOR"), +CMD(asciik, "", CMD_REF(debug), N_("SELECTOR"), N_("Prints an ASCII representation of the revisions' graph"), N_(""), options::opts::none) ============================================================ --- cmd.hh 035c6902d1e393f75cd1038c48a19f2305640014 +++ cmd.hh d3c3fc52273553c99712772721878277e95ad6dd @@ -22,41 +22,64 @@ namespace commands namespace commands { - std::string const & hidden_parent(); - std::string const & root_parent(); + class command + { + public: + typedef std::set< utf8 > names_set; + typedef std::set< command * > children_set; - struct command - { + private: // NB: these strings are stored _un_translated, because we cannot // translate them until after main starts, by which time the // command objects have all been constructed. - std::set< std::string > names; - 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, - std::string const & p, - std::string const & a, - std::string const & d, - bool u, - options::options_type const & o); - virtual ~command(); - virtual std::string params(); - virtual std::string abstract() const; - virtual std::string desc(); - virtual options::options_type get_options(std::vector const & args); + utf8 m_primary_name; + names_set m_names; + command * m_parent; + utf8 m_params; + utf8 m_abstract; + utf8 m_desc; + bool m_use_workspace_options; + options::options_type m_opts; + children_set m_children; + + public: + command(std::string const & primary_name, + std::string const & other_names, + command * parent, + std::string const & params, + std::string const & abstract, + std::string const & desc, + bool use_workspace_options, + options::options_type const & opts); + + virtual ~command(void); + + command_id ident(void) const; + + utf8 const & primary_name(void) const; + names_set const & names(void) const; + command * parent(void) const; + virtual std::string params(void) const; + virtual std::string abstract(void) const; + virtual std::string desc(void) const; + options::options_type const & opts(void) const; + bool use_workspace_options(void) const; + children_set & children(void); + children_set const & children(void) const; + + bool operator<(command const & cmd) const; + virtual void exec(app_state & app, - std::string const & name, - std::vector const & args) = 0; + std::string const & name, // XXX Extra parameter + std::vector< utf8 > const & args) = 0; + + bool has_name(utf8 const & name) const; + void complete_child_name(utf8 const & prefix, + std::set< utf8 > & matches) const; + command * find_child_by_components(std::vector< utf8 > const & cs, + std::vector< utf8 > & rest); + command * find_child_by_name(utf8 const & name) const; }; - - command * find_command(command const * startcmd, std::string const & name); }; inline std::vector @@ -105,10 +128,19 @@ process_commit_message_args(bool & given app_state & app, utf8 message_prefix = utf8("")); +#define CMD_FWD_DECL(C) \ +namespace commands { \ + class cmd_ ## C; \ + extern cmd_ ## C C ## _cmd; \ +} + +#define CMD_REF(C) ((commands::command *)&(commands::C ## _cmd)) + #define CMD(C, aliases, parent, params, abstract, desc, opts) \ namespace commands { \ - struct cmd_ ## C : public command \ + class cmd_ ## C : public command \ { \ + public: \ cmd_ ## C() : command(#C, aliases, parent, params, abstract, \ desc, true, \ options::options_type() | opts) \ @@ -117,7 +149,7 @@ namespace commands { std::string const & name, \ std::vector const & args); \ }; \ - static cmd_ ## C C ## _cmd; \ + cmd_ ## C C ## _cmd; \ } \ void commands::cmd_ ## C::exec(app_state & app, \ std::string const & name, \ @@ -128,8 +160,9 @@ namespace commands { // and possibly "list".) #define CMD_WITH_SUBCMDS(C, aliases, parent, abstract, desc, opts) \ namespace commands { \ - struct cmd_ ## C : public command \ + class cmd_ ## C : public command \ { \ + public: \ cmd_ ## C() : command(#C, aliases, parent, "", abstract, desc, \ true, options::options_type() | opts) \ {} \ @@ -139,7 +172,7 @@ namespace commands { std::string params(); \ options::options_type get_options(vector const & args); \ }; \ - static cmd_ ## C C ## _cmd; \ + cmd_ ## C C ## _cmd; \ } \ void commands::cmd_ ## C::exec(app_state & app, \ std::string const & name, \ @@ -148,8 +181,9 @@ namespace commands { // XXX Should the 'opts' parameter go away? #define CMD_GROUP(C, aliases, parent, abstract, desc, opts) \ namespace commands { \ - struct cmd_ ## C : public command \ + class cmd_ ## C : public command \ { \ + public: \ cmd_ ## C() : command(#C, aliases, parent, "", abstract, desc, \ true, options::options_type() | opts) \ {} \ @@ -157,7 +191,7 @@ namespace commands { std::string const & name, \ std::vector const & args); \ }; \ - static cmd_ ## C C ## _cmd; \ + cmd_ ## C C ## _cmd; \ } \ void commands::cmd_ ## C::exec(app_state & app, \ std::string const & name, \ @@ -170,11 +204,11 @@ void commands::cmd_ ## C::exec(app_state ++i; \ vector< utf8 > removed (i, args.end()); \ /* XXX Command completion... */ \ - command * child = find_command(this, idx(args, 0)()); \ + /*command * child = find_command(this, idx(args, 0)()); \ if (child == NULL) \ throw usage(name); \ else \ - child->exec(app, idx(args, 0)(), removed); \ + child->exec(app, idx(args, 0)(), removed); */ \ } // Use this for commands that should specifically _not_ look for an @@ -182,8 +216,9 @@ namespace commands { #define CMD_NO_WORKSPACE(C, aliases, parent, params, abstract, desc, opts) \ namespace commands { \ - struct cmd_ ## C : public command \ + class cmd_ ## C : public command \ { \ + public: \ cmd_ ## C() : command(#C, aliases, parent, params, abstract, \ desc, false, \ options::options_type() | opts) \ @@ -192,12 +227,28 @@ namespace commands { std::string const & name, \ std::vector const & args); \ }; \ - static cmd_ ## C C ## _cmd; \ + cmd_ ## C C ## _cmd; \ } \ void commands::cmd_ ## C::exec(app_state & app, \ std::string const & name, \ std::vector const & args) \ +CMD_FWD_DECL(public); +CMD_FWD_DECL(hidden); + +CMD_FWD_DECL(automation); +CMD_FWD_DECL(database); +CMD_FWD_DECL(debug); +CMD_FWD_DECL(informative); +CMD_FWD_DECL(key_and_cert); +CMD_FWD_DECL(network); +CMD_FWD_DECL(packet_io); +CMD_FWD_DECL(rcs); +CMD_FWD_DECL(review); +CMD_FWD_DECL(tree); +CMD_FWD_DECL(variables); +CMD_FWD_DECL(workspace); + namespace automation { struct automate { ============================================================ --- cmd_automate.cc 21cb7f86d4fc6e33e3f6346bc16521f4317bbe4f +++ cmd_automate.cc d9f0c96fbf1a28ab130df20d86c0a09d2fd87a28 @@ -374,7 +374,7 @@ AUTOMATE(stdio, "", options::opts::autom } -CMD_WITH_SUBCMDS(automate, "", N_("automation"), +CMD_WITH_SUBCMDS(automate, "", CMD_REF(automation), N_("Interface for scripted execution"), N_("This set of commands provides a stable interface to run " "monotone from other, external tools and interact with it " ============================================================ --- cmd_db.cc 7c6ee07a886223cd0a13d0376798befd22dc7e42 +++ cmd_db.cc 3a960d8829125e7b89c899ad72251e1bf89d5a3a @@ -45,7 +45,7 @@ kill_rev_locally(app_state& app, string app.db.delete_existing_rev_and_certs(ident); } -CMD(db, "", N_("database"), +CMD(db, "", CMD_REF(database), N_("init\n" "info\n" "version\n" @@ -123,7 +123,7 @@ CMD(db, "", N_("database"), throw usage(name); } -CMD(set, "", N_("vars"), N_("DOMAIN NAME VALUE"), +CMD(set, "", CMD_REF(variables), N_("DOMAIN NAME VALUE"), N_("Sets a database variable"), N_("This command modifies (or adds if it did not exist before) the " "variable named NAME, stored in the database, and sets it to the " @@ -142,7 +142,7 @@ CMD(set, "", N_("vars"), N_("DOMAIN NAME app.db.set_var(make_pair(d, n), v); } -CMD(unset, "", N_("vars"), N_("DOMAIN NAME"), +CMD(unset, "", CMD_REF(variables), N_("DOMAIN NAME"), N_("Unsets a database variable"), N_("This command removes the variable NAME from domain DOMAIN, which" "was previously stored in the database."), @@ -161,7 +161,7 @@ CMD(unset, "", N_("vars"), N_("DOMAIN NA app.db.clear_var(k); } -CMD(complete, "", N_("informative"), N_("(revision|file|key) PARTIAL-ID"), +CMD(complete, "", CMD_REF(informative), N_("(revision|file|key) PARTIAL-ID"), N_("Completes a partial identifier"), N_(""), options::opts::verbose) @@ -210,7 +210,7 @@ CMD(complete, "", N_("informative"), N_( throw usage(name); } -CMD(test_migration_step, "", hidden_parent(), "SCHEMA", +CMD(test_migration_step, "", CMD_REF(hidden), "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_diff_log.cc 5822504cb86d441755b7b3e29dd7802f32985e86 +++ cmd_diff_log.cc 2efa162a0b8960c8b59fc90511e740a3e874ae36 @@ -468,7 +468,7 @@ prepare_diff(cset & included, revheader = header.str(); } -CMD(diff, "", N_("informative"), N_("[PATH]..."), +CMD(diff, "", CMD_REF(informative), N_("[PATH]..."), N_("Shows current differences"), N_("Compares the current tree with the files in the repository and " "prints the differences on the standard output.\n" @@ -600,7 +600,7 @@ typedef priority_queue >, rev_cmp> frontier_t; -CMD(log, "", N_("informative"), N_("[FILE] ..."), +CMD(log, "", CMD_REF(informative), N_("[FILE] ..."), N_("Prints history in reverse order"), N_("This command prints history in reverse order, filtering it by " "FILE if given. If one or more revisions are given, uses them as " ============================================================ --- cmd_files.cc 929ca87b0e12c5127f70e01d8f4d1c43d5de294c +++ cmd_files.cc 9b70ba030e4f2499515a97c75ff1dbcea865a48d @@ -26,7 +26,7 @@ using std::vector; // fload, fmerge, and fdiff are simple commands for debugging the line // merger. -CMD(fload, "", N_("debug"), "", +CMD(fload, "", CMD_REF(debug), "", N_("Loads a file's contents into the database"), N_(""), options::opts::none) @@ -44,7 +44,7 @@ CMD(fload, "", N_("debug"), "", guard.commit(); } -CMD(fmerge, "", N_("debug"), N_(" "), +CMD(fmerge, "", CMD_REF(debug), N_(" "), N_("Merges 3 files and outputs the result"), N_(""), options::opts::none) @@ -82,7 +82,7 @@ CMD(fmerge, "", N_("debug"), N_("content); } -CMD(cat, "", N_("informative"), +CMD(cat, "", CMD_REF(informative), N_("FILENAME"), N_("Prints a file from the database"), N_("Fetches the given file FILENAME from the database and prints it " ============================================================ --- cmd_key_cert.cc a6d2d847b28748f03595fd2b9ea210865171dd5d +++ cmd_key_cert.cc 8f49c311c06384161e99bdecb3a1fb9269e86c19 @@ -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, "", CMD_REF(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, "", CMD_REF(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, "", CMD_REF(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, "", CMD_REF(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, "", CMD_REF(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, "", CMD_REF(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, "", CMD_REF(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."), @@ -259,7 +259,7 @@ CMD(trusted, "", N_("key_and_cert"), << '\n'; // final newline is kept out of the translation } -CMD(tag, "", N_("review"), N_("REVISION TAGNAME"), +CMD(tag, "", CMD_REF(review), N_("REVISION TAGNAME"), N_("Puts a symbolic tag certificate on a revision"), N_(""), options::opts::none) @@ -273,7 +273,7 @@ CMD(tag, "", N_("review"), N_("REVISION } -CMD(testresult, "", N_("review"), N_("ID (pass|fail|true|false|yes|no|1|0)"), +CMD(testresult, "", CMD_REF(review), N_("ID (pass|fail|true|false|yes|no|1|0)"), N_("Notes the results of running a test on a revision"), N_(""), options::opts::none) @@ -287,7 +287,7 @@ CMD(testresult, "", N_("review"), N_("ID } -CMD(approve, "", N_("review"), N_("REVISION"), +CMD(approve, "", CMD_REF(review), N_("REVISION"), N_("Approves a particular revision"), N_(""), options::opts::branch) @@ -302,7 +302,7 @@ CMD(approve, "", N_("review"), N_("REVIS app.get_project().put_revision_in_branch(r, app.opts.branchname); } -CMD(comment, "", N_("review"), N_("REVISION [COMMENT]"), +CMD(comment, "", CMD_REF(review), N_("REVISION [COMMENT]"), N_("Comments on a particular revision"), N_(""), options::opts::none) ============================================================ --- cmd_list.cc 1524a512fa17638d091937605591bc9d74c56c64 +++ cmd_list.cc a399752544510ca4a6fb5ce4e96c419b3fef743d @@ -39,7 +39,15 @@ using std::vector; using std::string; using std::vector; -CMD(certs, "", "list", "ID", +CMD_GROUP(list, "ls", CMD_REF(informative), + N_("Shows database objects"), + N_("This command is used to query information from the database. " + "It shows database objects, or the current workspace manifest, " + "or known, unknown, intentionally ignored, missing, or " + "changed-state files."), + options::opts::none); + +CMD(certs, "", CMD_REF(list), "ID", N_("Lists certificates attached to an identifier"), N_(""), options::opts::depth | options::opts::exclude) @@ -142,7 +150,7 @@ CMD(certs, "", "list", "ID", guard.commit(); } -CMD(keys, "", "list", "[PATTERN]", +CMD(keys, "", CMD_REF(list), "[PATTERN]", N_("Lists keys that match a pattern"), N_(""), options::opts::depth | options::opts::exclude) @@ -236,7 +244,7 @@ CMD(keys, "", "list", "[PATTERN]", } } -CMD(branches, "", "list", "[PATTERN]", +CMD(branches, "", CMD_REF(list), "[PATTERN]", N_("Lists branches in the database that match a pattern"), N_(""), options::opts::depth | options::opts::exclude) @@ -264,7 +272,7 @@ CMD(branches, "", "list", "[PATTERN]", } } -CMD(epochs, "", "list", "[BRANCH [...]]", +CMD(epochs, "", CMD_REF(list), "[BRANCH [...]]", N_("Lists the current epoch of branches that match a pattern"), N_(""), options::opts::depth | options::opts::exclude) @@ -294,7 +302,7 @@ CMD(epochs, "", "list", "[BRANCH [...]]" } } -CMD(tags, "", "list", "", +CMD(tags, "", CMD_REF(list), "", N_("Lists all tags in the database"), N_(""), options::opts::depth | options::opts::exclude) @@ -310,7 +318,7 @@ CMD(tags, "", "list", "", } } -CMD(vars, "", "list", "[DOMAIN]", +CMD(vars, "", CMD_REF(list), "[DOMAIN]", N_("Lists variables in the whole database or a domain"), N_(""), options::opts::depth | options::opts::exclude) @@ -344,7 +352,7 @@ CMD(vars, "", "list", "[DOMAIN]", } } -CMD(known, "", "list", "", +CMD(known, "", CMD_REF(list), "", N_("Lists workspace files that belong to the current branch"), N_(""), options::opts::depth | options::opts::exclude) @@ -386,7 +394,7 @@ CMD(known, "", "list", "", } } -CMD(unknown, "ignored", "list", "", +CMD(unknown, "ignored", CMD_REF(list), "", N_("Lists workspace files that do not belong to the current branch"), N_(""), options::opts::depth | options::opts::exclude) @@ -417,7 +425,7 @@ CMD(unknown, "ignored", "list", "", } } -CMD(missing, "", "list", "", +CMD(missing, "", CMD_REF(list), "", N_("Lists files that belong to the branch but are not in the workspace"), N_(""), options::opts::depth | options::opts::exclude) @@ -441,7 +449,7 @@ CMD(missing, "", "list", "", } -CMD(changed, "", "list", "", +CMD(changed, "", CMD_REF(list), "", N_("Lists files that have changed with respect to the current revision"), N_(""), options::opts::depth | options::opts::exclude) @@ -497,14 +505,6 @@ CMD(changed, "", "list", "", } -CMD_GROUP(list, "ls", "informative", - N_("Shows database objects"), - N_("This command is used to query information from the database. " - "It shows database objects, or the current workspace manifest, " - "or known, unknown, intentionally ignored, missing, or " - "changed-state files."), - options::opts::none); - namespace { namespace syms ============================================================ --- cmd_merging.cc 253ec239c75a1acb480398447547d7e346463636 +++ cmd_merging.cc 25048165330d8f7eee7f25786ed53d24c5657b26 @@ -126,7 +126,7 @@ pick_branch_for_update(revision_id chose return switched_branch; } -CMD(update, "", N_("workspace"), "", +CMD(update, "", CMD_REF(workspace), "", N_("Updates the workspace"), N_("This command modifies your workspace to be based off of a " "different revision, preserving uncommitted changes as it does so. " @@ -329,7 +329,7 @@ merge_two(revision_id const & left, revi // since a single 'merge' command may perform arbitrarily many actual merges. // (Possibility: append the --message/--message-file text to the synthetic // log message constructed in merge_two().) -CMD(merge, "", N_("tree"), "", +CMD(merge, "", CMD_REF(tree), "", N_("Merges unmerged heads of a branch"), N_(""), options::opts::branch | options::opts::date | options::opts::author) @@ -430,7 +430,7 @@ CMD(merge, "", N_("tree"), "", P(F("note: your workspaces have not been updated")); } -CMD(propagate, "", N_("tree"), N_("SOURCE-BRANCH DEST-BRANCH"), +CMD(propagate, "", CMD_REF(tree), N_("SOURCE-BRANCH DEST-BRANCH"), N_("Merges from one branch to another asymmetrically"), N_(""), options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile) @@ -442,7 +442,7 @@ CMD(propagate, "", N_("tree"), N_("SOURC process(app, "merge_into_dir", a); } -CMD(merge_into_dir, "", N_("tree"), N_("SOURCE-BRANCH DEST-BRANCH DIR"), +CMD(merge_into_dir, "", CMD_REF(tree), N_("SOURCE-BRANCH DEST-BRANCH DIR"), N_("Merges one branch into a subdirectory in another branch"), N_(""), options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile) @@ -599,7 +599,7 @@ CMD(merge_into_dir, "", N_("tree"), N_(" } } -CMD(merge_into_workspace, "", N_("tree"), +CMD(merge_into_workspace, "", CMD_REF(tree), N_("OTHER-REVISION"), N_("Merges a revision into the current workspace's base revision"), N_("Merges OTHER-REVISION into the current workspace's base revision, " @@ -693,7 +693,7 @@ CMD(merge_into_workspace, "", N_("tree") "[right] %s\n") % left_id % right_id); } -CMD(explicit_merge, "", N_("tree"), +CMD(explicit_merge, "", CMD_REF(tree), N_("LEFT-REVISION RIGHT-REVISION DEST-BRANCH"), N_("Merges two explicitly given revisions"), N_("The results of the merge are placed on the branch specified by " @@ -720,7 +720,7 @@ CMD(explicit_merge, "", N_("tree"), merge_two(left, right, branch, string("explicit merge"), app); } -CMD(show_conflicts, "", N_("informative"), N_("REV REV"), +CMD(show_conflicts, "", CMD_REF(informative), N_("REV REV"), N_("Shows what conflicts need resolution between two revisions"), N_("The conflicts are calculated based on the two revisions given in " "the REV parameters."), @@ -762,7 +762,7 @@ CMD(show_conflicts, "", N_("informative" % result.directory_loop_conflicts.size()); } -CMD(pluck, "", N_("workspace"), N_("[-r FROM] -r TO [PATH...]"), +CMD(pluck, "", CMD_REF(workspace), N_("[-r FROM] -r TO [PATH...]"), N_("Applies changes made at arbitrary places in history"), N_("This command takes changes made at any point in history, and " "edits your current workspace to include those changes. The end result " @@ -929,7 +929,7 @@ CMD(pluck, "", N_("workspace"), N_("[-r } } -CMD(heads, "", N_("tree"), "", +CMD(heads, "", CMD_REF(tree), "", N_("Shows unmerged head revisions of a branch"), N_(""), options::opts::branch) @@ -955,7 +955,7 @@ CMD(heads, "", N_("tree"), "", cout << describe_revision(app, *i) << '\n'; } -CMD(get_roster, "", N_("debug"), N_("[REVID]"), +CMD(get_roster, "", CMD_REF(debug), N_("[REVID]"), N_("Dumps the roster associated with a given identifier"), N_("If no REVID is given, the workspace is used."), options::opts::none) ============================================================ --- cmd_netsync.cc 112367df549e8baf0dbbb5e62684b6b076093b07 +++ cmd_netsync.cc 32ea7e1d6bd0d4c41c2818502d679ff297db1fc4 @@ -121,7 +121,7 @@ extract_patterns(vector const & ar } } -CMD(push, "", N_("network"), N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"), +CMD(push, "", CMD_REF(network), N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"), N_("Pushes branches to a netsync server"), N_("This will push all branches that match the pattern given in PATTERN " "to the netsync server at the address ADDRESS."), @@ -138,7 +138,7 @@ CMD(push, "", N_("network"), N_("[ADDRES include_pattern, exclude_pattern, app); } -CMD(pull, "", N_("network"), N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"), +CMD(pull, "", CMD_REF(network), N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"), N_("Pulls branches from a netsync server"), N_("This pulls all branches that match the pattern given in PATTERN " "from the netsync server at the address ADDRESS."), @@ -156,7 +156,7 @@ CMD(pull, "", N_("network"), N_("[ADDRES include_pattern, exclude_pattern, app); } -CMD(sync, "", N_("network"), N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"), +CMD(sync, "", CMD_REF(network), N_("[ADDRESS[:PORTNUMBER] [PATTERN ...]]"), N_("Synchronizes branches with a netsync server"), N_("This synchronizes branches that match the pattern given in PATTERN " "with the netsync server at the address ADDRESS."), @@ -200,7 +200,7 @@ private: system_path dir; }; -CMD(clone, "", N_("network"), N_("ADDRESS[:PORTNUMBER] [DIRECTORY]"), +CMD(clone, "", CMD_REF(network), N_("ADDRESS[:PORTNUMBER] [DIRECTORY]"), N_("Checks out a revision from remote a database into a directory"), N_("If a revision is given, that's the one that will be checked out. " "Otherwise, it will be the head of the branch supplied. " @@ -387,7 +387,7 @@ private: system_path path; }; -CMD_NO_WORKSPACE(serve, "", N_("network"), "", +CMD_NO_WORKSPACE(serve, "", CMD_REF(network), "", N_("Serves the database to connecting clients"), N_(""), options::opts::bind | options::opts::pidfile | ============================================================ --- cmd_othervcs.cc 9150351d7bd8029429f505f1d08ef5775b80910b +++ cmd_othervcs.cc 47c3fa7227298e1586f3b2c6d295e52a2af09f60 @@ -13,7 +13,7 @@ using std::vector; using std::vector; -CMD(rcs_import, "", N_("debug"), N_("RCSFILE..."), +CMD(rcs_import, "", CMD_REF(debug), N_("RCSFILE..."), N_("Parses versions in RCS files"), N_("This command doesn't reconstruct or import revisions. " "You probably want to use cvs_import."), @@ -30,7 +30,7 @@ CMD(rcs_import, "", N_("debug"), N_("RCS } -CMD(cvs_import, "", N_("rcs"), N_("CVSROOT"), +CMD(cvs_import, "", CMD_REF(rcs), N_("CVSROOT"), N_("Imports all versions in a CVS repository"), N_(""), options::opts::branch) ============================================================ --- cmd_packet.cc 327c443c53e7e17d0837ebe60a0cec79cc36e8aa +++ cmd_packet.cc 55a90475da7e0540fd7ac210741ea151efabe78c @@ -19,7 +19,7 @@ using std::vector; using std::istringstream; using std::vector; -CMD(pubkey, "", N_("packet_io"), N_("ID"), +CMD(pubkey, "", CMD_REF(packet_io), N_("ID"), N_("Prints a public key packet"), N_(""), options::opts::none) @@ -49,7 +49,7 @@ CMD(pubkey, "", N_("packet_io"), N_("ID" pw.consume_public_key(ident, key); } -CMD(privkey, "", N_("packet_io"), N_("ID"), +CMD(privkey, "", CMD_REF(packet_io), N_("ID"), N_("Prints a private key packet"), N_(""), options::opts::none) @@ -130,7 +130,7 @@ namespace } -CMD(read, "", N_("packet_io"), "[FILE1 [FILE2 [...]]]", +CMD(read, "", CMD_REF(packet_io), "[FILE1 [FILE2 [...]]]", N_("Reads packets from files"), N_("If no files are provided, the standard input is used."), options::opts::none) ============================================================ --- cmd_ws_commit.cc bf08400c71b6b82ba071b6c4e4033dcad240ab85 +++ cmd_ws_commit.cc 782fff198802bd9032a00482e3a9169f39cea6e1 @@ -78,7 +78,7 @@ get_log_message_interactively(revision_t system_to_utf8(log_message_external, log_message); } -CMD(revert, "", N_("workspace"), N_("[PATH]..."), +CMD(revert, "", CMD_REF(workspace), N_("[PATH]..."), N_("Reverts files and/or directories"), N_("In order to revert the entire workspace, specify \".\" as the " "file name."), @@ -210,7 +210,7 @@ CMD(revert, "", N_("workspace"), N_("[PA app.work.maybe_update_inodeprints(); } -CMD(disapprove, "", N_("review"), N_("REVISION"), +CMD(disapprove, "", CMD_REF(review), N_("REVISION"), N_("Disapproves a particular revision"), N_(""), options::opts::branch | options::opts::messages | options::opts::date | @@ -264,7 +264,7 @@ CMD(disapprove, "", N_("review"), N_("RE } } -CMD(mkdir, "", N_("workspace"), N_("[DIRECTORY...]"), +CMD(mkdir, "", CMD_REF(workspace), N_("[DIRECTORY...]"), N_("Creates directories and adds them to the workspace"), N_(""), options::opts::no_ignore) @@ -309,7 +309,7 @@ CMD(mkdir, "", N_("workspace"), N_("[DIR app.work.perform_additions(paths, false, true); } -CMD(add, "", N_("workspace"), N_("[PATH]..."), +CMD(add, "", CMD_REF(workspace), N_("[PATH]..."), N_("Adds files to the workspace"), N_(""), options::opts::unknown | options::opts::no_ignore | @@ -343,7 +343,7 @@ CMD(add, "", N_("workspace"), N_("[PATH] app.work.perform_additions(paths, add_recursive, !app.opts.no_ignore); } -CMD(drop, "rm", N_("workspace"), N_("[PATH]..."), +CMD(drop, "rm", CMD_REF(workspace), N_("[PATH]..."), N_("Drops files from the workspace"), N_(""), options::opts::bookkeep_only | options::opts::missing | options::opts::recursive) @@ -372,7 +372,7 @@ CMD(drop, "rm", N_("workspace"), N_("[PA } -CMD(rename, "mv", N_("workspace"), +CMD(rename, "mv", CMD_REF(workspace), N_("SRC DEST\n" "SRC1 [SRC2 [...]] DEST_DIR"), N_("Renames entries in the workspace"), @@ -396,7 +396,7 @@ CMD(rename, "mv", N_("workspace"), } -CMD(pivot_root, "", N_("workspace"), N_("NEW_ROOT PUT_OLD"), +CMD(pivot_root, "", CMD_REF(workspace), N_("NEW_ROOT PUT_OLD"), N_("Renames the root directory"), N_("After this command, the directory that currently " "has the name NEW_ROOT " @@ -415,7 +415,7 @@ CMD(pivot_root, "", N_("workspace"), N_( app.work.perform_pivot_root(new_root, put_old, app.opts.bookkeep_only); } -CMD(status, "", N_("informative"), N_("[PATH]..."), +CMD(status, "", CMD_REF(informative), N_("[PATH]..."), N_("Shows workspace's status information"), N_(""), options::opts::depth | options::opts::exclude) @@ -489,7 +489,7 @@ CMD(status, "", N_("informative"), N_("[ } } -CMD(checkout, "co", N_("tree"), N_("[DIRECTORY]"), +CMD(checkout, "co", CMD_REF(tree), N_("[DIRECTORY]"), N_("Checks out a revision from the database into a directory"), N_("If a revision is given, that's the one that will be checked out. " "Otherwise, it will be the head of the branch (given or implicit). " @@ -596,7 +596,7 @@ CMD(checkout, "co", N_("tree"), N_("[DIR guard.commit(); } -CMD(attr, "", N_("workspace"), +CMD(attr, "", CMD_REF(workspace), N_("set PATH ATTR VALUE\nget PATH [ATTR]\ndrop PATH [ATTR]"), N_("Manages file attributes"), N_("This command is used to set, get or drop file attributes."), @@ -698,7 +698,7 @@ CMD(attr, "", N_("workspace"), -CMD(commit, "ci", N_("workspace"), N_("[PATH]..."), +CMD(commit, "ci", CMD_REF(workspace), N_("[PATH]..."), N_("Commits workspace changes to the database"), N_(""), options::opts::branch | options::opts::message | options::opts::msgfile @@ -945,7 +945,7 @@ CMD(commit, "ci", N_("workspace"), N_("[ } } -CMD_NO_WORKSPACE(setup, "", N_("tree"), N_("[DIRECTORY]"), +CMD_NO_WORKSPACE(setup, "", CMD_REF(tree), N_("[DIRECTORY]"), N_("Sets up a new workspace directory"), N_("If no directory is specified, uses the current directory."), options::opts::branch) @@ -969,7 +969,7 @@ CMD_NO_WORKSPACE(setup, "", N_("tree"), app.work.put_work_rev(rev); } -CMD_NO_WORKSPACE(import, "", N_("tree"), N_("DIRECTORY"), +CMD_NO_WORKSPACE(import, "", CMD_REF(tree), N_("DIRECTORY"), N_("Imports the contents of a directory into a branch"), N_(""), options::opts::branch | options::opts::revision | @@ -1070,7 +1070,7 @@ CMD_NO_WORKSPACE(import, "", N_("tree"), delete_dir_recursive(bookkeeping_root); } -CMD_NO_WORKSPACE(migrate_workspace, "", N_("tree"), N_("[DIRECTORY]"), +CMD_NO_WORKSPACE(migrate_workspace, "", CMD_REF(tree), N_("[DIRECTORY]"), N_("Migrates a workspace directory's metadata to the latest format"), N_("If no directory is given, defaults to the current workspace."), options::opts::none) @@ -1084,7 +1084,7 @@ CMD_NO_WORKSPACE(migrate_workspace, "", app.work.migrate_ws_format(); } -CMD(refresh_inodeprints, "", N_("tree"), "", +CMD(refresh_inodeprints, "", CMD_REF(tree), "", N_("Refreshes the inodeprint cache"), N_(""), options::opts::none) ============================================================ --- commands.cc 38463c25992cc776d9150992e6d31c27509c612b +++ commands.cc bac7ee1efa3e53a8ef5335d7f4303cbd0b88e969 @@ -41,55 +41,59 @@ using std::vector; using std::strlen; using std::vector; +CMD_GROUP(the_root, "", NULL, N_(""), N_(""), options::opts::none); +CMD_GROUP(public, "", CMD_REF(the_root), N_(""), N_(""), options::opts::none); +CMD_GROUP(hidden, "", CMD_REF(the_root), N_(""), N_(""), options::opts::none); + // // Definition of top-level commands, used to classify the real commands // in logical groups. // -CMD_GROUP(automation, "", root_parent(), +CMD_GROUP(automation, "", CMD_REF(public), N_("Commands that aid in scripted execution"), N_(""), options::opts::none); -CMD_GROUP(database, "", root_parent(), +CMD_GROUP(database, "", CMD_REF(public), N_("Commands that manipulate the database"), N_(""), options::opts::none); -CMD_GROUP(debug, "", root_parent(), +CMD_GROUP(debug, "", CMD_REF(public), N_("Commands that aid in program debugging"), N_(""), options::opts::none); -CMD_GROUP(informative, "", root_parent(), +CMD_GROUP(informative, "", CMD_REF(public), N_("Commands for information retrieval"), N_(""), options::opts::none); -CMD_GROUP(key_and_cert, "", root_parent(), +CMD_GROUP(key_and_cert, "", CMD_REF(public), N_("Commands to manage keys and certificates"), N_(""), options::opts::none); -CMD_GROUP(network, "", root_parent(), +CMD_GROUP(network, "", CMD_REF(public), N_("Commands that access the network"), N_(""), options::opts::none); -CMD_GROUP(packet_io, "", root_parent(), +CMD_GROUP(packet_io, "", CMD_REF(public), N_("Commands for packet reading and writing"), N_(""), options::opts::none); -CMD_GROUP(rcs, "", root_parent(), +CMD_GROUP(rcs, "", CMD_REF(public), N_("Commands for interaction with RCS and CVS"), N_(""), options::opts::none); -CMD_GROUP(review, "", root_parent(), +CMD_GROUP(review, "", CMD_REF(public), N_("Commands to review revisions"), N_(""), options::opts::none); -CMD_GROUP(tree, "", root_parent(), +CMD_GROUP(tree, "", CMD_REF(public), N_("Commands to manipulate the tree"), N_(""), options::opts::none); -CMD_GROUP(variables, "", root_parent(), +CMD_GROUP(variables, "", CMD_REF(public), N_("Commands to manage persistent variables"), N_(""), options::opts::none); -CMD_GROUP(workspace, "", root_parent(), +CMD_GROUP(workspace, "", CMD_REF(public), N_("Commands that deal with the workspace"), N_(""), options::opts::none); @@ -119,255 +123,298 @@ namespace commands // guarantee about what order they'll be initialized in. So have this // be something that doesn't get automatic initialization, and initialize // it ourselves the first time we use it. - static map * cmds; - command::command(string const & n, - string const & aliases, - string const & g, - string const & p, - string const & a, - string const & d, - bool u, - options::options_type const & o) - : parent(g), params_(p), abstract_(a), desc_(d), - use_workspace_options(u), opts(o) + typedef map< command *, command * > relation_map; + static relation_map * cmds_relation_map = NULL; + + static void init_children(void) { - if (cmds == NULL) - cmds = new map< string, command * >; + static bool children_inited = false; - names.insert(n); - (*cmds)[n] = this; + if (!children_inited) + { + children_inited = true; - vector< string > as; - split_into_words(aliases, as); - for (vector< string >::const_iterator iter = as.begin(); - iter != as.end(); iter++) - { - names.insert(*iter); - (*cmds)[*iter] = this; + for (relation_map::iterator iter = cmds_relation_map->begin(); + iter != cmds_relation_map->end(); iter++) + { + if ((*iter).second != NULL) + (*iter).second->children().insert((*iter).first); + } } } - command::~command() {} - string command::params() {return safe_gettext(params_.c_str());} - string command::abstract() const + + // XXX Remove. + static command * find_command(string const & name) { - return safe_gettext(abstract_.c_str()); + return NULL; } - string command::desc() +} + +// +// Implementation of the commands::command class. +// +namespace commands { + command::command(std::string const & primary_name, + std::string const & other_names, + command * parent, + std::string const & params, + std::string const & abstract, + std::string const & desc, + bool use_workspace_options, + options::options_type const & opts) + : m_primary_name(utf8(primary_name)), + m_parent(parent), + m_params(utf8(params)), + m_abstract(utf8(abstract)), + m_desc(utf8(desc)), + m_use_workspace_options(use_workspace_options), + m_opts(opts) { - return abstract() + ".\n" + safe_gettext(desc_.c_str()); + // A warning about the parent pointer: commands are defined as global + // variables, so they are initialized during program startup. As they + // are spread over different compilation units, we have no idea of the + // order in which they will be initialized. Therefore, accessing + // *parent from here is dangerous. + // + // This is the reason for the cmds_relation_map. We cannot set up + // the m_children set until a late stage during program execution. + + if (cmds_relation_map == NULL) + cmds_relation_map = new relation_map(); + (*cmds_relation_map)[this] = m_parent; + + m_names.insert(m_primary_name); + + vector< utf8 > onv = split_into_words(utf8(other_names)); + m_names.insert(onv.begin(), onv.end()); } - options::options_type command::get_options(vector const & args) + + command::~command() { - return opts; } - bool operator<(command const & self, command const & other); - string const & root_parent() + + command_id + command::ident(void) const { - static const string the_root_parent("root"); - return the_root_parent; + command_id i; + + if (parent() != NULL) + i = parent()->ident(); + i.push_back(primary_name()); + + return i; } - string const & hidden_parent() + + const utf8 & + command::primary_name(void) const { - static const string the_hidden_parent("hidden"); - return the_hidden_parent; + return m_primary_name; } -}; -namespace std -{ - template <> - struct greater + const command::names_set & + command::names(void) const { - bool operator()(commands::command const * a, commands::command const * b) - { - return *a < *b; - } - }; -}; + return m_names; + } -namespace commands -{ - bool operator<(command const & self, command const & other) + command * + command::parent(void) const { - // These two get the "minor" names of each command, as the 'names' - // set is sorted alphabetically. - string const & selfname = *(self.names.begin()); - string const & othername = *(other.names.begin()); - // *twitch* - return ((string(_(self.parent.c_str())) < string(_(other.parent.c_str()))) - || ((self.parent == other.parent) - && (string(_(selfname.c_str())) < (string(_(othername.c_str())))))); + return m_parent; } - // XXX Remove this one. - static command * find_command(string const & name) + std::string + command::params() const { - map< string, command * >::iterator iter = (*cmds).find(name); - return iter == (*cmds).end() ? NULL : (*iter).second; + return safe_gettext(m_params().c_str()); } - command * find_command(command const * startcmd, string const & name) + std::string + command::abstract() const { - for (set< command * >::iterator iter = startcmd->children.begin(); - iter != startcmd->children.end(); iter++) - { - command * child = *iter; + return safe_gettext(m_abstract().c_str()); + } - for (set< string >::const_iterator iter2 = child->names.begin(); - iter2 != child->names.end(); iter2++) - { - if (*iter2 == name) - return child; - } - } + std::string + command::desc() const + { + return abstract() + ".\n" + safe_gettext(m_desc().c_str()); + } - return NULL; + options::options_type const & + command::opts(void) const + { + return m_opts; } - static void init_children(void) + bool + command::use_workspace_options(void) const { - static bool children_inited = false; + return m_use_workspace_options; + } - if (!children_inited) - { - for (map< string, command * >::iterator iter = (*cmds).begin(); - iter != (*cmds).end(); iter++) - { - command * cmd = (*iter).second; + command::children_set & + command::children(void) + { + init_children(); + return m_children; + } - if (cmd->parent != root_parent() && - cmd->parent != hidden_parent()) - { - command * cmdparent = find_command(cmd->parent); - assert(cmdparent != NULL); - cmdparent->children.insert(cmd); - } - } + command::children_set const & + command::children(void) const + { + init_children(); + return m_children; + } - children_inited = true; - } + bool + command::operator<(command const & cmd) const + { + // *twitch* + return (parent()->primary_name() < cmd.parent()->primary_name() || + ((parent() == cmd.parent()) && + primary_name() < cmd.primary_name())); } - set< command * > find_root_commands(void) + bool + command::has_name(utf8 const & name) const { - static set< command * > roots; - - if (roots.empty()) - { - 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); - } - } - - return roots; + return names().find(name) != names().end(); } - static void complete_names(string const & name, - set< string > const & names, - set< string > & matched) + void + command::complete_child_name(utf8 const & name, set< utf8 > & matches) + const { - for (set< string >::const_iterator iter = names.begin(); - iter != names.end(); iter++) + I(!name().empty()); + + for (children_set::const_iterator iter = children().begin(); + iter != children().end(); iter++) { - if (name.length() < (*iter).length()) + command const * child = *iter; + + for (names_set::const_iterator iter2 = child->names().begin(); + iter2 != child->names().end(); iter2++) { - string prefix(*iter, 0, name.length()); - if (name == prefix) - matched.insert(*iter); + if (name == *iter2) + matches.insert(*iter2); + else if (name().length() < (*iter2)().length()) + { + utf8 p(string((*iter2)(), 0, name().length())); + if (name == p) + matches.insert(*iter2); + } } } } - static void complete_command_aux(string const & cmdname, - command const * curcmd, - int const maxlevel, - set< string > & matched) + command * + command::find_child_by_components(vector< utf8 > const & cs, + vector< utf8 > & rest) { - I(!cmdname.empty()); - I(curcmd != NULL); - I(maxlevel >= 0); + command * cmd = this; - complete_names(cmdname, curcmd->names, matched); + vector< utf8 >::const_iterator iter; + for (iter = cs.begin(); iter != cs.end(); iter++) + { + set< utf8 > matches; + cmd->complete_child_name(*iter, matches); - if (maxlevel > 0) + N(matches.size() <= 1, + N_(F("Ambiguous command '%s'") % *iter)); + + if (matches.size() == 0) + break; + + I(matches.size() == 1); + cmd = cmd->find_child_by_name(*(matches.begin())); + I(cmd != NULL); + } + + if (iter != cs.end()) { - for (set< command * >::const_iterator iter = curcmd->children.begin(); - iter != curcmd->children.end(); iter++) - { - complete_names(cmdname, (*iter)->names, matched); - complete_command_aux(cmdname, *iter, maxlevel - 1, matched); - } + rest.clear(); + rest.insert(rest.end(), iter, cs.end()); } + + return cmd; } - string complete_command(string const & cmd) + command * + command::find_child_by_name(utf8 const & name) const { - init_children(); + I(!name().empty()); - if (cmd.length() == 0 || find_command(cmd) != NULL) - return cmd; + command * cmd = NULL; - L(FL("expanding command '%s'") % cmd); + for (children_set::const_iterator iter = children().begin(); + iter != children().end() && cmd == NULL; iter++) + { + command * child = *iter; - set< string > matched; + if (child->has_name(name)) + cmd = child; + } - 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); + return cmd; + } +}; - // no matched commands - if (matched.size() == 0) - return ""; +namespace std +{ + template <> + struct greater + { + bool operator()(commands::command const * a, commands::command const * b) + { + return *a < *b; + } + }; +}; - // one matched command - if (matched.size() == 1) - { - string completed = *matched.begin(); - L(FL("expanded command to '%s'") % completed); - return completed; - } +namespace commands +{ + command_id + complete_command(vector< utf8 > const & args, + vector< utf8 > & rest) + { + command * root = CMD_REF(public); + I(root != NULL); - // 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 ""; + command * cmd = root->find_child_by_components(args, rest); + I(cmd != NULL); + return cmd->ident(); } static string format_command_path(command const * cmd) { string path; + /* - if (cmd->parent == root_parent() || cmd->parent == hidden_parent()) - path = *(cmd->names.begin()); // XXX + if (cmd->parent() == NULL) + path = cmd->primary_name(); else { - command const * cmdparent = find_command(cmd->parent); + command const * cmdparent = cmd->parent(); I(cmdparent != NULL); - string const & name = *(cmd->names.begin()); // XXX + string const & name = cmd->primary_name(); path = format_command_path(cmdparent) + " " + name; } + */ return path; } // 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) + static string format_names(command::names_set const & names) { I(names.size() > 0); string text; +/* set< string >::const_iterator iter = names.begin(); do { @@ -377,6 +424,7 @@ namespace commands text += ", "; } while (iter != names.end()); +*/ return text; } @@ -388,6 +436,7 @@ namespace commands static void describe(const string & tag, const string & abstract, size_t colabstract, ostream & out) { + /* // The algorithm below avoids printing an space on entry (note that // there are two before the tag but just one after it) and considers // that the colabstract is always one unit less than that given on @@ -402,11 +451,10 @@ namespace commands out << ' '; col = colabstract - 1; - vector words; - split_into_words(abstract, words); + vector< utf8 > words = split_into_words(abstract); const size_t maxcol = terminal_width(); - vector::const_iterator i = words.begin(); + vector< utf8 >::const_iterator i = words.begin(); while (i != words.end()) { string const & word = *i; @@ -450,20 +498,22 @@ namespace commands i++; } out << endl; + */ } static void explain_children(set< command * > const & children, ostream & out) { + /* I(children.size() > 0); vector< command * > sorted; size_t colabstract = 0; - for (set< command * >::const_iterator i = children.begin(); + for (command::children_set::const_iterator i = children.begin(); i != children.end(); i++) { - size_t len = display_width(utf8(format_names((*i)->names) + " ")); + size_t len = display_width(utf8(format_names((*i)->names()) + " ")); if (colabstract < len) colabstract = len; @@ -474,18 +524,20 @@ namespace commands for (vector< command * >::const_iterator i = sorted.begin(); i != sorted.end(); i++) - describe(format_names((*i)->names), (*i)->abstract(), colabstract, out); + describe(format_names((*i)->names()), (*i)->abstract(), colabstract, out); + */ } static void explain_cmd_usage(string const & name, ostream & out) { + /* command * cmd = find_command(name); // XXX Should be const. assert(cmd != NULL); vector< string > lines; // XXX Use ui.prog_name instead of hardcoding 'mtn'. - if (cmd->children.size() > 0) + if (cmd->children().size() > 0) out << F(safe_gettext("Subcommands for 'mtn %s':")) % format_command_path(cmd) << endl << endl; else @@ -503,9 +555,9 @@ namespace commands out << endl; } - if (cmd->children.size() > 0) + if (cmd->children().size() > 0) { - explain_children(cmd->children, out); + explain_children(cmd->children(), out); out << endl; } @@ -517,19 +569,18 @@ namespace commands out << endl; } - if (cmd->names.size() > 1) + if (cmd->names().size() > 1) { - set< string > othernames = cmd->names; + command::names_set othernames = cmd->names(); othernames.erase(name); describe("", "Aliases: " + format_names(othernames) + ".", 4, out); out << endl; } + */ } void explain_usage(string const & name, ostream & out) { - init_children(); - if (find_command(name) != NULL) explain_cmd_usage(name, out); else @@ -538,7 +589,7 @@ namespace commands // TODO Wrap long lines in these messages. out << "Top-level commands:" << endl << endl; - explain_children(find_root_commands(), out); + explain_children(CMD_REF(public)->children(), out); out << endl; out << "For information on a specific command, type " "'mtn help '." << endl; @@ -550,17 +601,17 @@ namespace commands int process(app_state & app, string const & name, vector const & args) { - command * cmd = find_command(name); + command * cmd = NULL; if (cmd != NULL) { L(FL("executing command '%s'") % name); // at this point we process the data from _MTN/options if // the command needs it. - if (cmd->use_workspace_options) + if (cmd->use_workspace_options()) app.process_options(); - cmd->exec(app, name, args); + cmd->exec(app, "foo", args); return 0; } else @@ -572,12 +623,13 @@ namespace commands options::options_type command_options(vector const & cmdline) { +/* if (cmdline.empty()) return options::options_type(); string name = complete_command(idx(cmdline,0)()); if (!name.empty()) { - return find_command(name)->get_options(cmdline); + return find_command(name)->opts(); } else { @@ -585,6 +637,8 @@ namespace commands F("unknown command '%s'") % idx(cmdline, 0)); return options::options_type(); } +*/ + return options::options_type(); } options::options_type toplevel_command_options(string const & name) @@ -592,7 +646,7 @@ namespace commands command * cmd = find_command(name); if (cmd != NULL) { - return cmd->opts; + return cmd->opts(); } else { @@ -602,7 +656,7 @@ namespace commands } //////////////////////////////////////////////////////////////////////// -CMD(help, "", N_("informative"), N_("command [ARGS...]"), +CMD(help, "", CMD_REF(informative), N_("command [ARGS...]"), N_("Displays help about commands and options"), N_(""), options::opts::none) @@ -613,8 +667,21 @@ CMD(help, "", N_("informative"), N_("com throw usage(""); } - string full_cmd = complete_command(idx(args, 0)()); +/* + vector< string > fooargs,rest; + for (vector< utf8 >::const_iterator i = args.begin(); i != args.end(); i++) + fooargs.push_back((*i)()); + command & cmd = commands::find_command(fooargs, rest); + N(!rest.empty(), + F("could not match any command given '%s'; failed after '%s'") % + join_words(fooargs)() % join_words(rest)()); + + app.opts.help = true; + throw usage(fooargs); +*/ + +/* if (find_command(full_cmd) != NULL) { app.opts.help = true; @@ -627,9 +694,10 @@ CMD(help, "", N_("informative"), N_("com F("unknown command '%s'") % idx(args, 0)()); throw usage(""); } +*/ } -CMD(crash, "", hidden_parent(), "{ N | E | I | exception | signal }", +CMD(crash, "", CMD_REF(hidden), "{ N | E | I | exception | signal }", N_("Triggers the specified kind of crash"), N_(""), options::opts::none) @@ -842,74 +910,86 @@ process_commit_message_args(bool & given #ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" -CMD(__test1, "", hidden_parent(), "", "", "", options::opts::none) {} +CMD(__test1, "", CMD_REF(hidden), "", "", "", options::opts::none) {} CMD(__test2, "__test2.1", - hidden_parent(), "", "", "", options::opts::none) {} + CMD_REF(hidden), "", "", "", options::opts::none) {} CMD(__test3, "__test3.1 __test3.2", - hidden_parent(), "", "", "", options::opts::none) {} + CMD_REF(hidden), "", "", "", options::opts::none) {} -CMD_GROUP(__group, "", root_parent(), "", "", options::opts::none); -CMD(__child1, "", "__group", "", "", "", options::opts::none) {} -CMD(__child2, "", "__group", "", "", "", options::opts::none) {} +CMD(test_visible, "", CMD_REF(public), "", "", "", options::opts::none) {} +CMD(test_invisible, "", CMD_REF(hidden), "", "", "", options::opts::none) {} -UNIT_TEST(commands, find_command) -{ - using namespace commands; +CMD_GROUP(test_group, "", CMD_REF(public), "", "", options::opts::none); +CMD(test_subcmd, "", CMD_REF(test_group), "", "", "", options::opts::none) {} - // Non-existent command. - BOOST_CHECK(find_command("__test0") == NULL); +static void +cc_aux(commands::command * startcmd, + utf8 const & path, + commands::command const * expcmd, + utf8 const & exprest) +{ + I(!path.empty()); + I(path[0] != ' '); - // Lookup commands using their "primary" name. - BOOST_CHECK(find_command("__test1") != NULL); - BOOST_CHECK(find_command("__test2") != NULL); - BOOST_CHECK(find_command("__test3") != NULL); + vector< utf8 > components = split_into_words(path); - // Lookup commands using any of their "secondary" names. - BOOST_CHECK(find_command("__test2.1") != NULL); - BOOST_CHECK(find_command("__test3.1") != NULL); - BOOST_CHECK(find_command("__test3.2") != NULL); + vector< utf8 > rest; + commands::command * cmd = + startcmd->find_child_by_components(components, rest); - // Lookup a top-level group command. - BOOST_CHECK(find_command("__group") != NULL); - BOOST_CHECK(find_command("__group_ne") == NULL); + BOOST_CHECK(cmd == expcmd); - // Lookup command that are one level deep in the tree. - BOOST_CHECK(find_command("__child1") != NULL); - BOOST_CHECK(find_command("__child2") != NULL); + vector< utf8 > restwords = split_into_words(exprest); + BOOST_CHECK(rest == restwords); } -UNIT_TEST(commands, find_root_commands) +UNIT_TEST(commands, complete_command) { - using namespace commands; + using commands::command; + using commands::init_children; - set< command * > roots = find_root_commands(); - BOOST_CHECK(roots.find(find_command("__group")) != roots.end()); - BOOST_CHECK(roots.find(find_command("__child1")) == roots.end()); + command * root = CMD_REF(public); + BOOST_REQUIRE(root != NULL); + command * group = CMD_REF(test_group); + BOOST_REQUIRE(group != NULL); + + cc_aux(root, "test_group", CMD_REF(test_group), ""); + cc_aux(root, "test_gr", CMD_REF(test_group), ""); + cc_aux(root, "bar", root, "bar"); + + cc_aux(root, "test_group test_subcmd", CMD_REF(test_subcmd), ""); + cc_aux(root, "test_gr test_subcmd", CMD_REF(test_subcmd), ""); + cc_aux(root, "test_group test_sub", CMD_REF(test_subcmd), ""); + cc_aux(root, "test_gr test_sub", CMD_REF(test_subcmd), ""); + cc_aux(root, "test_group bar", group, "bar"); + + cc_aux(group, "test_subcmd", CMD_REF(test_subcmd), ""); + cc_aux(group, "test_sub", CMD_REF(test_subcmd), ""); + cc_aux(group, "bar", group, "bar"); } UNIT_TEST(commands, format_names) { - using namespace commands; + using commands::format_names; - // Command with one name. - BOOST_CHECK(format_names(find_command("__test1")->names) == - "__test1"); + set< string > s; - // Command with two names. - BOOST_CHECK(format_names(find_command("__test2")->names) == - "__test2, __test2.1"); - BOOST_CHECK(format_names(find_command("__test2.1")->names) == - "__test2, __test2.1"); + s.clear(); + s.insert("a"); + BOOST_CHECK(format_names(s) == "a"); - // Command with three names. - BOOST_CHECK(format_names(find_command("__test3")->names) == - "__test3, __test3.1, __test3.2"); - BOOST_CHECK(format_names(find_command("__test3.1")->names) == - "__test3, __test3.1, __test3.2"); - BOOST_CHECK(format_names(find_command("__test3.2")->names) == - "__test3, __test3.1, __test3.2"); + s.clear(); + s.insert("a"); + s.insert("b"); + BOOST_CHECK(format_names(s) == "a, b"); + + s.clear(); + s.insert("a"); + s.insert("b"); + s.insert("c"); + BOOST_CHECK(format_names(s) == "a, b, c"); } #endif // BUILD_UNIT_TESTS ============================================================ --- commands.hh ca9f64fec576524a9f7ca2cf84c75bb3f6eee05a +++ commands.hh 80b7579b55e1340285975f0b68f7a4fbb541e908 @@ -27,8 +27,11 @@ namespace commands { }; namespace commands { + typedef std::vector< utf8 > command_id; + void explain_usage(std::string const & cmd, std::ostream & out); - std::string complete_command(std::string const & cmd); + command_id complete_command(std::vector< utf8 > const & args, + std::vector< utf8 > & rest); int process(app_state & app, std::string const & cmd, std::vector const & args); options::options_type command_options(std::vector const & cmdline); ============================================================ --- monotone.cc d0ec43295366c930d574c3852e3efab8596eb15c +++ monotone.cc dd581646d1e81f9a512e69fd694304c19a5ce4c5 @@ -125,19 +125,21 @@ void localize_monotone() } // read command-line options and return the command name -string read_options(options & opts, vector args) +commands::command_id read_options(options & opts, vector< utf8 > args, vector< string > argsstr) { option::concrete_option_set optset = options::opts::all_options().instantiate(&opts); - optset.from_command_line(args); + optset.from_command_line(argsstr); // consume the command, and perform completion if necessary - string cmd; + commands::command_id cmd; if (!opts.args.empty()) { - cmd = commands::complete_command(idx(opts.args, 0)()); + vector args(args.begin(), args.end()); // XXX + vector rest; + cmd = commands::complete_command(args, rest); N(!cmd.empty(), - F("unknown command '%s'") % idx(opts.args, 0)()); + F("unknown command '%s'") % join_words(args)()); } // reparse options, now that we know what command-specific @@ -147,7 +149,7 @@ string read_options(options & opts, vect optset.reset(); optset = (options::opts::globals() | cmdopts).instantiate(&opts); - optset.from_command_line(args, false); + optset.from_command_line(argsstr, false); if (!opts.args.empty()) opts.args.erase(opts.args.begin()); @@ -183,13 +185,16 @@ cpp_main(int argc, char ** argv) save_initial_path(); // decode all argv values into a UTF-8 array - vector args; + vector< utf8 > args; + // XXX Remove argsstr. + vector< string > argsstr; for (int i = 1; i < argc; ++i) { external ex(argv[i]); utf8 ut; system_to_utf8(ex, ut); - args.push_back(ut()); + args.push_back(ut); + argsstr.push_back(ut()); } // find base name of executable, convert to utf8, and save it in the @@ -207,7 +212,9 @@ cpp_main(int argc, char ** argv) app_state app; try { - string cmd = read_options(app.opts, args); + commands::command_id cmd = read_options(app.opts, args, argsstr); + // XXX Remove cmdstr + string cmdstr = cmd.size() > 0 ? (*(cmd.begin()))() : ""; if (app.opts.version_given) { @@ -236,7 +243,7 @@ cpp_main(int argc, char ** argv) // stop here if they asked for help if (app.opts.help) { - throw usage(cmd); // cmd may be empty, and that's fine. + throw usage(cmdstr); // cmd may be empty, and that's fine. } // at this point we allow a workspace (meaning search for it @@ -250,7 +257,7 @@ cpp_main(int argc, char ** argv) if (!app.found_workspace && global_sanity.filename.empty()) global_sanity.filename = (app.opts.conf_dir / "dump").as_external(); - app.lua.hook_note_mtn_startup(args); + app.lua.hook_note_mtn_startup(argsstr); // main options processed, now invoke the // sub-command w/ remaining args @@ -261,7 +268,7 @@ cpp_main(int argc, char ** argv) else { vector args(app.opts.args.begin(), app.opts.args.end()); - return commands::process(app, cmd, args); + return commands::process(app, cmdstr, args); } } catch (option::option_error const & e) ============================================================ --- revision.cc 842aedb1c3fea0703fdf97a4271aafb72c7aaa8e +++ revision.cc 41cfd3bfde9445d2301ef32f0f0f022fee83c832 @@ -1804,7 +1804,7 @@ regenerate_caches(app_state & app) P(F("finished regenerating cached rosters and heights")); } -CMD(rev_height, "", hidden_parent(), N_("REV"), +CMD(rev_height, "", CMD_REF(hidden), N_("REV"), N_("Shows a revision's height"), N_(""), options::opts::none) ============================================================ --- sha1.cc 7d08674e126fc1f697c65720580e210e1e5ba3d6 +++ sha1.cc 77161d5428c58c00bdb6909f01d5e94c7a87daa7 @@ -92,7 +92,7 @@ void hook_botan_sha1() Botan::global_state().add_engine(new Monotone_SHA1_Engine); } -CMD(benchmark_sha1, "", hidden_parent(), "", +CMD(benchmark_sha1, "", CMD_REF(hidden), "", N_("Benchmarks SHA-1 cores"), N_(""), options::opts::none) ============================================================ --- simplestring_xform.cc cd4834f8b565f622d0a10d75904f5cb196e29d76 +++ simplestring_xform.cc 5ddea3ab3ab80af864e0d7c283a097e2a5978e4c @@ -104,33 +104,26 @@ split_into_lines(string const & in, split_into_lines(in, constants::default_encoding, out); } -void split_into_words(string const & in, - string const & encoding, - vector & out) +std::vector< utf8 > split_into_words(utf8 const & in) { - string lc_encoding = lowercase(encoding); - out.clear(); + string const & instr = in(); + vector< utf8 > out; string::size_type begin = 0; - string::size_type end = in.find_first_of(" ", begin); + string::size_type end = instr.find_first_of(" ", begin); while (end != string::npos && end >= begin) { - out.push_back(in.substr(begin, end-begin)); + out.push_back(utf8(instr.substr(begin, end-begin))); begin = end + 1; - if (begin >= in.size()) + if (begin >= instr.size()) break; - end = in.find_first_of(" ", begin); + end = instr.find_first_of(" ", begin); } - if (begin < in.size()) - out.push_back(in.substr(begin, in.size() - begin)); -} + if (begin < instr.size()) + out.push_back(utf8(instr.substr(begin, instr.size() - begin))); -void -split_into_words(string const & in, - vector & out) -{ - split_into_words(in, constants::default_encoding, out); + return out; } void ============================================================ --- simplestring_xform.hh e83fbfd81cd0bd0f6470abc818dc3b26a6a10156 +++ simplestring_xform.hh f23bd9bd2bc2a91360f6a0deee4a01db3f881767 @@ -1,9 +1,13 @@ #ifndef __SIMPLESTRING_XFORM_HH__ #define __SIMPLESTRING_XFORM_HH__ +#include #include +#include #include +class utf8; + std::string uppercase(std::string const & in); std::string lowercase(std::string const & in); @@ -14,13 +18,6 @@ void split_into_lines(std::string const std::string const & encoding, std::vector & out); -void split_into_words(std::string const & in, - std::vector & out); - -void split_into_words(std::string const & in, - std::string const & encoding, - std::vector & out); - void join_lines(std::vector const & in, std::string & out, std::string const & linesep); @@ -28,6 +25,16 @@ void join_lines(std::vector void join_lines(std::vector const & in, std::string & out); +std::vector< utf8 > split_into_words(utf8 const & in); + +template< class T > +T join_words(std::vector< T > const & in) +{ + std::ostringstream oss; + copy(in.begin(), in.end(), std::ostream_iterator< T >(oss, " ")); + return T(oss.str()); +} + void prefix_lines_with(std::string const & prefix, std::string const & lines, std::string & out);