From b3c8d8563793fbfe228ac7eddd192d311cb0c24e Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 5 Dec 2022 17:08:08 -0800 Subject: [PATCH 01/10] new plugin: script-manager scans all scripts and discovers onStateChange and isEnabled functions --- plugins/CMakeLists.txt | 1 + plugins/lua/script-manager.lua | 57 ++++++++++++++++++++++++++++++++++ plugins/script-manager.cpp | 31 ++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 plugins/lua/script-manager.lua create mode 100644 plugins/script-manager.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3ad19efb4..78bf63903 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -154,6 +154,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) dfhack_plugin(reveal reveal.cpp LINK_LIBRARIES lua) + dfhack_plugin(script-manager script-manager.cpp LINK_LIBRARIES lua) dfhack_plugin(search search.cpp) dfhack_plugin(seedwatch seedwatch.cpp) dfhack_plugin(showmood showmood.cpp) diff --git a/plugins/lua/script-manager.lua b/plugins/lua/script-manager.lua new file mode 100644 index 000000000..4d750091e --- /dev/null +++ b/plugins/lua/script-manager.lua @@ -0,0 +1,57 @@ +local _ENV = mkmodule('plugins.script-manager') + +local utils = require('utils') + +-- for each script that can be loaded as a module, calls cb(script_name, env) +function foreach_module_script(cb) + for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do + local files = dfhack.filesystem.listdir_recursive( + script_path, nil, false) + if not files then goto skip_path end + for _,f in ipairs(files) do + if not f.isdir and + f.path:endswith('.lua') and + not f.path:startswith('test/') and + not f.path:startswith('internal/') then + local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' + local ok, script_env = pcall(reqscript, script_name) + if ok then + cb(script_name, script_env) + end + end + end + ::skip_path:: + end +end + +local enabled_map = {} + +local function process_global(env_name, env, global_name, target) + local fn = env[global_name] + if not fn then return end + if type(fn) ~= 'function' then + dfhack.printerr( + ('error registering %s() from "%s": global' .. + ' value is not a function'):format(global_name, env_name)) + return + end + target[env_name] = fn +end + +local function process_script(env_name, env) + process_global(env_name, env, 'onStateChange', dfhack.onStateChange) + process_global(env_name, env, 'isEnabled', enabled_map) +end + +function init() + enabled_map = utils.OrderedTable() + foreach_module_script(process_script) +end + +function list() + for name,fn in pairs(enabled_map) do + print(('%20s\t%-3s'):format(name..':', fn() and 'on' or 'off')) + end +end + +return _ENV diff --git a/plugins/script-manager.cpp b/plugins/script-manager.cpp new file mode 100644 index 000000000..27a1a9943 --- /dev/null +++ b/plugins/script-manager.cpp @@ -0,0 +1,31 @@ +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("script-manager"); + +namespace DFHack { + DBG_DECLARE(script_manager, log, DebugCategory::LINFO); +} + +DFhackCExport command_result plugin_init(color_ostream &, std::vector &) { + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event != SC_CORE_INITIALIZED) + return CR_OK; + + DEBUG(log,out).print("scanning scripts for onStateChange() functions\n"); + + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(out, L, "plugins.script-manager", "init"); + + return CR_OK; +} From 0362d76b399b6991a2c7f1f38128da242e37578b Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 5 Dec 2022 17:08:50 -0800 Subject: [PATCH 02/10] list enableable scripts in the `enable` builtin --- library/Core.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 836772c04..33d9c43b2 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -826,9 +826,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v "%20s\t%-3s%s\n", (plug->getName()+":").c_str(), plug->is_enabled() ? "on" : "off", - plug->can_set_enabled() ? "" : " (controlled elsewhere)" + plug->can_set_enabled() ? "" : " (controlled internally)" ); } + + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, + "plugins.script-manager", "list"); } } else if (first == "ls" || first == "dir") From 426a538e304da5cdf6abe5486ca0847d0a8fa666 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 5 Dec 2022 17:09:08 -0800 Subject: [PATCH 03/10] refactor overlay to use script-manager code --- plugins/lua/overlay.lua | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index d63041993..95148e8b8 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -5,6 +5,8 @@ local json = require('json') local utils = require('utils') local widgets = require('gui.widgets') +local scriptmanager = require('plugins.script-manager') + local OVERLAY_CONFIG_FILE = 'dfhack-config/overlay.json' local OVERLAY_WIDGETS_VAR = 'OVERLAY_WIDGETS' @@ -250,11 +252,9 @@ local function load_widget(name, widget_class) end end -local function load_widgets(env_prefix, provider, env_fn) - local env_name = env_prefix .. provider - local ok, provider_env = pcall(env_fn, env_name) - if not ok or not provider_env[OVERLAY_WIDGETS_VAR] then return end - local overlay_widgets = provider_env[OVERLAY_WIDGETS_VAR] +local function load_widgets(env_name, env) + local overlay_widgets = env[OVERLAY_WIDGETS_VAR] + if not overlay_widgets then return end if type(overlay_widgets) ~= 'table' then dfhack.printerr( ('error loading overlay widgets from "%s": %s map is malformed') @@ -262,7 +262,7 @@ local function load_widgets(env_prefix, provider, env_fn) return end for widget_name,widget_class in pairs(overlay_widgets) do - local name = provider .. '.' .. widget_name + local name = env_name .. '.' .. widget_name if not safecall(load_widget, name, widget_class) then dfhack.printerr(('error loading overlay widget "%s"'):format(name)) end @@ -274,23 +274,13 @@ function reload() reset() for _,plugin in ipairs(dfhack.internal.listPlugins()) do - load_widgets('plugins.', plugin, require) - end - for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do - local files = dfhack.filesystem.listdir_recursive( - script_path, nil, false) - if not files then goto skip_path end - for _,f in ipairs(files) do - if not f.isdir and - f.path:endswith('.lua') and - not f.path:startswith('test/') and - not f.path:startswith('internal/') then - local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' - load_widgets('', script_name, reqscript) - end + local env_name = 'plugins.' .. plugin + local ok, plugin_env = pcall(require, env_name) + if ok then + load_widgets(plugin, plugin_env) end - ::skip_path:: end + scriptmanager.foreach_module_script(load_widgets) for name in pairs(widget_db) do table.insert(widget_index, name) From 854642734c77b9ca44f1270786b7ac2965cdf308 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 5 Dec 2022 17:20:27 -0800 Subject: [PATCH 04/10] add docs for script-manager --- docs/plugins/script-manager.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/plugins/script-manager.rst diff --git a/docs/plugins/script-manager.rst b/docs/plugins/script-manager.rst new file mode 100644 index 000000000..2301c2f6e --- /dev/null +++ b/docs/plugins/script-manager.rst @@ -0,0 +1,10 @@ +script-manager +============== + +.. dfhack-tool:: + :summary: Manages startup tasks for scripts. + :tags: dev + :no-command: + +This plugin connects ``onStateChange()`` hooks for scripts so they can load +saved state and handles enabled state tracking and reporting. From a872cdbcd4b6e4e67fa37a8d4202d7a4b84e4263 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 6 Dec 2022 14:42:27 -0800 Subject: [PATCH 05/10] no onChangeState fn, let scripts attach themselves --- docs/plugins/script-manager.rst | 8 +++++--- plugins/lua/script-manager.lua | 10 +++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/plugins/script-manager.rst b/docs/plugins/script-manager.rst index 2301c2f6e..3644b5337 100644 --- a/docs/plugins/script-manager.rst +++ b/docs/plugins/script-manager.rst @@ -2,9 +2,11 @@ script-manager ============== .. dfhack-tool:: - :summary: Manages startup tasks for scripts. + :summary: Triggers startup tasks for scripts. :tags: dev :no-command: -This plugin connects ``onStateChange()`` hooks for scripts so they can load -saved state and handles enabled state tracking and reporting. +This plugin loads all scripts that are declared as modules so that they can +put state change hooks in place for loading persistent data. It also scans for +global ``isEnabled()`` functions and gathers them for script enabled state +tracking and reporting for the `enable` command. diff --git a/plugins/lua/script-manager.lua b/plugins/lua/script-manager.lua index 4d750091e..4e3889ba9 100644 --- a/plugins/lua/script-manager.lua +++ b/plugins/lua/script-manager.lua @@ -26,7 +26,8 @@ end local enabled_map = {} -local function process_global(env_name, env, global_name, target) +local function process_script(env_name, env) + local global_name = 'isEnabled' local fn = env[global_name] if not fn then return end if type(fn) ~= 'function' then @@ -35,12 +36,7 @@ local function process_global(env_name, env, global_name, target) ' value is not a function'):format(global_name, env_name)) return end - target[env_name] = fn -end - -local function process_script(env_name, env) - process_global(env_name, env, 'onStateChange', dfhack.onStateChange) - process_global(env_name, env, 'isEnabled', enabled_map) + enabled_map[env_name] = fn end function init() From 70ad8a2260bc20e73ee47aadcd0c4ea624d3ac27 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 6 Dec 2022 15:53:17 -0800 Subject: [PATCH 06/10] let prioritize restore its state --- data/examples/init/onMapLoad_dreamfort.init | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index fe2b1d54e..8f341cf7e 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -48,8 +48,8 @@ enable seedwatch seedwatch all 30 # ensures important tasks get assigned to workers. -# otherwise these job types can get ignored in busy forts. -prioritize -aq defaults +# otherwise many job types can get ignored in busy forts. +on-new-fortress prioritize -aq defaults # autobutcher settings are saved in the savegame, so we only need to set them once. # this way, any custom settings you set during gameplay are not overwritten From 107be0a4a6db459ebc9faf8972c48ce807c657f7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 6 Dec 2022 16:42:14 -0800 Subject: [PATCH 07/10] more script enablement docs --- docs/builtins/enable.rst | 3 +++ docs/dev/Lua API.rst | 39 +++++++++++++++++++++++++++++++++ docs/plugins/script-manager.rst | 2 ++ 3 files changed, 44 insertions(+) diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst index 7af10a9f3..8525214b3 100644 --- a/docs/builtins/enable.rst +++ b/docs/builtins/enable.rst @@ -16,6 +16,9 @@ and disabled plugins, and shows whether that can be changed through the same commands. Passing plugin names to these commands will enable or disable the specified plugins. +If you are a script developer, see `script-enable-api` for how to expose whether +your script is currently enabled or disabled. + Usage ----- diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 022d24de6..b25b41166 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -5532,6 +5532,8 @@ Importing scripts not declare support as described above, although it is preferred to update such scripts so that ``reqscript()`` can be used instead. +.. _script-enable-api: + Enabling and disabling scripts ============================== @@ -5546,18 +5548,55 @@ table passed to the script will have the following fields set: * ``enable``: Always ``true`` if the script is being enabled *or* disabled * ``enable_state``: ``true`` if the script is being enabled, ``false`` otherwise +If you declare a global function named ``isEnabled`` that returns a boolean +indicating whether your script is enabled, then your script will be listed among +the other enableable scripts and plugins when the player runs the `enable` +command. + Example usage:: --@ enable = true + + enabled = enabled or false + function isEnabled() + return enabled + end + -- (function definitions...) + if dfhack_flags.enable then if dfhack_flags.enable_state then start() + enabled = true else stop() + enabled = false end end +If the state of your script is tied to the active savegame, then your script +should hook the appropriate events to load persisted state when the savegame is +reloaded. For example:: + + local json = require('json') + local persist = require('persist-table') + + local GLOBAL_KEY = 'my-script-name' + g_state = g_state or {} + + dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc ~= SC_MAP_LOADED or df.global.gamemode ~= df.game_mode.DWARF then + return + end + local state = json.decode(persist.GlobalTable[GLOBAL_KEY] or '') + g_state = state or {} + end + +The attachment to ``dfhack.onStateChange`` should appear in your script code +outside of any function. The `script-manager` will load your script as a module +when DFHack is initialized, giving this code an opportunity to run and attach +hooks before a game is loaded. + Save init script ================ diff --git a/docs/plugins/script-manager.rst b/docs/plugins/script-manager.rst index 3644b5337..6a2e07f53 100644 --- a/docs/plugins/script-manager.rst +++ b/docs/plugins/script-manager.rst @@ -10,3 +10,5 @@ This plugin loads all scripts that are declared as modules so that they can put state change hooks in place for loading persistent data. It also scans for global ``isEnabled()`` functions and gathers them for script enabled state tracking and reporting for the `enable` command. + +Please see `script-enable-api` for more details. From 0d4d10de086a7981592737e19b89653b102e4c55 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 6 Dec 2022 16:45:17 -0800 Subject: [PATCH 08/10] rename init to reload --- plugins/lua/script-manager.lua | 2 +- plugins/script-manager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/lua/script-manager.lua b/plugins/lua/script-manager.lua index 4e3889ba9..919db077b 100644 --- a/plugins/lua/script-manager.lua +++ b/plugins/lua/script-manager.lua @@ -39,7 +39,7 @@ local function process_script(env_name, env) enabled_map[env_name] = fn end -function init() +function reload() enabled_map = utils.OrderedTable() foreach_module_script(process_script) end diff --git a/plugins/script-manager.cpp b/plugins/script-manager.cpp index 27a1a9943..767819f93 100644 --- a/plugins/script-manager.cpp +++ b/plugins/script-manager.cpp @@ -25,7 +25,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan auto L = Lua::Core::State; Lua::StackUnwinder top(L); - Lua::CallLuaModuleFunction(out, L, "plugins.script-manager", "init"); + Lua::CallLuaModuleFunction(out, L, "plugins.script-manager", "reload"); return CR_OK; } From e88b1fdfe5981b7ed7fa729196600434618bef99 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 7 Dec 2022 13:26:35 -0800 Subject: [PATCH 09/10] move script-manager into core --- docs/dev/Lua API.rst | 6 ++-- docs/plugins/script-manager.rst | 14 ---------- library/Core.cpp | 9 ++++-- {plugins => library}/lua/script-manager.lua | 2 +- plugins/CMakeLists.txt | 1 - plugins/lua/overlay.lua | 3 +- plugins/script-manager.cpp | 31 --------------------- 7 files changed, 11 insertions(+), 55 deletions(-) delete mode 100644 docs/plugins/script-manager.rst rename {plugins => library}/lua/script-manager.lua (97%) delete mode 100644 plugins/script-manager.cpp diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index b25b41166..1a2fdc18c 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -5593,9 +5593,9 @@ reloaded. For example:: end The attachment to ``dfhack.onStateChange`` should appear in your script code -outside of any function. The `script-manager` will load your script as a module -when DFHack is initialized, giving this code an opportunity to run and attach -hooks before a game is loaded. +outside of any function. DFHack will load your script as a module just before +the ``SC_DFHACK_INITIALIZED`` state change event is sent, giving your code an +opportunity to run and attach hooks before the game is loaded. Save init script ================ diff --git a/docs/plugins/script-manager.rst b/docs/plugins/script-manager.rst deleted file mode 100644 index 6a2e07f53..000000000 --- a/docs/plugins/script-manager.rst +++ /dev/null @@ -1,14 +0,0 @@ -script-manager -============== - -.. dfhack-tool:: - :summary: Triggers startup tasks for scripts. - :tags: dev - :no-command: - -This plugin loads all scripts that are declared as modules so that they can -put state change hooks in place for loading persistent data. It also scans for -global ``isEnabled()`` functions and gathers them for script enabled state -tracking and reporting for the `enable` command. - -Please see `script-enable-api` for more details. diff --git a/library/Core.cpp b/library/Core.cpp index 33d9c43b2..eed2de30a 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -832,8 +832,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v auto L = Lua::Core::State; Lua::StackUnwinder top(L); - Lua::CallLuaModuleFunction(con, L, - "plugins.script-manager", "list"); + Lua::CallLuaModuleFunction(con, L, "script-manager", "list"); } } else if (first == "ls" || first == "dir") @@ -1838,8 +1837,12 @@ void Core::doUpdate(color_ostream &out, bool first_update) { Lua::Core::Reset(out, "DF code execution"); - if (first_update) + if (first_update) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(out, L, "script-manager", "reload"); onStateChange(out, SC_CORE_INITIALIZED); + } // find the current viewscreen df::viewscreen *screen = NULL; diff --git a/plugins/lua/script-manager.lua b/library/lua/script-manager.lua similarity index 97% rename from plugins/lua/script-manager.lua rename to library/lua/script-manager.lua index 919db077b..ef0c1fede 100644 --- a/plugins/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -1,4 +1,4 @@ -local _ENV = mkmodule('plugins.script-manager') +local _ENV = mkmodule('script-manager') local utils = require('utils') diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 78bf63903..3ad19efb4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -154,7 +154,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) dfhack_plugin(reveal reveal.cpp LINK_LIBRARIES lua) - dfhack_plugin(script-manager script-manager.cpp LINK_LIBRARIES lua) dfhack_plugin(search search.cpp) dfhack_plugin(seedwatch seedwatch.cpp) dfhack_plugin(showmood showmood.cpp) diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index 95148e8b8..9f2c9029b 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -2,11 +2,10 @@ local _ENV = mkmodule('plugins.overlay') local gui = require('gui') local json = require('json') +local scriptmanager = require('script-manager') local utils = require('utils') local widgets = require('gui.widgets') -local scriptmanager = require('plugins.script-manager') - local OVERLAY_CONFIG_FILE = 'dfhack-config/overlay.json' local OVERLAY_WIDGETS_VAR = 'OVERLAY_WIDGETS' diff --git a/plugins/script-manager.cpp b/plugins/script-manager.cpp deleted file mode 100644 index 767819f93..000000000 --- a/plugins/script-manager.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "Debug.h" -#include "LuaTools.h" -#include "PluginManager.h" - -using std::string; -using std::vector; - -using namespace DFHack; - -DFHACK_PLUGIN("script-manager"); - -namespace DFHack { - DBG_DECLARE(script_manager, log, DebugCategory::LINFO); -} - -DFhackCExport command_result plugin_init(color_ostream &, std::vector &) { - return CR_OK; -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - if (event != SC_CORE_INITIALIZED) - return CR_OK; - - DEBUG(log,out).print("scanning scripts for onStateChange() functions\n"); - - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - Lua::CallLuaModuleFunction(out, L, "plugins.script-manager", "reload"); - - return CR_OK; -} From ffd646462ab4897399cde9a5da5252add24ebe18 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 10 Dec 2022 22:13:38 -0800 Subject: [PATCH 10/10] ensure `enable` doesn't miss newly-added scripts and add some more documentation --- docs/dev/Lua API.rst | 15 ++++++++++++--- library/lua/script-manager.lua | 3 +++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 1a2fdc18c..c20514279 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -5574,9 +5574,9 @@ Example usage:: end end -If the state of your script is tied to the active savegame, then your script -should hook the appropriate events to load persisted state when the savegame is -reloaded. For example:: +If the state of your script can be tied to an active savegame, then your script +should hook the appropriate events to load persisted state when a savegame is +loaded. For example:: local json = require('json') local persist = require('persist-table') @@ -5597,6 +5597,15 @@ outside of any function. DFHack will load your script as a module just before the ``SC_DFHACK_INITIALIZED`` state change event is sent, giving your code an opportunity to run and attach hooks before the game is loaded. +If an enableable script is added to a DFHack `script path ` while +DF is running, then it will miss the initial sweep that loads all the module +scripts and any ``onStateChange`` handlers the script may want to register will +not be registered until the script is loaded via some means, either by running +it or loading it as a module. If you just added new scripts that you want to +load so they can attach their ``onStateChange`` handlers, run ``enable`` without +parameters or call ``:lua require('script-manager').reload()`` to scan and load +all script modules. + Save init script ================ diff --git a/library/lua/script-manager.lua b/library/lua/script-manager.lua index ef0c1fede..008e9443e 100644 --- a/library/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -45,6 +45,9 @@ function reload() end function list() + -- call reload every time we list to make sure we get scripts that have + -- just been added + reload() for name,fn in pairs(enabled_map) do print(('%20s\t%-3s'):format(name..':', fn() and 'on' or 'off')) end