Below is the file 'cmd_automate.cc' from this revision. You can also download the file.
// Copyright (C) 2002, 2008 Graydon Hoare <graydon@pobox.com> // // This program is made available under the GNU GPL version 2.0 or // greater. See the accompanying file COPYING for details. // // This program is distributed WITHOUT ANY WARRANTY; without even the // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. #include "base.hh" #include <iostream> #include <sstream> #include <map> #include "cmd.hh" #include "app_state.hh" #include "ui.hh" #include "lua.hh" #include "lua_hooks.hh" #include "database.hh" #include "work.hh" using std::istream; using std::make_pair; using std::map; using std::ostream; using std::ostringstream; using std::pair; using std::set; using std::string; using std::vector; CMD_GROUP(automate, "automate", "au", CMD_REF(automation), N_("Interface for scripted execution"), ""); namespace commands { automate::automate(string const & name, string const & params, string const & abstract, string const & desc, options::options_type const & opts) : command(name, "", CMD_REF(automate), false, false, params, abstract, // We set use_workspace_options true, because all automate // commands need a database, and they expect to get the database // name from the workspace options, even if they don't need a // workspace for anything else. desc, true, opts, false) { } void automate::exec(app_state & app, command_id const & execid, args_vector const & args, std::ostream & output) const { make_io_binary(); exec_from_automate(app, execid, args, output); } void automate::exec(app_state & app, command_id const & execid, args_vector const & args) const { exec(app, execid, args, std::cout); } } static string const interface_version = "8.0"; // Major or minor number only increments once for each monotone release; // check the most recent release before incrementing this. // Name: interface_version // Arguments: none // Added in: 0.0 // Purpose: Prints version of automation interface. Major number increments // whenever a backwards incompatible change is made; minor number increments // whenever any change is made (but is reset when major number // increments). // Output format: "<decimal number>.<decimal number>\n". Always matches // "[0-9]+\.[0-9]+\n". // Error conditions: None. CMD_AUTOMATE(interface_version, "", N_("Prints the automation interface's version"), "", options::opts::none) { N(args.size() == 0, F("no arguments needed")); output << interface_version << '\n'; } // Name: stdio // Arguments: none // Added in: 1.0 // Purpose: Allow multiple automate commands to be run from one instance // of monotone. // // Input format: The input is a series of lines of the form // 'l'<size>':'<string>[<size>':'<string>...]'e', with characters // after the 'e' of one command, but before the 'l' of the next ignored. // This space is reserved, and should not contain characters other // than '\n'. // Example: // l6:leavese // l7:parents40:0e3171212f34839c2e3263e7282cdeea22fc5378e // // Output format: <command number>:<err code>:<last?>:<size>:<output> // <command number> is a decimal number specifying which command // this output is from. It is 0 for the first command, and increases // by one each time. // <err code> is 0 for success, 1 for a syntax error, and 2 for any // other error. // <last?> is 'l' if this is the last piece of output for this command, // and 'm' if there is more output to come. // <size> is the number of bytes in the output. // <output> is the output of the command. // Example: // 0:0:l:205:0e3171212f34839c2e3263e7282cdeea22fc5378 // 1f4ef73c3e056883c6a5ff66728dd764557db5e6 // 2133c52680aa2492b18ed902bdef7e083464c0b8 // 23501f8afd1f9ee037019765309b0f8428567f8a // 2c295fcf5fe20301557b9b3a5b4d437b5ab8ec8c // 1:0:l:41:7706a422ccad41621c958affa999b1a1dd644e79 // // Error conditions: Errors encountered by the commands run only set // the error code in the output for that command. Malformed input // results in exit with a non-zero return value and an error message. // automate_streambuf and automate_ostream are so we can dump output at a // set length, rather than waiting until we have all of the output. class automate_reader { istream & in; enum location {opt, cmd, none, eof}; location loc; bool get_string(std::string & out) { out.clear(); if (loc == none || loc == eof) { return false; } size_t size(0); char c; read(&c, 1); if (c == 'e') { loc = none; return false; } while(c <= '9' && c >= '0') { size = (size*10)+(c-'0'); read(&c, 1); } E(c == ':', F("Bad input to automate stdio: expected ':' after string size")); char *str = new char[size]; size_t got = 0; while(got < size) { int n = read(str+got, size-got); got += n; } out = std::string(str, size); delete[] str; L(FL("Got string '%s'") % out); return true; } std::streamsize read(char *buf, size_t nbytes, bool eof_ok = false) { std::streamsize rv; rv = in.rdbuf()->sgetn(buf, nbytes); E(eof_ok || rv > 0, F("Bad input to automate stdio: unexpected EOF")); return rv; } void go_to_next_item() { if (loc == eof) return; string starters("ol"); string whitespace(" \r\n\t"); string foo; while (loc != none) get_string(foo); char c('e'); do { if (read(&c, 1, true) == 0) { loc = eof; return; } } while (whitespace.find(c) != std::string::npos); switch (c) { case 'o': loc = opt; break; case 'l': loc = cmd; break; default: E(false, F("Bad input to automate stdio: unknown start token '%c'") % c); } } public: automate_reader(istream & is) : in(is), loc(none) {} bool get_command(vector<pair<string, string> > & params, vector<string> & cmdline) { params.clear(); cmdline.clear(); if (loc == none) go_to_next_item(); if (loc == eof) return false; else if (loc == opt) { string key, val; while(get_string(key) && get_string(val)) params.push_back(make_pair(key, val)); go_to_next_item(); } E(loc == cmd, F("Bad input to automate stdio: expected '%c' token") % cmd); string item; while (get_string(item)) { cmdline.push_back(item); } return true; } }; struct automate_streambuf : public std::streambuf { private: size_t _bufsize; std::ostream *out; automate_reader *in; int cmdnum; int err; public: automate_streambuf(size_t bufsize) : std::streambuf(), _bufsize(bufsize), out(0), in(0), cmdnum(0), err(0) { char *inbuf = new char[_bufsize]; setp(inbuf, inbuf + _bufsize); } automate_streambuf(std::ostream & o, size_t bufsize) : std::streambuf(), _bufsize(bufsize), out(&o), in(0), cmdnum(0), err(0) { char *inbuf = new char[_bufsize]; setp(inbuf, inbuf + _bufsize); } automate_streambuf(automate_reader & i, size_t bufsize) : std::streambuf(), _bufsize(bufsize), out(0), in(&i), cmdnum(0), err(0) { char *inbuf = new char[_bufsize]; setp(inbuf, inbuf + _bufsize); } ~automate_streambuf() {} void set_err(int e) { sync(); err = e; } void end_cmd() { _M_sync(true); ++cmdnum; err = 0; } virtual int sync() { _M_sync(); return 0; } void _M_sync(bool end = false) { if (!out) { setp(pbase(), pbase() + _bufsize); return; } int num = pptr() - pbase(); if (num || end) { (*out) << cmdnum << ':' << err << ':' << (end?'l':'m') << ':' << num << ':' << std::string(pbase(), num); setp(pbase(), pbase() + _bufsize); out->flush(); } } void write_out_of_band(char type, std::string const& data) { unsigned chunksize = _bufsize; size_t length = data.size(), offset = 0; do { if (offset+chunksize>length) chunksize = length-offset; (*out) << cmdnum << ':' << err << ':' << type << ':' << chunksize << ':' << data.substr(offset, chunksize); offset+= chunksize; } while (offset<length); out->flush(); } int_type overflow(int_type c = traits_type::eof()) { sync(); sputc(c); return 0; } }; struct automate_ostream : public std::ostream { automate_streambuf _M_autobuf; automate_ostream(std::ostream &out, size_t blocksize) : std::ostream(NULL), _M_autobuf(out, blocksize) { this->init(&_M_autobuf); } ~automate_ostream() {} automate_streambuf * rdbuf() const { return const_cast<automate_streambuf *>(&_M_autobuf); } void set_err(int e) { _M_autobuf.set_err(e); } void end_cmd() { _M_autobuf.end_cmd(); } }; static void out_of_band_to_automate_streambuf(char channel, std::string const& text, void *opaque) { reinterpret_cast<automate_streambuf*>(opaque)->write_out_of_band(channel, text); } CMD_AUTOMATE(stdio, "", N_("Automates several commands in one run"), "", options::opts::automate_stdio_size) { N(args.size() == 0, F("no arguments needed")); database db(app); // initialize the database early so any calling process is notified // immediately if a version discrepancy exists db.ensure_open(); automate_ostream os(output, app.opts.automate_stdio_size); automate_reader ar(std::cin); vector<pair<string, string> > params; vector<string> cmdline; global_sanity.set_out_of_band_handler(&out_of_band_to_automate_streambuf, &os._M_autobuf); while(ar.get_command(params, cmdline))//while(!EOF) { args_vector args; vector<string>::iterator i = cmdline.begin(); E(i != cmdline.end(), F("Bad input to automate stdio: command name is missing")); for (; i != cmdline.end(); ++i) { args.push_back(arg_type(*i)); } try { options::options_type opts; opts = options::opts::all_options() - options::opts::globals(); opts.instantiate(&app.opts).reset(); command_id id; for (args_vector::const_iterator iter = args.begin(); iter != args.end(); iter++) id.push_back(utf8((*iter)())); if (!id.empty()) { I(!args.empty()); set< command_id > matches = CMD_REF(automate)->complete_command(id); if (matches.size() == 0) { N(false, F("no completions for this command")); } else if (matches.size() > 1) { N(false, F("multiple completions possible for this command")); } id = *matches.begin(); I(args.size() >= id.size()); for (command_id::size_type i = 0; i < id.size(); i++) args.erase(args.begin()); command const * cmd = CMD_REF(automate)->find_command(id); I(cmd != NULL); automate const * acmd = reinterpret_cast< automate const * >(cmd); opts = options::opts::globals() | acmd->opts(); if (cmd->use_workspace_options()) { // Re-read the ws options file, rather than just copying // the options from the previous apts.opts object, because // the file may have changed due to user activity. workspace::check_ws_format(); workspace::get_ws_options(app.opts); } opts.instantiate(&app.opts).from_key_value_pairs(params); // set a fixed ticker type regardless what the user wants to // see, because anything else would screw the stdio-encoded output ui.set_tick_write_stdio(); acmd->exec_from_automate(app, id, args, os); } else opts.instantiate(&app.opts).from_key_value_pairs(params); } catch(informative_failure & f) { os.set_err(2); //Do this instead of printing f.what directly so the output //will be split into properly-sized blocks automatically. global_sanity.maybe_write_to_out_of_band_handler('e', f.what()); } os.end_cmd(); } global_sanity.set_out_of_band_handler(); } LUAEXT(mtn_automate, ) { args_vector args; std::stringstream output; bool result = true; std::stringstream & os = output; try { app_state* app_p = get_app_state(L); I(app_p != NULL); I(app_p->lua.check_lua_state(L)); E(app_p->mtn_automate_allowed, F("It is illegal to call the mtn_automate() lua extension,\n" "unless from a command function defined by register_command().")); // don't allow recursive calls app_p->mtn_automate_allowed = false; // automate_ostream os(output, app_p->opts.automate_stdio_size); int n = lua_gettop(L); E(n > 0, F("Bad input to mtn_automate() lua extension: command name is missing")); L(FL("Starting call to mtn_automate lua hook")); for (int i=1; i<=n; i++) { arg_type next_arg(luaL_checkstring(L, i)); L(FL("arg: %s")%next_arg()); args.push_back(next_arg); } commands::command_id id; for (args_vector::const_iterator iter = args.begin(); iter != args.end(); iter++) id.push_back(utf8((*iter)())); E(!id.empty(), F("no command found")); set< commands::command_id > matches = CMD_REF(automate)->complete_command(id); if (matches.size() == 0) { N(false, F("no completions for this command")); } else if (matches.size() > 1) { N(false, F("multiple completions possible for this command")); } id = *matches.begin(); I(args.size() >= id.size()); for (commands::command_id::size_type i = 0; i < id.size(); i++) args.erase(args.begin()); commands::command const * cmd = CMD_REF(automate)->find_command(id); I(cmd != NULL); commands::automate const * acmd = reinterpret_cast< commands::automate const * >(cmd); acmd->exec(*app_p, id, args, os); // allow further calls app_p->mtn_automate_allowed = true; } catch(informative_failure & f) { // informative failures are passed back to the caller result = false; L(FL("Informative failure caught inside lua call to mtn_automate: %s") % f.what()); os.flush(); output.flush(); output.str().clear(); os << f.what(); } catch (std::logic_error & e) { // invariant failures are permanent result = false; ui.fatal(e.what()); lua_pushstring(L, e.what()); lua_error(L); } os.flush(); lua_pushboolean(L, result); lua_pushstring(L, output.str().c_str()); return 2; } // Local Variables: // mode: C++ // fill-column: 76 // c-file-style: "gnu" // indent-tabs-mode: nil // End: // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: