diff --git a/NEWS b/NEWS index 959972c6f..04ea1a1a4 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ DFHack future - support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua). - Lua API for listing files in directory. Needed for mod-manager. - Lua API for creating unit combat reports and writing to gamelog. + - Lua API for running arbitrary DFHack commands - support for multiple raw/init.d/*.lua init scripts in one save. - eventful now has a more friendly way of making custom sidebars - new plugin: building-hacks. Allows to add custom functionality and/or animations to buildings. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 3a374445f..529f51edb 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2227,6 +2227,64 @@ static int internal_getDir(lua_State *L) } return 1; } + +static int internal_runCommand(lua_State *L) +{ + buffered_color_ostream out; + command_result res; + if (lua_gettop(L) == 0) + { + lua_pushstring(L, ""); + } + int type_1 = lua_type(L, 1); + if (type_1 == LUA_TTABLE) + { + std::string command = ""; + std::vector args; + lua_pushnil(L); // first key + while (lua_next(L, 1) != 0) + { + if (command == "") + command = lua_tostring(L, -1); + else + args.push_back(lua_tostring(L, -1)); + lua_pop(L, 1); // remove value, leave key + } + CoreSuspender suspend; + res = Core::getInstance().runCommand(out, command, args); + } + else if (type_1 == LUA_TSTRING) + { + std::string command = lua_tostring(L, 1); + CoreSuspender suspend; + res = Core::getInstance().runCommand(out, command); + } + else + { + lua_pushnil(L); + lua_pushfstring(L, "Expected table, got %s", lua_typename(L, type_1)); + return 2; + } + auto fragments = out.fragments(); + lua_newtable(L); + lua_pushinteger(L, (int)res); + lua_setfield(L, -2, "status"); + int i = 1; + for (auto iter = fragments.begin(); iter != fragments.end(); iter++, i++) + { + int color = iter->first; + std::string output = iter->second; + lua_createtable(L, 2, 0); + lua_pushinteger(L, color); + lua_rawseti(L, -2, 1); + lua_pushstring(L, output.c_str()); + lua_rawseti(L, -2, 2); + lua_rawseti(L, -2, i); + } + lua_pushvalue(L, -1); + return 1; +} + static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, @@ -2240,6 +2298,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "memscan", internal_memscan }, { "diffscan", internal_diffscan }, { "getDir", internal_getDir }, + { "runCommand", internal_runCommand }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 77c62311a..3cc98e882 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -10,6 +10,14 @@ local dfhack = dfhack local base_env = dfhack.BASE_G local _ENV = base_env +CR_LINK_FAILURE = -3 +CR_NEEDS_CONSOLE = -2 +CR_NOT_IMPLEMENTED = -1 +CR_OK = 0 +CR_FAILURE = 1 +CR_WRONG_USAGE = 2 +CR_NOT_FOUND = 3 + -- Console color constants COLOR_RESET = -1 @@ -256,7 +264,8 @@ function dfhack.interpreter(prompt,hfile,env) print("Shortcuts:\n".. " '= foo' => '_1,_2,... = foo'\n".. " '! foo' => 'print(foo)'\n".. - "Both save the first result as '_'.") + " '~ foo' => 'printall(foo)'\n".. + "All of these save the first result as '_'.") print_banner = false end @@ -357,6 +366,31 @@ function dfhack.run_script(name,...) return f(...) end +function dfhack.run_command(...) + args = {...} + if type(args[1]) == 'table' then + command = args[1] + elseif #args > 1 and type(args[2]) == 'table' then + -- {args[1]} + args[2] + command = args[2] + table.insert(command, 1, args[1]) + elseif #args == 1 and type(args[1]) == 'string' then + command = args[1] + elseif #args > 1 and type(args[1]) == 'string' then + command = args + else + error('Invalid arguments') + end + result = internal.runCommand(command) + output = "" + for i, f in pairs(result) do + if type(f) == 'table' then + output = output .. f[2] + end + end + return output, result.status +end + -- Per-save init file function dfhack.getSavePath()