Expose builtin commands to dfhack-run, and add lua script support.

Move builtin command implementation to Core methods, and fall
back to hack/scripts/*.lua for otherwise unrecognized commands.
develop
Alexander Gavrilov 2012-05-04 19:47:18 +04:00
parent 5afe2ca002
commit d4d6349f48
12 changed files with 260 additions and 94 deletions

@ -300,6 +300,9 @@ install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION} DESTINATION ${DFHACK_LUA_DESTINATION}
FILES_MATCHING PATTERN "*.lua") FILES_MATCHING PATTERN "*.lua")
install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
DESTINATION ${DFHACK_DATA_DESTINATION})
# Unused for so long that it's not even relevant now... # Unused for so long that it's not even relevant now...
if(BUILD_DEVEL) if(BUILD_DEVEL)
if(WIN32) if(WIN32)

@ -51,6 +51,8 @@ using namespace std;
#include "RemoteServer.h" #include "RemoteServer.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "MiscUtils.h"
using namespace DFHack; using namespace DFHack;
#include "df/ui.h" #include "df/ui.h"
@ -72,8 +74,6 @@ using df::global::init;
// FIXME: A lot of code in one file, all doing different things... there's something fishy about it. // FIXME: A lot of code in one file, all doing different things... there's something fishy about it.
static void loadScriptFile(Core *core, PluginManager *plug_mgr, string fname, bool silent);
static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command);
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod); static bool parseKeySpec(std::string keyspec, int *psym, int *pmod);
struct Core::Cond struct Core::Cond
@ -178,21 +178,10 @@ void fHKthread(void * iodata)
{ {
color_ostream_proxy out(core->getConsole()); color_ostream_proxy out(core->getConsole());
vector <string> args; auto rv = core->runCommand(out, stuff);
Core::cheap_tokenise(stuff, args);
if (args.empty()) {
out.printerr("Empty hotkey command.\n");
continue;
}
string first = args[0]; if (rv == CR_NOT_IMPLEMENTED)
args.erase(args.begin()); out.printerr("Invalid hotkey command: '%s'\n", stuff.c_str());
command_result cr = plug_mgr->InvokeCommand(out, first, args);
if(cr == CR_NEEDS_CONSOLE)
{
out.printerr("It isn't possible to run an interactive command outside the console.\n");
}
} }
} }
} }
@ -212,38 +201,122 @@ struct sortable
}; };
}; };
static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command) static std::string getLuaHelp(std::string path)
{ {
Console & con = core->getConsole(); ifstream script(path);
if (script.good())
{
std::string help;
if (getline(script, help) &&
help.substr(0,3) == "-- ")
return help.substr(3);
}
return "Lua script.";
}
static std::map<string,string> listLuaScripts(std::string path)
{
std::vector<string> files;
getdir(path, files);
std::map<string,string> pset;
for (size_t i = 0; i < files.size(); i++)
{
if (hasEnding(files[i], ".lua"))
{
std::string help = getLuaHelp(path + files[i]);
pset[files[i].substr(0, files[i].size()-4)] = help;
}
}
return pset;
}
static bool fileExists(std::string path)
{
ifstream script(path);
return script.good();
}
namespace {
struct ScriptArgs {
const string *pcmd;
vector<string> *pargs;
};
}
static bool init_run_script(color_ostream &out, lua_State *state, void *info)
{
auto args = (ScriptArgs*)info;
if (!lua_checkstack(state, args->pargs->size()+10))
return false;
Lua::PushDFHack(state);
lua_getfield(state, -1, "run_script");
lua_remove(state, -2);
lua_pushstring(state, args->pcmd->c_str());
for (size_t i = 0; i < args->pargs->size(); i++)
lua_pushstring(state, (*args->pargs)[i].c_str());
return true;
}
static command_result runLuaScript(color_ostream &out, std::string filename, vector<string> &args)
{
ScriptArgs data;
data.pcmd = &filename;
data.pargs = &args;
#ifndef LINUX_BUILD
filename = toLower(filename);
#endif
bool ok = Lua::RunCoreQueryLoop(out, Lua::Core::State, init_run_script, &data);
return ok ? CR_OK : CR_FAILURE;
}
command_result Core::runCommand(color_ostream &out, const std::string &command)
{
if (!command.empty()) if (!command.empty())
{ {
// cut the input into parts
vector <string> parts; vector <string> parts;
Core::cheap_tokenise(command,parts); Core::cheap_tokenise(command,parts);
if(parts.size() == 0) if(parts.size() == 0)
{ return CR_NOT_IMPLEMENTED;
clueless_counter ++;
return;
}
string first = parts[0]; string first = parts[0];
parts.erase(parts.begin()); parts.erase(parts.begin());
if (first[0] == '#') return; if (first[0] == '#')
return CR_OK;
cerr << "Invoking: " << command << endl; cerr << "Invoking: " << command << endl;
return runCommand(out, first, parts);
}
else
return CR_NOT_IMPLEMENTED;
}
command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts)
{
if (!first.empty())
{
// let's see what we actually got // let's see what we actually got
if(first=="help" || first == "?" || first == "man") if(first=="help" || first == "?" || first == "man")
{ {
if(!parts.size()) if(!parts.size())
{
if (con.is_console())
{ {
con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n"
"Some basic editing capabilities are included (single-line text editing).\n" "Some basic editing capabilities are included (single-line text editing).\n"
"The console also has a command history - you can navigate it with Up and Down keys.\n" "The console also has a command history - you can navigate it with Up and Down keys.\n"
"On Windows, you may have to resize your console window. The appropriate menu is accessible\n" "On Windows, you may have to resize your console window. The appropriate menu is accessible\n"
"by clicking on the program icon in the top bar of the window.\n\n" "by clicking on the program icon in the top bar of the window.\n\n");
"Basic commands:\n" }
con.print("Basic commands:\n"
" help|?|man - This text.\n" " help|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\n" " help COMMAND - Usage help for the given command.\n"
" ls|dir [PLUGIN] - List available commands. Optionally for single plugin.\n" " ls|dir [PLUGIN] - List available commands. Optionally for single plugin.\n"
@ -274,9 +347,15 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
con.reset_color(); con.reset_color();
if (!pcmd.usage.empty()) if (!pcmd.usage.empty())
con << "Usage:\n" << pcmd.usage << flush; con << "Usage:\n" << pcmd.usage << flush;
return; return CR_OK;
} }
} }
auto filename = getHackPath() + "scripts/" + parts[0] + ".lua";
if (fileExists(filename))
{
string help = getLuaHelp(filename);
con.print("%s: %s\n", parts[0].c_str(), help.c_str());
}
con.printerr("Unknown command: %s\n", parts[0].c_str()); con.printerr("Unknown command: %s\n", parts[0].c_str());
} }
else else
@ -421,6 +500,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str()); con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str());
con.reset_color(); con.reset_color();
} }
auto scripts = listLuaScripts(getHackPath() + "scripts/");
if (!scripts.empty())
{
con.print("\nscripts:\n");
for (auto iter = scripts.begin(); iter != scripts.end(); ++iter)
con.print(" %-22s- %s\n", iter->first.c_str(), iter->second.c_str());
}
} }
} }
else if(first == "plug") else if(first == "plug")
@ -439,10 +525,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{ {
std::string keystr = parts[1]; std::string keystr = parts[1];
if (parts[0] == "set") if (parts[0] == "set")
core->ClearKeyBindings(keystr); ClearKeyBindings(keystr);
for (int i = parts.size()-1; i >= 2; i--) for (int i = parts.size()-1; i >= 2; i--)
{ {
if (!core->AddKeyBinding(keystr, parts[i])) { if (!AddKeyBinding(keystr, parts[i])) {
con.printerr("Invalid key spec: %s\n", keystr.c_str()); con.printerr("Invalid key spec: %s\n", keystr.c_str());
break; break;
} }
@ -452,7 +538,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{ {
for (size_t i = 1; i < parts.size(); i++) for (size_t i = 1; i < parts.size(); i++)
{ {
if (!core->ClearKeyBindings(parts[i])) { if (!ClearKeyBindings(parts[i])) {
con.printerr("Invalid key spec: %s\n", parts[i].c_str()); con.printerr("Invalid key spec: %s\n", parts[i].c_str());
break; break;
} }
@ -460,7 +546,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
} }
else if (parts.size() == 2 && parts[0] == "list") else if (parts.size() == 2 && parts[0] == "list")
{ {
std::vector<std::string> list = core->ListKeyBindings(parts[1]); std::vector<std::string> list = ListKeyBindings(parts[1]);
if (list.empty()) if (list.empty())
con << "No bindings." << endl; con << "No bindings." << endl;
for (size_t i = 0; i < list.size(); i++) for (size_t i = 0; i < list.size(); i++)
@ -478,13 +564,19 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
} }
else if(first == "fpause") else if(first == "fpause")
{ {
World * w = core->getWorld(); World * w = getWorld();
w->SetPauseState(true); w->SetPauseState(true);
con.print("The game was forced to pause!"); con.print("The game was forced to pause!\n");
} }
else if(first == "cls") else if(first == "cls")
{ {
con.clear(); if (con.is_console())
((Console&)con).clear();
else
{
con.printerr("No console to clear.\n");
return CR_NEEDS_CONSOLE;
}
} }
else if(first == "die") else if(first == "die")
{ {
@ -494,12 +586,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{ {
if(parts.size() == 1) if(parts.size() == 1)
{ {
loadScriptFile(core, plug_mgr, parts[0], false); loadScriptFile(con, parts[0], false);
} }
else else
{ {
con << "Usage:" << endl con << "Usage:" << endl
<< " script <filename>" << endl; << " script <filename>" << endl;
return CR_WRONG_USAGE;
} }
} }
else else
@ -507,35 +600,44 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
command_result res = plug_mgr->InvokeCommand(con, first, parts); command_result res = plug_mgr->InvokeCommand(con, first, parts);
if(res == CR_NOT_IMPLEMENTED) if(res == CR_NOT_IMPLEMENTED)
{ {
auto filename = getHackPath() + "scripts/" + first + ".lua";
if (fileExists(filename))
res = runLuaScript(con, filename, parts);
else
con.printerr("%s is not a recognized command.\n", first.c_str()); con.printerr("%s is not a recognized command.\n", first.c_str());
clueless_counter ++;
} }
else if (res == CR_NEEDS_CONSOLE)
con.printerr("%s needs interactive console to work.\n", first.c_str());
return res;
} }
return CR_OK;
} }
return CR_NOT_IMPLEMENTED;
} }
static void loadScriptFile(Core *core, PluginManager *plug_mgr, string fname, bool silent) bool Core::loadScriptFile(color_ostream &out, string fname, bool silent)
{ {
if(!silent) if(!silent)
core->getConsole() << "Loading script at " << fname << std::endl; out << "Loading script at " << fname << std::endl;
ifstream script(fname); ifstream script(fname);
if (script.good()) if (script.good())
{ {
int tmp = 0;
string command; string command;
while (getline(script, command)) while (getline(script, command))
{ {
if (!command.empty()) if (!command.empty())
runInteractiveCommand(core, plug_mgr, tmp, command); runCommand(out, command);
} }
return true;
} }
else else
{ {
if(!silent) if(!silent)
core->getConsole().printerr("Error loading script\n"); out.printerr("Error loading script\n");
return false;
} }
script.close();
} }
// A thread function... for the interactive console. // A thread function... for the interactive console.
@ -555,7 +657,7 @@ void fIOthread(void * iodata)
return; return;
} }
loadScriptFile(core, plug_mgr, "dfhack.init", true); core->loadScriptFile(con, "dfhack.init", true);
con.print("DFHack is ready. Have a nice day!\n" con.print("DFHack is ready. Have a nice day!\n"
"Type in '?' or 'help' for general help, 'ls' to see all commands.\n"); "Type in '?' or 'help' for general help, 'ls' to see all commands.\n");
@ -582,7 +684,10 @@ void fIOthread(void * iodata)
main_history.save("dfhack.history"); main_history.save("dfhack.history");
} }
runInteractiveCommand(core, plug_mgr, clueless_counter, command); auto rv = core->runCommand(con, command);
if (rv == CR_NOT_IMPLEMENTED)
clueless_counter++;
if(clueless_counter == 3) if(clueless_counter == 3)
{ {
@ -636,6 +741,15 @@ void Core::fatal (std::string output, bool deactivate)
#endif #endif
} }
std::string Core::getHackPath()
{
#ifdef LINUX_BUILD
return p->getPath() + "/hack/";
#else
return p->getPath() + "\\hack\\";
#endif
}
bool Core::Init() bool Core::Init()
{ {
if(started) if(started)

@ -706,6 +706,16 @@ static int DFHACK_DFHACK_TOKEN = 0;
static int DFHACK_BASE_G_TOKEN = 0; static int DFHACK_BASE_G_TOKEN = 0;
static int DFHACK_REQUIRE_TOKEN = 0; static int DFHACK_REQUIRE_TOKEN = 0;
void Lua::PushDFHack(lua_State *state)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
}
void Lua::PushBaseGlobals(lua_State *state)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
}
bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module) bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module)
{ {
AssertCoreSuspend(state); AssertCoreSuspend(state);
@ -884,13 +894,9 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
bool (*init)(color_ostream&, lua_State*, void*), bool (*init)(color_ostream&, lua_State*, void*),
void *arg) void *arg)
{ {
if (!out.is_console())
return false;
if (!lua_checkstack(state, 20)) if (!lua_checkstack(state, 20))
return false; return false;
Console &con = static_cast<Console&>(out);
lua_State *thread; lua_State *thread;
int rv; int rv;
std::string prompt; std::string prompt;
@ -910,6 +916,10 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
return false; return false;
} }
// If not interactive, run without coroutine and bail out
if (!out.is_console())
return SafeCall(out, state, lua_gettop(state)-base-1, 0);
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
lua_pushvalue(state, base+1); lua_pushvalue(state, base+1);
lua_remove(state, base+1); lua_remove(state, base+1);
@ -920,6 +930,8 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile); rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile);
} }
Console &con = static_cast<Console&>(out);
while (rv == LUA_YIELD) while (rv == LUA_YIELD)
{ {
if (histfile != histname) if (histfile != histname)

@ -30,9 +30,12 @@ distribution.
#ifndef LINUX_BUILD #ifndef LINUX_BUILD
#include <Windows.h> #include <Windows.h>
#include "wdirent.h"
#else #else
#include <sys/time.h> #include <sys/time.h>
#include <ctime> #include <ctime>
#include <dirent.h>
#include <errno.h>
#endif #endif
#include <ctype.h> #include <ctype.h>
@ -125,6 +128,34 @@ std::string toLower(const std::string &str)
return rv; return rv;
} }
int getdir(std::string dir, std::vector<std::string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(std::string(dirp->d_name));
}
closedir(dp);
return 0;
}
bool hasEnding (std::string const &fullString, std::string const &ending)
{
if (fullString.length() > ending.length())
{
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
}
else
{
return false;
}
}
df::general_ref *DFHack::findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type) df::general_ref *DFHack::findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{ {
for (int i = vec.size()-1; i >= 0; i--) for (int i = vec.size()-1; i >= 0; i--)

@ -45,41 +45,8 @@ using namespace std;
#include "tinythread.h" #include "tinythread.h"
using namespace tthread; using namespace tthread;
#ifdef LINUX_BUILD
#include <dirent.h>
#include <errno.h>
#else
#include "wdirent.h"
#endif
#include <assert.h> #include <assert.h>
static int getdir (string dir, vector<string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(string(dirp->d_name));
}
closedir(dp);
return 0;
}
bool hasEnding (std::string const &fullString, std::string const &ending)
{
if (fullString.length() > ending.length())
{
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
}
else
{
return false;
}
}
struct Plugin::RefLock struct Plugin::RefLock
{ {
RefLock() RefLock()
@ -563,10 +530,10 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core)
{ {
#ifdef LINUX_BUILD #ifdef LINUX_BUILD
string path = core->p->getPath() + "/hack/plugins/"; string path = core->getHackPath() + "plugins/";
const string searchstr = ".plug.so"; const string searchstr = ".plug.so";
#else #else
string path = core->p->getPath() + "\\hack\\plugins\\"; string path = core->getHackPath() + "plugins\\";
const string searchstr = ".plug.dll"; const string searchstr = ".plug.dll";
#endif #endif
cmdlist_mutex = new mutex(); cmdlist_mutex = new mutex();

@ -711,7 +711,7 @@ command_result CoreService::RunCommand(color_ostream &stream,
for (int i = 0; i < in->arguments_size(); i++) for (int i = 0; i < in->arguments_size(); i++)
args.push_back(in->arguments(i)); args.push_back(in->arguments(i));
return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args); return Core::getInstance().runCommand(stream, cmd, args);
} }
command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt) command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt)

@ -35,6 +35,8 @@ distribution.
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include "SDL_events.h" #include "SDL_events.h"
#include "RemoteClient.h"
struct WINDOW; struct WINDOW;
namespace tthread namespace tthread
@ -117,10 +119,16 @@ namespace DFHack
/// returns a named pointer. /// returns a named pointer.
void *GetData(std::string key); void *GetData(std::string key);
command_result runCommand(color_ostream &out, const std::string &command, std::vector <std::string> &parameters);
command_result runCommand(color_ostream &out, const std::string &command);
bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false);
bool ClearKeyBindings(std::string keyspec); bool ClearKeyBindings(std::string keyspec);
bool AddKeyBinding(std::string keyspec, std::string cmdline); bool AddKeyBinding(std::string keyspec, std::string cmdline);
std::vector<std::string> ListKeyBindings(std::string keyspec); std::vector<std::string> ListKeyBindings(std::string keyspec);
std::string getHackPath();
bool isWorldLoaded() { return (last_world_data_ptr != NULL); } bool isWorldLoaded() { return (last_world_data_ptr != NULL); }
bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); } bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); }

@ -48,6 +48,9 @@ namespace DFHack {namespace Lua {
*/ */
DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL); DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL);
DFHACK_EXPORT void PushDFHack(lua_State *state);
DFHACK_EXPORT void PushBaseGlobals(lua_State *state);
/** /**
* Load a module using require(). Leaves the stack as is. * Load a module using require(). Leaves the stack as is.
*/ */

