# # # add_dir "tests/automate_lua" # # add_file "tests/automate_lua/__driver__.lua" # content [c404572e850ce7623fed9ee7013a6ec63e8fe276] # # add_file "tests/automate_lua/test.lua" # content [9ac45e93bf665fa4ea5a90828aa6e834c1aee7a8] # # patch "NEWS" # from [af85eefcea2e435fcdd6e04a48a4922f877e66cf] # to [94f333190db9922d097bcf481a0397914239cce4] # # patch "automate.cc" # from [598a04952daffa804d60dde204967b20e17fc37a] # to [240c35d1563672b59f932812acc0b6bb4d4775dc] # # patch "lua_hooks.cc" # from [0476cf2512e471ca31d9a3b185968487fbeb9e8e] # to [2cea55853bda5f175dc82c227ee21947236327e8] # # patch "lua_hooks.hh" # from [eb7235af2f81fcce2921725bfd150df2ba3bfc65] # to [a5ac6a6139a2672c9e8d16bb97162ac849179dda] # # patch "monotone.texi" # from [9b7b4b168714e8b980e72d95eba542ecc7234fcd] # to [794aeeeb1450859129acc05515a60a2dd32c793f] # # patch "std_hooks.lua" # from [c4f02c95a5717f64d0a60251081ff5bd1a8038de] # to [6fa95c0269b7e1b615bf5d2dad24e32948e48350] # ============================================================ --- tests/automate_lua/__driver__.lua c404572e850ce7623fed9ee7013a6ec63e8fe276 +++ tests/automate_lua/__driver__.lua c404572e850ce7623fed9ee7013a6ec63e8fe276 @@ -0,0 +1,76 @@ + +mtn_setup() +check(get("test.lua")) + +function equalRecursive(left, right) + if (type(left) ~= type(right)) then + L("type mismatch: ", type(left), " vs ", type(right), "\n") + return false + end + + if (type(left) == "table") then + for k,v in ipairs(left) do + -- a not existing key is a key with its value set to nil + if (right[k] == nil) then + L("key ", k, " does not exist in right", "\n") + return false + end + if (not equalRecursive(left[k], right[k])) then + return false + end + end + return true + end + + return left == right +end + +function echo(retval, err, ...) + local args = {n=select('#', ...), ... } + check(mtn("automate", "lua", "--rcfile=test.lua", "echo", unpack(args, 1, args.n)), retval, true, true) + if (retval == 0) then + -- actually we're doing something here which the std_hook does itself + -- as well - might not be a good test...? + for i=1,args.n do + args[i] = assert(loadstring("return " .. args[i]))() + end + args.n = nil + canonicalize("stdout") + local out = readfile("stdout") + local echoed = assert(loadstring("return { "..out.."}"))(); + check(equalRecursive(args, echoed)) + else + canonicalize("stderr") + check(grep(err, "stderr"), 0, false, false) + end +end + +-- testing simple types +echo(0, nil, 'nil') +echo(0, nil, '3') +echo(0, nil, '3.4') +echo(0, nil, 'true') +echo(0, nil, 'false') +-- different quoting style +echo(0, nil, "'foo'") +echo(0, nil, '"foo"') + +-- testing tables and nesting +echo(0, nil, "{1,2,3}") +echo(0, nil, "{true,false,{1,2,{'foo','bar'}}}") +echo(0, nil, "{[true] = false; ['foo'] = 'bar'; 3 }") + +-- multiple arguments +echo(0, nil, 'nil', '3', '3.4', "'foo'", '"foo"', 'true', 'false', "{1,2,3}") + +-- this is an unknown variable name which gets evaluated to nil +echo(1, "was evaluated to nil", "unknown") + +-- this is an invalid argument (missing bracket) +echo(1, "could not be evaluated", "{1,2,3") + +-- and finally, call a function which does not exist at all +check(mtn("automate", "lua", "foo"), 1, false, true) +canonicalize("stderr") +check(grep("lua function 'foo' does not exist", "stderr"), 0, false, false) + ============================================================ --- tests/automate_lua/test.lua 9ac45e93bf665fa4ea5a90828aa6e834c1aee7a8 +++ tests/automate_lua/test.lua 9ac45e93bf665fa4ea5a90828aa6e834c1aee7a8 @@ -0,0 +1,5 @@ +-- this very simply lua function just returns all input parameters it +-- receives in the right order and without any modification +function echo(...) + return ... +end ============================================================ --- NEWS af85eefcea2e435fcdd6e04a48a4922f877e66cf +++ NEWS 94f333190db9922d097bcf481a0397914239cce4 @@ -34,6 +34,11 @@ - New command group 'mtn conflicts *'; provides asynchronous conflict resolutions for merge and propagate. + - New 'automate lua' command with which lua functions, like + monotone hooks, can be called over automate. This is particularily + useful to get user defaults, like ignorable files, branch keys and + passwords, which are managed through one or more monotonerc files. + - 'merge' and 'propagate' accept user commit messages; the 'merge rev rev' or 'propagate branch branch' message will be prefixed to the user message. --no-prefix removes the prefix. ============================================================ --- automate.cc 598a04952daffa804d60dde204967b20e17fc37a +++ automate.cc 240c35d1563672b59f932812acc0b6bb4d4775dc @@ -2276,6 +2276,51 @@ CMD_AUTOMATE(get_workspace_root, "", output << get_current_working_dir() << '\n'; } +// Name: lua +// Arguments: +// A lua function name +// Zero or more function arguments +// Changes: +// 9.0 (added) +// Purpose: +// Execute lua functions and return their results. +// Output format: +// Lua parsable output. +// Error conditions: +// a runtime exception is thrown if the function does not exists, the arguments cannot be parsed +// or the function cannot be executed for some other reason. + +CMD_AUTOMATE(lua, "LUA_FUNCTION [ARG1 [ARG2 [...]]]", + N_("Executes the given lua function and returns the result"), + "", + options::opts::none) +{ + N(args.size() >= 1, + F("wrong argument count")); + + std::string func = idx(args, 0)(); + + N(app.lua.hook_exists(func), + F("lua function '%s' does not exist") % func); + + std::vector func_args; + if (args.size() > 1) + { + for (unsigned int i=1; i const & args, + std::string & out) +{ + Lua ll(st); + ll.func("hook_wrapper") + .push_str(func_name); + + for (std::vector::const_iterator i = args.begin(); + i != args.end(); ++i) + { + ll.push_str(*i); + } + + ll.call(args.size() + 1, 1); + ll.extract_str_nolog(out); + return ll.ok(); +} + bool lua_hooks::hook_use_inodeprints() { ============================================================ --- lua_hooks.hh eb7235af2f81fcce2921725bfd150df2ba3bfc65 +++ lua_hooks.hh a5ac6a6139a2672c9e8d16bb97162ac849179dda @@ -138,6 +138,11 @@ public: bool & validated, std::string & reason); + // meta hooks + bool hook_hook_wrapper(std::string const & func_name, + std::vector const & args, + std::string & out); + // notification hooks bool hook_note_commit(revision_id const & new_id, revision_data const & rdat, ============================================================ --- monotone.texi 9b7b4b168714e8b980e72d95eba542ecc7234fcd +++ monotone.texi 794aeeeb1450859129acc05515a60a2dd32c793f @@ -8045,7 +8045,7 @@ @section Automation @item Updated in: -8.1 +9.0 Added @option{--with-header} option. @@ -9139,7 +9139,7 @@ @section Automation @item Added in: -8.1 +9.0 @item Purpose: @@ -9156,6 +9156,79 @@ @section Automation @end table address@hidden mtn automate lua @var{function_name} address@hidden + address@hidden @strong address@hidden Arguments: + +A valid Lua function name and zero or more function arguments. Note that string +arguments need to be wrapped in another pair of quotes, i.e. @code{"foo"} or address@hidden'foo'} will not work, but @code{"'foo'"} or @code{'"foo"'} will. + +Complex types are also supported, anything which can be evaluated as valid +Lua expression can be given as input, including nested tables and functions, +like f.e. @address@hidden,true,@{['func'] = function(...) return ... end @address@hidden + address@hidden Added in: + +9.0 + address@hidden Purpose: + +Call Lua functions, like monotone hooks, in the monotone context, f.e. to +retrieve user defaults like keys, passwords, ignorable files and more. + address@hidden Output format: + +A string dump of the return value of the function, in Lua code. The Lua types address@hidden, @code{thread}, @code{userdata} and @code{lightuserdata} are not +serialized, but set to @code{nil} in the dump. + +Please note that @code{nil} values in tables are not printed since Lua does not +distinguish between unset and not existing entries in a table like other +programming languages do. + address@hidden Sample output: + +A single string return value: + address@hidden +[1] = "Output"; address@hidden verbatim + +Two numeric return values: + address@hidden +[1] = 3; +[2] = 4.4; address@hidden verbatim + +A nested table: + address@hidden +[1] = { + ["bar"] = { + [1] = 1; + [2] = 2; + [3] = 3; + }; +}; address@hidden verbatim + +A callback function: + address@hidden +[1] = nil --[[function]]; address@hidden verbatim + address@hidden Error conditions: + +This command prints an error message and exists with status 1 if the function +does not exist, one or more function arguments could not be evaluated or the +function could not be called for another reason. + address@hidden table + @end ftable @page @@ -10186,6 +10259,21 @@ @subsection Validation Hooks @end ftable address@hidden Meta Hooks + +Monotone allows the execution of arbitrary Lua hooks and functions through a +special generalized "meta hook". See @command{automate lua} for more information. + address@hidden @code address@hidden hook_wrapper (@var{func_name}, @var{...}) + +This hook is explicitely called on every execution of @command{automate lua}. +It takes a function name and zero or more string function arguments which are +internally evaluated into Lua code. It returns a dump of the return value of +the called function in Lua code on success. + address@hidden ftable + @page @node Additional Lua Functions, , Hooks, Hook Reference @section Additional Lua Functions ============================================================ --- std_hooks.lua c4f02c95a5717f64d0a60251081ff5bd1a8038de +++ std_hooks.lua 6fa95c0269b7e1b615bf5d2dad24e32948e48350 @@ -1157,7 +1157,54 @@ end return default_args end +hook_wrapper_dump = {} +hook_wrapper_dump.depth = 0 +hook_wrapper_dump._string = function(s) return string.format("%q", s) end +hook_wrapper_dump._number = function(n) return tostring(n) end +hook_wrapper_dump._boolean = function(b) if (b) then return "true" end return "false" end +hook_wrapper_dump._userdata = function(u) return "nil --[[userdata]]" end +-- if we really need to return / serialize functions we could do it +-- like address@hidden did here: http://lua-users.org/wiki/TablePersistence +hook_wrapper_dump._function = function(f) return "nil --[[function]]" end +hook_wrapper_dump._nil = function(n) return "nil" end +hook_wrapper_dump._thread = function(t) return "nil --[[thread]]" end +hook_wrapper_dump._lightuserdata = function(l) return "nil --[[lightuserdata]]" end +hook_wrapper_dump._table = function(t) + local buf = '' + if (hook_wrapper_dump.depth > 0) then + buf = buf .. '{\n' + end + hook_wrapper_dump.depth = hook_wrapper_dump.depth + 1; + for k,v in pairs(t) do + buf = buf..string.format('%s[%s] = %s;\n', + string.rep("\t", hook_wrapper_dump.depth - 1), + hook_wrapper_dump["_" .. type(k)](k), + hook_wrapper_dump["_" .. type(v)](v)) + end + hook_wrapper_dump.depth = hook_wrapper_dump.depth - 1; + if (hook_wrapper_dump.depth > 0) then + buf = buf .. string.rep("\t", hook_wrapper_dump.depth - 1) .. '}' + end + return buf +end + +function hook_wrapper(func_name, ...) + -- we have to ensure that nil arguments are restored properly for the + -- function call, see http://lua-users.org/wiki/StoringNilsInTables + local args = { n=select('#', ...), ... } + for i=1,args.n do + local val = assert(loadstring("return " .. args[i]), + "argument "..args[i].." could not be evaluated")() + assert(val ~= nil or args[i] == "nil", + "argument "..args[i].." was evaluated to nil") + args[i] = val + end + local res = { _G[func_name](unpack(args, 1, args.n)) } + return hook_wrapper_dump._table(res) +end + + function get_remote_unix_socket_command(host) return "socat" end