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}
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...
if(BUILD_DEVEL)
if(WIN32)

@ -51,6 +51,8 @@ using namespace std;
#include "RemoteServer.h"
#include "LuaTools.h"
#include "MiscUtils.h"
using namespace DFHack;
#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.
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);
struct Core::Cond
@ -178,21 +178,10 @@ void fHKthread(void * iodata)
{
color_ostream_proxy out(core->getConsole());
vector <string> args;
Core::cheap_tokenise(stuff, args);
if (args.empty()) {
out.printerr("Empty hotkey command.\n");
continue;
}
auto rv = core->runCommand(out, stuff);
string first = args[0];
args.erase(args.begin());
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");
}
if (rv == CR_NOT_IMPLEMENTED)
out.printerr("Invalid hotkey command: '%s'\n", stuff.c_str());
}
}
}
@ -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)
{
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)
{
Console & con = core->getConsole();
if (!command.empty())
{
// cut the input into parts
vector <string> parts;
Core::cheap_tokenise(command,parts);
if(parts.size() == 0)
{
clueless_counter ++;
return;
}
return CR_NOT_IMPLEMENTED;
string first = parts[0];
parts.erase(parts.begin());
if (first[0] == '#') return;
if (first[0] == '#')
return CR_OK;
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
if(first=="help" || first == "?" || first == "man")
{
if(!parts.size())
{
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"
"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"
"by clicking on the program icon in the top bar of the window.\n\n"
"Basic commands:\n"
if (con.is_console())
{
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"
"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"
"by clicking on the program icon in the top bar of the window.\n\n");
}
con.print("Basic commands:\n"
" help|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\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();
if (!pcmd.usage.empty())
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());
}
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.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")
@ -439,10 +525,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
std::string keystr = parts[1];
if (parts[0] == "set")
core->ClearKeyBindings(keystr);
ClearKeyBindings(keystr);
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());
break;
}
@ -452,7 +538,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
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());
break;
}
@ -460,7 +546,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
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())
con << "No bindings." << endl;
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")
{
World * w = core->getWorld();
World * w = getWorld();
w->SetPauseState(true);
con.print("The game was forced to pause!");
con.print("The game was forced to pause!\n");
}
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")
{
@ -494,12 +586,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{
if(parts.size() == 1)
{
loadScriptFile(core, plug_mgr, parts[0], false);
loadScriptFile(con, parts[0], false);
}
else
{
con << "Usage:" << endl
<< " script <filename>" << endl;
return CR_WRONG_USAGE;
}
}
else
@ -507,35 +600,44 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
command_result res = plug_mgr->InvokeCommand(con, first, parts);
if(res == CR_NOT_IMPLEMENTED)
{
con.printerr("%s is not a recognized command.\n", first.c_str());
clueless_counter ++;
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());
}
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)
core->getConsole() << "Loading script at " << fname << std::endl;
out << "Loading script at " << fname << std::endl;
ifstream script(fname);
if (script.good())
{
int tmp = 0;
string command;
while (getline(script, command))
{
if (!command.empty())
runInteractiveCommand(core, plug_mgr, tmp, command);
runCommand(out, command);
}
return true;
}
else
{
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.
@ -555,7 +657,7 @@ void fIOthread(void * iodata)
return;
}
loadScriptFile(core, plug_mgr, "dfhack.init", true);
core->loadScriptFile(con, "dfhack.init", true);
con.print("DFHack is ready. Have a nice day!\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");
}
runInteractiveCommand(core, plug_mgr, clueless_counter, command);
auto rv = core->runCommand(con, command);
if (rv == CR_NOT_IMPLEMENTED)
clueless_counter++;
if(clueless_counter == 3)
{
@ -636,6 +741,15 @@ void Core::fatal (std::string output, bool deactivate)
#endif
}
std::string Core::getHackPath()
{
#ifdef LINUX_BUILD
return p->getPath() + "/hack/";
#else
return p->getPath() + "\\hack\\";
#endif
}
bool Core::Init()
{
if(started)

@ -706,6 +706,16 @@ static int DFHACK_DFHACK_TOKEN = 0;
static int DFHACK_BASE_G_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)
{
AssertCoreSuspend(state);
@ -884,13 +894,9 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
bool (*init)(color_ostream&, lua_State*, void*),
void *arg)
{
if (!out.is_console())
return false;
if (!lua_checkstack(state, 20))
return false;
Console &con = static_cast<Console&>(out);
lua_State *thread;
int rv;
std::string prompt;
@ -910,6 +916,10 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
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_pushvalue(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);
}
Console &con = static_cast<Console&>(out);
while (rv == LUA_YIELD)
{
if (histfile != histname)

@ -30,9 +30,12 @@ distribution.
#ifndef LINUX_BUILD
#include <Windows.h>
#include "wdirent.h"
#else
#include <sys/time.h>
#include <ctime>
#include <dirent.h>
#include <errno.h>
#endif
#include <ctype.h>
@ -125,6 +128,34 @@ std::string toLower(const std::string &str)
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)
{
for (int i = vec.size()-1; i >= 0; i--)

@ -45,41 +45,8 @@ using namespace std;
#include "tinythread.h"
using namespace tthread;
#ifdef LINUX_BUILD
#include <dirent.h>
#include <errno.h>
#else
#include "wdirent.h"
#endif
#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
{
RefLock()
@ -563,10 +530,10 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
PluginManager::PluginManager(Core * core)
{
#ifdef LINUX_BUILD
string path = core->p->getPath() + "/hack/plugins/";
string path = core->getHackPath() + "plugins/";
const string searchstr = ".plug.so";
#else
string path = core->p->getPath() + "\\hack\\plugins\\";
string path = core->getHackPath() + "plugins\\";
const string searchstr = ".plug.dll";
#endif
cmdlist_mutex = new mutex();

@ -711,7 +711,7 @@ command_result CoreService::RunCommand(color_ostream &stream,
for (int i = 0; i < in->arguments_size(); 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)

@ -35,6 +35,8 @@ distribution.
#include "modules/Graphic.h"
#include "SDL_events.h"
#include "RemoteClient.h"
struct WINDOW;
namespace tthread
@ -117,10 +119,16 @@ namespace DFHack
/// returns a named pointer.
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 AddKeyBinding(std::string keyspec, std::string cmdline);
std::vector<std::string> ListKeyBindings(std::string keyspec);
std::string getHackPath();
bool isWorldLoaded() { return (last_world_data_ptr != NULL); }
bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); }
@ -177,7 +185,7 @@ namespace DFHack
} s_mods;
std::vector <Module *> allModules;
DFHack::PluginManager * plug_mgr;
// hotkey-related stuff
struct KeyBinding {
int modifiers;

@ -48,6 +48,9 @@ namespace DFHack {namespace Lua {
*/
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.
*/

@ -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 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)
{
return (required & mask) == (required & mask & ok);

@ -269,5 +269,23 @@ function dfhack.interpreter(prompt,hfile,env)
return true
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.
return dfhack

@ -1,6 +1,7 @@
#include "lua_Console.h"
#include "LuaTools.h"
#include "lua_Console.h"
#include <sstream>
//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.')