@ -274,6 +274,9 @@ DFHACK_EXPORT std::string join_strings(const std::string &separator, const std::
DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toUpper(const std::string &str);
DFHACK_EXPORT std::string toLower(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str);
DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files);
DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending);
inline bool bits_match(unsigned required, unsigned ok, unsigned mask) inline bool bits_match(unsigned required, unsigned ok, unsigned mask)
{ {
return (required & mask) == (required & mask & ok); return (required & mask) == (required & mask & ok);

@ -269,5 +269,23 @@ function dfhack.interpreter(prompt,hfile,env)
return true return true
end end
-- Command scripts
dfhack.scripts = dfhack.scripts or {}
function dfhack.run_script(file,...)
local env = dfhack.scripts[file]
if env == nil then
env = {}
setmetatable(env, { __index = base_env })
dfhack.scripts[file] = env
end
local f,perr = loadfile(file, 't', env)
if f == nil then
error(perr)
end
return f(...)
end
-- Feed the table back to the require() mechanism. -- Feed the table back to the require() mechanism.
return dfhack return dfhack

@ -1,6 +1,7 @@
#include "lua_Console.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "lua_Console.h"
#include <sstream> #include <sstream>
//TODO error management. Using lua error? or something other? //TODO error management. Using lua error? or something other?

@ -0,0 +1,6 @@
-- Example of a lua script.
run_count = (run_count or 0) + 1
print('Arguments: ',...)
print('Command called '..run_count..' times.')