diff --git a/Lua API.rst b/Lua API.rst index 3f92ad4db..f9390c777 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1964,6 +1964,33 @@ and are only documented here for completeness: Wraps strerror() - returns a string describing a platform-specific error code +* ``dfhack.internal.addScriptPath(path, search_before)`` + + Adds ``path`` to the list of paths searched for scripts (both in Lua and Ruby). + If ``search_before`` is passed and ``true``, the path will be searched before + the default paths (e.g. ``raw/scripts``, ``hack/scripts``); otherwise, it will + be searched after. + + Returns ``true`` if successful or ``false`` otherwise (e.g. if the path does + not exist or has already been registered). + +* ``dfhack.internal.removeScriptPath(path)`` + + Removes ``path`` from the script search paths and returns ``true`` if successful. + +* ``dfhack.internal.getScriptPaths()`` + + Returns the list of script paths in the order they are searched, including defaults. + (This can change if a world is loaded.) + +* ``dfhack.internal.findScript(name)`` + + Searches script paths for the script ``name`` and returns the path of the first + file found, or ``nil`` on failure. + + Note: This requires an extension to be specified (``.lua`` or ``.rb``) - + use ``dfhack.findScript()`` to include the ``.lua`` extension automatically. + Core interpreter context ======================== diff --git a/library/Core.cpp b/library/Core.cpp index 854f5b0d8..c5834875e 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -427,24 +427,65 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std:: return false; } -string findScript(string path, string name) { - if (df::global::world) { - //first try the save folder if it exists - string save = World::ReadWorldFolder(); - if ( save != "" ) { - string file = path + "/data/save/" + save + "/raw/scripts/" + name; - if (fileExists(file)) { - return file; - } +bool Core::addScriptPath(string path, bool search_before) +{ + lock_guard lock(*script_path_mutex); + vector &vec = script_paths[search_before ? 0 : 1]; + if (std::find(vec.begin(), vec.end(), path) != vec.end()) + return false; + if (!Filesystem::isdir(path)) + return false; + vec.push_back(path); + return true; +} + +bool Core::removeScriptPath(string path) +{ + lock_guard lock(*script_path_mutex); + bool found = false; + for (int i = 0; i < 2; i++) + { + vector &vec = script_paths[i]; + while (1) + { + auto it = std::find(vec.begin(), vec.end(), path); + if (it == vec.end()) + break; + vec.erase(it); + found = true; } } - string file = path + "/raw/scripts/" + name; - if (fileExists(file)) { - return file; + return found; +} + +void Core::getScriptPaths(std::vector *dest) +{ + lock_guard lock(*script_path_mutex); + dest->clear(); + string df_path = this->p->getPath(); + for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) + dest->push_back(*it); + if (df::global::world) { + string save = World::ReadWorldFolder(); + if (save.size()) + dest->push_back(df_path + "/data/save/" + save + "/raw/scripts"); } - file = path + "/hack/scripts/" + name; - if (fileExists(file)) { - return file; + dest->push_back(df_path + "/raw/scripts"); + dest->push_back(df_path + "/hack/scripts"); + for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it) + dest->push_back(*it); +} + + +string Core::findScript(string name) +{ + vector paths; + getScriptPaths(&paths); + for (auto it = paths.begin(); it != paths.end(); ++it) + { + string path = *it + "/" + name; + if (Filesystem::isfile(path)) + return path; } return ""; } @@ -592,15 +633,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_OK; } } - string path = this->p->getPath(); - string file = findScript(path, parts[0] + ".lua"); + string file = findScript(parts[0] + ".lua"); if ( file != "" ) { string help = getScriptHelp(file, "-- "); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); return CR_OK; } if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) { - file = findScript(path, parts[0] + ".rb"); + file = findScript(parts[0] + ".rb"); if ( file != "" ) { string help = getScriptHelp(file, "# "); con.print("%s: %s\n", parts[0].c_str(), help.c_str()); @@ -682,7 +722,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(!plug) { - std::string lua = findScript(this->p->getPath(), part + ".lua"); + std::string lua = findScript(part + ".lua"); if (lua.size()) { res = enableLuaScript(con, part, enable); @@ -873,11 +913,11 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v { con << " is part of plugin " << plug->getName() << "." << std::endl; } - else if (findScript(this->p->getPath(), parts[0] + ".lua").size()) + else if (findScript(parts[0] + ".lua").size()) { con << " is a Lua script." << std::endl; } - else if (findScript(this->p->getPath(), parts[0] + ".rb").size()) + else if (findScript(parts[0] + ".rb").size()) { con << " is a Ruby script." << std::endl; } @@ -1100,11 +1140,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v if(res == CR_NOT_IMPLEMENTED) { string completed; - string path = this->p->getPath(); - string filename = findScript(path, first + ".lua"); + string filename = findScript(first + ".lua"); bool lua = filename != ""; if ( !lua ) { - filename = findScript(path, first + ".rb"); + filename = findScript(first + ".rb"); } if ( lua ) res = runLuaScript(con, first, parts); @@ -1273,6 +1312,8 @@ Core::Core() server = NULL; color_ostream::log_errors_to_stderr = true; + + script_path_mutex = new mutex(); }; void Core::fatal (std::string output, bool deactivate) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d601b198a..698b4ba5a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2637,6 +2637,47 @@ static int internal_getModifiers(lua_State *L) return 1; } +static int internal_addScriptPath(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + bool search_before = (lua_gettop(L) > 1 && lua_toboolean(L, 2)); + lua_pushboolean(L, Core::getInstance().addScriptPath(path, search_before)); + return 1; +} + +static int internal_removeScriptPath(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + lua_pushboolean(L, Core::getInstance().removeScriptPath(path)); + return 1; +} + +static int internal_getScriptPaths(lua_State *L) +{ + int i = 1; + lua_newtable(L); + std::vector paths; + Core::getInstance().getScriptPaths(&paths); + for (auto it = paths.begin(); it != paths.end(); ++it) + { + lua_pushinteger(L, i++); + lua_pushstring(L, it->c_str()); + lua_settable(L, -3); + } + return 1; +} + +static int internal_findScript(lua_State *L) +{ + const char *name = luaL_checkstring(L, 1); + std::string path = Core::getInstance().findScript(name); + if (path.size()) + lua_pushstring(L, path.c_str()); + else + lua_pushnil(L); + return 1; +} + static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, @@ -2652,6 +2693,10 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getDir", filesystem_listdir }, { "runCommand", internal_runCommand }, { "getModifiers", internal_getModifiers }, + { "addScriptPath", internal_addScriptPath }, + { "removeScriptPath", internal_removeScriptPath }, + { "getScriptPaths", internal_getScriptPaths }, + { "findScript", internal_findScript }, { NULL, NULL } }; diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 6a2b52c71..306ad67f4 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -1017,6 +1017,7 @@ void PluginManager::unregisterCommands( Plugin * p ) Plugin *PluginManager::operator[] (std::string name) { + MUTEX_GUARD(plugin_mutex); if (all_plugins.find(name) == all_plugins.end()) { if (Filesystem::isfile(getPluginPath(name))) diff --git a/library/include/Core.h b/library/include/Core.h index 45d7fab35..2d74405e1 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -159,6 +159,11 @@ namespace DFHack command_result runCommand(color_ostream &out, const std::string &command); bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false); + bool addScriptPath(std::string path, bool search_before = false); + bool removeScriptPath(std::string path); + std::string findScript(std::string name); + void getScriptPaths(std::vector *dest); + bool ClearKeyBindings(std::string keyspec); bool AddKeyBinding(std::string keyspec, std::string cmdline); std::vector ListKeyBindings(std::string keyspec); @@ -231,6 +236,9 @@ namespace DFHack std::vector allModules; DFHack::PluginManager * plug_mgr; + std::vector script_paths[2]; + tthread::mutex *script_path_mutex; + // hotkey-related stuff struct KeyBinding { int modifiers; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 142ae3685..fa3a3a975 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -420,24 +420,7 @@ local scripts = internal.scripts local hack_path = dfhack.getHackPath() function dfhack.findScript(name) - local file - file = dfhack.getSavePath() - if file then - file = file .. '/raw/scripts/' .. name .. '.lua' - if dfhack.filesystem.exists(file) then - return file - end - end - local path = dfhack.getDFPath() - file = path..'/raw/scripts/' .. name .. '.lua' - if dfhack.filesystem.exists(file) then - return file - end - file = path..'/hack/scripts/'..name..'.lua' - if dfhack.filesystem.exists(file) then - return file - end - return nil + return dfhack.internal.findScript(name .. '.lua') end local valid_script_flags = { @@ -477,7 +460,7 @@ function dfhack.script_environment(name, strict) if not scripts[path] or scripts[path]:needs_update() then local _, env = dfhack.run_script_with_env(nil, name, { module=true, - module_strict=strict and true or false -- ensure that this key is present if 'strict' is nil + module_strict=(strict and true or false) -- ensure that this key is present if 'strict' is nil }) return env else