# # # patch "ChangeLog" # from [8ad001e397961905c6701e2d1c3e3d5174587e5b] # to [fe8a8cb76e9eaf3b17ed4dc96d67ab1a080d4c31] # # patch "automate.cc" # from [febb0307697728b588047da229b8caff50bde7af] # to [092f5069e1ce9988dc6260f868f3a9cd7f14e187] # # patch "cmd.hh" # from [da72439bf1a60869412b33484c41395263a2bff5] # to [b603bd9f4ad5e79d24d128694352376d0d5719a6] # # patch "cmd_automate.cc" # from [2d7904ca8237b941718b5b09c0fb1b716aed1e90] # to [715e703430c1a05f19404fecfad9d37a8db6c47e] # # patch "cmd_list.cc" # from [c806e8166080cea95a0fde0b7a2f143146369054] # to [5ee878c767f661cad7404c436e9b5446d2c1122f] # # patch "commands.cc" # from [336bfd2eb6329ecc9eec4997bfdbf28672977f71] # to [01337415288f19b925c2097f54cb3fe05fce3ced] # # patch "commands.hh" # from [fde6d00fecaf5ab640a68502d0fd8974a50d555f] # to [40f8cd23665763c729da1e92e03128d77596748e] # # patch "monotone.cc" # from [9269ce199b4ac630d0f6d53ea272df7fe2787da9] # to [1e915356716ebbe2cd1044ea308366f12a8f6380] # # patch "tests/automate_stdio/__driver__.lua" # from [81b4962edeaa532641858a4a6deb5fda47d129d7] # to [940095b06b3f41a3aa9afca31c9e81c1d42740cc] # ============================================================ --- ChangeLog 8ad001e397961905c6701e2d1c3e3d5174587e5b +++ ChangeLog fe8a8cb76e9eaf3b17ed4dc96d67ab1a080d4c31 @@ -1,5 +1,12 @@ 2006-11-02 Timothy Brownawell + Allow commands with subcommands (such as automate) to take + different options depending on which subcommand was called. + Make AUTOMATE() specify what options are taken. Test (and + fix) having spaces between automate commands. + +2006-11-02 Timothy Brownawell + Allow automate commands to take options. Change "automate stdio" error handling (exit instead of returning usage information when given an invalid command line) and bump automate version. Increase ============================================================ --- automate.cc febb0307697728b588047da229b8caff50bde7af +++ automate.cc 092f5069e1ce9988dc6260f868f3a9cd7f14e187 @@ -61,7 +61,7 @@ using std::vector; // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If the branch does not exist, prints nothing. (There are // no heads.) -AUTOMATE(heads, N_("[BRANCH]")) +AUTOMATE(heads, N_("[BRANCH]"), options::opts::none) { if (args.size() > 1) throw usage(help_name); @@ -85,7 +85,7 @@ AUTOMATE(heads, N_("[BRANCH]")) // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -AUTOMATE(ancestors, N_("REV1 [REV2 [REV3 [...]]]")) +AUTOMATE(ancestors, N_("REV1 [REV2 [REV3 [...]]]"), options::opts::none) { if (args.size() == 0) throw usage(help_name); @@ -132,7 +132,7 @@ AUTOMATE(ancestors, N_("REV1 [REV2 [REV3 // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -AUTOMATE(descendents, N_("REV1 [REV2 [REV3 [...]]]")) +AUTOMATE(descendents, N_("REV1 [REV2 [REV3 [...]]]"), options::opts::none) { if (args.size() == 0) throw usage(help_name); @@ -180,7 +180,7 @@ AUTOMATE(descendents, N_("REV1 [REV2 [RE // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -AUTOMATE(erase_ancestors, N_("[REV1 [REV2 [REV3 [...]]]]")) +AUTOMATE(erase_ancestors, N_("[REV1 [REV2 [REV3 [...]]]]"), options::opts::none) { set revs; for (vector::const_iterator i = args.begin(); i != args.end(); ++i) @@ -212,7 +212,7 @@ AUTOMATE(erase_ancestors, N_("[REV1 [REV // // Error conditions: If the file name has no attributes, prints only the // format version, if the file is unknown, escalates -AUTOMATE(attributes, N_("FILE")) +AUTOMATE(attributes, N_("FILE"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -321,7 +321,7 @@ AUTOMATE(attributes, N_("FILE")) // newline. Revisions are printed in topologically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -AUTOMATE(toposort, N_("[REV1 [REV2 [REV3 [...]]]]")) +AUTOMATE(toposort, N_("[REV1 [REV2 [REV3 [...]]]]"), options::opts::none) { set revs; for (vector::const_iterator i = args.begin(); i != args.end(); ++i) @@ -353,7 +353,7 @@ AUTOMATE(toposort, N_("[REV1 [REV2 [REV3 // newline. Revisions are printed in topologically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -AUTOMATE(ancestry_difference, N_("NEW_REV [OLD_REV1 [OLD_REV2 [...]]]")) +AUTOMATE(ancestry_difference, N_("NEW_REV [OLD_REV1 [OLD_REV2 [...]]]"), options::opts::none) { if (args.size() == 0) throw usage(help_name); @@ -392,7 +392,7 @@ AUTOMATE(ancestry_difference, N_("NEW_RE // Output format: A list of revision ids, in hexadecimal, each followed by a // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: None. -AUTOMATE(leaves, "") +AUTOMATE(leaves, "", options::opts::none) { if (args.size() != 0) throw usage(help_name); @@ -420,7 +420,7 @@ AUTOMATE(leaves, "") // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If the revision does not exist, prints nothing to stdout, // prints an error message to stderr, and exits with status 1. -AUTOMATE(parents, N_("REV")) +AUTOMATE(parents, N_("REV"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -444,7 +444,7 @@ AUTOMATE(parents, N_("REV")) // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If the revision does not exist, prints nothing to stdout, // prints an error message to stderr, and exits with status 1. -AUTOMATE(children, N_("REV")) +AUTOMATE(children, N_("REV"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -478,7 +478,7 @@ AUTOMATE(children, N_("REV")) // The output as a whole is alphabetically sorted; additionally, the parents // within each line are alphabetically sorted. // Error conditions: None. -AUTOMATE(graph, "") +AUTOMATE(graph, "", options::opts::none) { if (args.size() != 0) throw usage(help_name); @@ -521,7 +521,7 @@ AUTOMATE(graph, "") // Output format: A list of revision ids, in hexadecimal, each followed by a // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: None. -AUTOMATE(select, N_("SELECTOR")) +AUTOMATE(select, N_("SELECTOR"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -703,7 +703,7 @@ extract_added_file_paths(addition_map co // Error conditions: If no workspace book keeping _MTN directory is found, // prints an error message to stderr, and exits with status 1. -AUTOMATE(inventory, "") +AUTOMATE(inventory, "", options::opts::none) { if (args.size() != 0) throw usage(help_name); @@ -890,7 +890,7 @@ AUTOMATE(inventory, "") // the same type will be sorted by the filename they refer to. // Error conditions: If the revision specified is unknown or invalid // prints an error message to stderr and exits with status 1. -AUTOMATE(get_revision, N_("[REVID]")) +AUTOMATE(get_revision, N_("[REVID]"), options::opts::none) { if (args.size() > 1) throw usage(help_name); @@ -934,7 +934,7 @@ AUTOMATE(get_revision, N_("[REVID]")) // on. This is the value stored in _MTN/revision // Error conditions: If no workspace book keeping _MTN directory is found, // prints an error message to stderr, and exits with status 1. -AUTOMATE(get_base_revision_id, "") +AUTOMATE(get_base_revision_id, "", options::opts::none) { if (args.size() > 0) throw usage(help_name); @@ -955,7 +955,7 @@ AUTOMATE(get_base_revision_id, "") // files in the workspace. // Error conditions: If no workspace book keeping _MTN directory is found, // prints an error message to stderr, and exits with status 1. -AUTOMATE(get_current_revision_id, "") +AUTOMATE(get_current_revision_id, "", options::opts::none) { if (args.size() > 0) throw usage(help_name); @@ -1020,7 +1020,7 @@ AUTOMATE(get_current_revision_id, "") // // Error conditions: If the revision ID specified is unknown or // invalid prints an error message to stderr and exits with status 1. -AUTOMATE(get_manifest_of, N_("[REVID]")) +AUTOMATE(get_manifest_of, N_("[REVID]"), options::opts::none) { if (args.size() > 1) throw usage(help_name); @@ -1063,7 +1063,7 @@ AUTOMATE(get_manifest_of, N_("[REVID]")) // // Error conditions: If the file id specified is unknown or invalid prints // an error message to stderr and exits with status 1. -AUTOMATE(get_file, N_("FILEID")) +AUTOMATE(get_file, N_("FILEID"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -1089,7 +1089,7 @@ AUTOMATE(get_file, N_("FILEID")) // // Error conditions: If the revision id specified is unknown or // invalid prints an error message to stderr and exits with status 1. -AUTOMATE(packet_for_rdata, N_("REVID")) +AUTOMATE(packet_for_rdata, N_("REVID"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -1115,7 +1115,7 @@ AUTOMATE(packet_for_rdata, N_("REVID")) // // Error conditions: If the revision id specified is unknown or // invalid prints an error message to stderr and exits with status 1. -AUTOMATE(packets_for_certs, N_("REVID")) +AUTOMATE(packets_for_certs, N_("REVID"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -1142,7 +1142,7 @@ AUTOMATE(packets_for_certs, N_("REVID")) // // Error conditions: If the file id specified is unknown or invalid // prints an error message to stderr and exits with status 1. -AUTOMATE(packet_for_fdata, N_("FILEID")) +AUTOMATE(packet_for_fdata, N_("FILEID"), options::opts::none) { if (args.size() != 1) throw usage(help_name); @@ -1169,7 +1169,7 @@ AUTOMATE(packet_for_fdata, N_("FILEID")) // // Error conditions: If any of the file ids specified are unknown or // invalid prints an error message to stderr and exits with status 1. -AUTOMATE(packet_for_fdelta, N_("OLD_FILE NEW_FILE")) +AUTOMATE(packet_for_fdelta, N_("OLD_FILE NEW_FILE"), options::opts::none) { if (args.size() != 2) throw usage(help_name); @@ -1203,7 +1203,7 @@ AUTOMATE(packet_for_fdelta, N_("OLD_FILE // Error conditions: If any of the revisions do not exist, prints // nothing to stdout, prints an error message to stderr, and exits // with status 1. -AUTOMATE(common_ancestors, N_("REV1 [REV2 [REV3 [...]]]")) +AUTOMATE(common_ancestors, N_("REV1 [REV2 [REV3 [...]]]"), options::opts::none) { if (args.size() == 0) throw usage(help_name); @@ -1266,7 +1266,7 @@ AUTOMATE(common_ancestors, N_("REV1 [REV // in alphabetically sorted order. // Error conditions: // None. -AUTOMATE(branches, "") +AUTOMATE(branches, "", options::opts::none) { if (args.size() > 0) throw usage(help_name); @@ -1314,7 +1314,7 @@ AUTOMATE(branches, "") // Stanzas are printed in arbitrary order. // Error conditions: // A run-time exception is thrown for illegal patterns. -AUTOMATE(tags, N_("[BRANCH_PATTERN]")) +AUTOMATE(tags, N_("[BRANCH_PATTERN]"), options::opts::none) { utf8 incl("*"); bool filtering(false); @@ -1410,7 +1410,7 @@ namespace // // Error conditions: If the passphrase is empty or the key already exists, // prints an error message to stderr and exits with status 1. -AUTOMATE(genkey, N_("KEYID PASSPHRASE")) +AUTOMATE(genkey, N_("KEYID PASSPHRASE"), options::opts::none) { if (args.size() != 2) throw usage(help_name); @@ -1469,7 +1469,7 @@ AUTOMATE(genkey, N_("KEYID PASSPHRASE")) // Sample output (for 'mtn automate get_option branch: // net.venge.monotone // -AUTOMATE(get_option, N_("OPTION")) +AUTOMATE(get_option, N_("OPTION"), options::opts::none) { if (!app.opts.unknown && (args.size() < 1)) throw usage(help_name); @@ -1515,7 +1515,7 @@ AUTOMATE(get_option, N_("OPTION")) // Sample output (for 'mtn automate get_content_changed 3bccff99d08421df72519b61a4dded16d1139c33 ChangeLog): // content_mark [276264b0b3f1e70fc1835a700e6e61bdbe4c3f2f] // -AUTOMATE(get_content_changed, N_("REV FILE")) +AUTOMATE(get_content_changed, N_("REV FILE"), options::opts::none) { if (args.size() != 2) throw usage(help_name); @@ -1575,7 +1575,7 @@ AUTOMATE(get_content_changed, N_("REV FI // // Sample output (for automate get_corresponding_path 91f25c8ee830b11b52dd356c925161848d4274d0 foo2 dae0d8e3f944c82a9688bcd6af99f5b837b41968; see automate_get_corresponding_path test) // file "foo" -AUTOMATE(get_corresponding_path, N_("REV1 FILE REV2")) +AUTOMATE(get_corresponding_path, N_("REV1 FILE REV2"), options::opts::none) { if (args.size() != 3) throw usage(help_name); ============================================================ --- cmd.hh da72439bf1a60869412b33484c41395263a2bff5 +++ cmd.hh b603bd9f4ad5e79d24d128694352376d0d5719a6 @@ -45,6 +45,7 @@ namespace commands virtual ~command(); virtual std::string params(); virtual std::string desc(); + virtual options::options_type get_options(std::vector const & args); virtual void exec(app_state & app, std::vector const & args) = 0; }; @@ -137,7 +138,7 @@ 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_PARAMS_FN(C, group, desc, opts) \ +#define CMD_WITH_SUBCMDS(C, group, desc, opts) \ namespace commands { \ struct cmd_ ## C : public command \ { \ @@ -147,6 +148,7 @@ namespace commands { virtual void exec(app_state & app, \ std::vector const & args); \ std::string params(); \ + options::options_type get_options(vector const & args); \ }; \ static cmd_ ## C C ## _cmd; \ } \ @@ -204,7 +206,9 @@ namespace automation { { std::string name; std::string params; - automate(std::string const & n, std::string const & p); + options::options_type options; + automate(std::string const & n, std::string const & p, + options::options_type const & o); virtual void run(std::vector args, std::string const & help_name, app_state & app, @@ -213,11 +217,13 @@ namespace automation { }; } -#define AUTOMATE(NAME, PARAMS) \ +#define AUTOMATE(NAME, PARAMS, OPTIONS) \ namespace automation { \ struct auto_ ## NAME : public automate \ { \ - auto_ ## NAME () : automate(#NAME, PARAMS) {} \ + auto_ ## NAME () \ + : automate(#NAME, PARAMS, options::options_type() | OPTIONS) \ + {} \ void run(std::vector args, std::string const & help_name, \ app_state & app, std::ostream & output) const; \ virtual ~auto_ ## NAME() {} \ ============================================================ --- cmd_automate.cc 2d7904ca8237b941718b5b09c0fb1b716aed1e90 +++ cmd_automate.cc 715e703430c1a05f19404fecfad9d37a8db6c47e @@ -26,8 +26,9 @@ namespace automation { // guarantees about initialization order. So, use something we can // initialize ourselves. static map * automations; - automate::automate(string const &n, string const &p) - : name(n), params(p) + automate::automate(string const &n, string const &p, + options::options_type const & o) + : name(n), params(p), options(o) { static bool first(true); if (first) @@ -40,18 +41,24 @@ namespace automation { automate::~automate() {} } +automation::automate & +find_automation(utf8 const & name, string const & root_cmd_name) +{ + map::const_iterator + i = automation::automations->find(name()); + if (i == automation::automations->end()) + throw usage(root_cmd_name); + else + return *(i->second); +} + void automate_command(utf8 cmd, vector args, string const & root_cmd_name, app_state & app, ostream & output) { - map::const_iterator - i = automation::automations->find(cmd()); - if (i == automation::automations->end()) - throw usage(root_cmd_name); - else - i->second->run(args, root_cmd_name, app, output); + find_automation(cmd, root_cmd_name).run(args, root_cmd_name, app, output); } static string const interface_version = "4.0"; @@ -65,7 +72,7 @@ static string const interface_version = // Output format: ".\n". Always matches // "[0-9]+\.[0-9]+\n". // Error conditions: None. -AUTOMATE(interface_version, "") +AUTOMATE(interface_version, "", options::opts::none) { if (args.size() != 0) throw usage(help_name); @@ -172,7 +179,7 @@ class automate_reader while (loc != none) get_string(foo); char c('e'); - while (whitespace.find(c) != std::string::npos) + do { if (read(&c, 1, true) == 0) { @@ -180,11 +187,7 @@ class automate_reader return; } } - if (read(&c, 1, true) == 0) - { - loc = eof; - return; - } + while (whitespace.find(c) != std::string::npos); switch (c) { case 'o': loc = opt; break; @@ -200,7 +203,7 @@ public: { params.clear(); cmdline.clear(); - while (loc == none) + if (loc == none) go_to_next_item(); if (loc == eof) return false; @@ -302,8 +305,8 @@ struct automate_ostream : public std::os automate_streambuf _M_autobuf; automate_ostream(std::ostream &out, size_t blocksize) - : _M_autobuf(out, blocksize) - , std::ostream(NULL) + : std::ostream(NULL), + _M_autobuf(out, blocksize) { this->init(&_M_autobuf); } ~automate_ostream() @@ -321,7 +324,7 @@ struct automate_ostream : public std::os }; -AUTOMATE(stdio, "") +AUTOMATE(stdio, "", options::opts::automate_stdio_size) { if (args.size() != 0) throw usage(help_name); @@ -342,9 +345,9 @@ AUTOMATE(stdio, "") } try { - option::concrete_option_set opts; - opts = options::opts::all_options().instantiate(&app.opts); - opts.from_key_value_pairs(params); + options::options_type opts = options::opts::globals(); + opts = opts | find_automation(cmd, help_name).options; + opts.instantiate(&app.opts).from_key_value_pairs(params); automate_command(cmd, args, help_name, app, os); } catch(informative_failure & f) @@ -359,10 +362,9 @@ AUTOMATE(stdio, "") } -CMD_PARAMS_FN(automate, N_("automation"), - N_("automation interface"), - /*options::opts::automate_stdio_size*/ - options::opts::all_options) +CMD_WITH_SUBCMDS(automate, N_("automation"), + N_("automation interface"), + options::opts::none) { if (args.size() == 0) throw usage(name); @@ -391,7 +393,15 @@ std::string commands::cmd_automate::para return out; } +options::options_type +commands::cmd_automate::get_options(vector const & args) +{ + if (args.size() < 2) + return options::options_type(); + return find_automation(idx(args,1), idx(args,0)()).options; +} + // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- cmd_list.cc c806e8166080cea95a0fde0b7a2f143146369054 +++ cmd_list.cc 5ee878c767f661cad7404c436e9b5446d2c1122f @@ -558,7 +558,7 @@ namespace // private_location "keystore" // // Error conditions: None. -AUTOMATE(keys, "") +AUTOMATE(keys, "", options::opts::none) { if (args.size() != 0) throw usage(help_name); @@ -649,7 +649,7 @@ AUTOMATE(keys, "") // key, a warning message is printed to stderr. If the revision // specified is unknown or invalid prints an error message to stderr // and exits with status 1. -AUTOMATE(certs, N_("REV")) +AUTOMATE(certs, N_("REV"), options::opts::none) { if (args.size() != 1) throw usage(help_name); ============================================================ --- commands.cc 336bfd2eb6329ecc9eec4997bfdbf28672977f71 +++ commands.cc 01337415288f19b925c2097f54cb3fe05fce3ced @@ -73,6 +73,10 @@ namespace commands command::~command() {} std::string command::params() {return safe_gettext(params_.c_str());} std::string command::desc() {return safe_gettext(desc_.c_str());} + options::options_type command::get_options(vector const & args) + { + return options; + } bool operator<(command const & self, command const & other); std::string const & hidden_group() { @@ -235,10 +239,25 @@ namespace commands } } - options::options_type command_options(string const & cmd) + options::options_type command_options(vector const & cmdline) { + if (cmdline.empty()) + return options::options_type(); + string cmd = complete_command(idx(cmdline,0)()); if ((*cmds).find(cmd) != (*cmds).end()) { + return (*cmds)[cmd]->get_options(cmdline); + } + else + { + return options::options_type(); + } + } + + options::options_type toplevel_command_options(string const & cmd) + { + if ((*cmds).find(cmd) != (*cmds).end()) + { return (*cmds)[cmd]->options; } else ============================================================ --- commands.hh fde6d00fecaf5ab640a68502d0fd8974a50d555f +++ commands.hh 40f8cd23665763c729da1e92e03128d77596748e @@ -16,6 +16,7 @@ #include #include "options.hh" +#include "vocab.hh" using boost::shared_ptr; @@ -36,7 +37,8 @@ namespace commands { void explain_usage(std::string const & cmd, std::ostream & out); std::string complete_command(std::string const & cmd); int process(app_state & app, std::string const & cmd, std::vector const & args); - options::options_type command_options(std::string const & cmd); + options::options_type command_options(std::vector const & cmdline); + options::options_type toplevel_command_options(std::string const & cmd); }; // Local Variables: ============================================================ --- monotone.cc 9269ce199b4ac630d0f6d53ea272df7fe2787da9 +++ monotone.cc 1e915356716ebbe2cd1044ea308366f12a8f6380 @@ -136,12 +136,13 @@ string read_options(options & opts, vect string cmd; if (!opts.args.empty()) cmd = commands::complete_command(idx(opts.args, 0)()); - optset.reset(); // reparse options, now that we know what command-specific // options are allowed. - options::options_type cmdopts = commands::command_options(cmd); + options::options_type cmdopts = commands::command_options(opts.args); + optset.reset(); + optset = (options::opts::globals() | cmdopts).instantiate(&opts); optset.from_command_line(args, false); @@ -275,7 +276,7 @@ cpp_main(int argc, char ** argv) // Make sure to hide documentation that's not part of // the current command. - options::options_type cmd_options = commands::command_options(u.which); + options::options_type cmd_options = commands::toplevel_command_options(u.which); if (!cmd_options.empty()) { usage_stream << F("Options specific to '%s %s':") % ui.prog_name % u.which << "\n\n"; ============================================================ --- tests/automate_stdio/__driver__.lua 81b4962edeaa532641858a4a6deb5fda47d129d7 +++ tests/automate_stdio/__driver__.lua 940095b06b3f41a3aa9afca31c9e81c1d42740cc @@ -8,9 +8,11 @@ check(mtn("automate", "stdio"), 1, false check(mtn("automate", "stdio"), 1, false, false, "xl6:leavese") check(mtn("automate", "stdio"), 1, false, false, "o3:key0:exl6:leavese") check(mtn("automate", "stdio"), 1, false, false, "o3:ke0:el6:leavese") +check(mtn("automate", "stdio"), 1, false, false, "o6:author3:fooe l6:leavese") -- not broken check(mtn("automate", "stdio"), 0, false, false, "o3:key0:el6:leavese") +check(mtn("automate", "stdio"), 0, false, false, "o3:key0:e l6:leavese") function parse_stdio(dat, which) local got = {}