Use helpdb to implement help and ls built-in commands and dfhack.script_help() (#2242)

* use helpdb to implement the help and ls builtins

* use helpdb to implement dfhack.script_help()
develop
Myk 2022-07-08 15:53:51 -07:00 committed by myk002
parent 4ad8e7199a
commit e899510b8b
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
3 changed files with 117 additions and 190 deletions

@ -446,6 +446,7 @@ command_result Core::runCommand(color_ostream &out, const std::string &command)
static const std::set<std::string> built_in_commands = {
"ls" ,
"help" ,
"tags" ,
"type" ,
"load" ,
"unload" ,
@ -674,25 +675,71 @@ string getBuiltinCommand(std::string cmd)
return builtin;
}
void ls_helper(color_ostream &con, const string &name, const string &desc)
{
const size_t help_line_length = 80 - 22 - 5;
const string padding = string(80 - help_line_length, ' ');
vector<string> lines;
con.print(" %-22s - ", name.c_str());
word_wrap(&lines, desc, help_line_length);
void help_helper(color_ostream &con, const string &entry_name) {
CoreSuspender suspend;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
// print first line, then any additional lines preceded by padding
for (size_t i = 0; i < lines.size(); i++)
con.print("%s%s\n", i ? padding.c_str() : "", lines[i].c_str());
if (!lua_checkstack(L, 2) ||
!Lua::PushModulePublic(con, L, "helpdb", "help")) {
con.printerr("Failed to load helpdb Lua code\n");
return;
}
void ls_helper(color_ostream &con, const PluginCommand &pcmd)
{
if (pcmd.isHotkeyCommand())
con.color(COLOR_CYAN);
ls_helper(con, pcmd.name, pcmd.description);
con.reset_color();
Lua::Push(L, entry_name);
if (!Lua::SafeCall(con, L, 1, 0)) {
con.printerr("Failed Lua call to helpdb.help.\n");
}
}
void tags_helper(color_ostream &con) {
CoreSuspender suspend;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 1) ||
!Lua::PushModulePublic(con, L, "helpdb", "tags")) {
con.printerr("Failed to load helpdb Lua code\n");
return;
}
if (!Lua::SafeCall(con, L, 0, 0)) {
con.printerr("Failed Lua call to helpdb.tags.\n");
}
}
void ls_helper(color_ostream &con, const vector<string> &params) {
vector<string> filter;
bool skip_tags = false;
bool show_dev_commands = false;
for (auto str : params) {
if (str == "--notags")
skip_tags = true;
else if (str == "--dev")
show_dev_commands = true;
else
filter.push_back(str);
}
CoreSuspender suspend;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 4) ||
!Lua::PushModulePublic(con, L, "helpdb", "ls")) {
con.printerr("Failed to load helpdb Lua code\n");
return;
}
Lua::PushVector(L, filter);
Lua::Push(L, skip_tags);
Lua::Push(L, show_dev_commands);
if (!Lua::SafeCall(con, L, 3, 0)) {
con.printerr("Failed Lua call to helpdb.ls.\n");
}
}
command_result Core::runCommand(color_ostream &con, const std::string &first_, vector<string> &parts)
@ -733,69 +780,32 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
"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"
con.print("Here are some basic commands to get you started:\n"
" help|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\n"
" ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n"
" help <tool> - Usage help for the given plugin, command, or script.\n"
" tags - List the tags that the DFHack tools are grouped by.\n"
" ls|dir [<filter>] - List commands, optionally filtered by a tag or substring.\n"
" Optional parameters:\n"
" --notags: skip printing tags for each command.\n"
" --dev: include commands intended for developers and modders.\n"
" cls|clear - Clear the console.\n"
" fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n"
" keybinding - Modify bindings of commands to keys\n"
"Plugin management (useful for developers):\n"
" plug [PLUGIN|v] - List plugin state and description.\n"
" load PLUGIN|-all - Load a plugin by name or load all possible plugins.\n"
" unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|-all - Reload a plugin or all loaded plugins.\n"
" die - Force DF to close immediately, without saving.\n"
" keybinding - Modify bindings of commands to in-game key shortcuts.\n"
"\n"
"See more commands by running 'ls'.\n\n"
);
con.print("\nDFHack version %s\n", dfhack_version_desc().c_str());
}
else if (parts.size() == 1)
{
if (getBuiltinCommand(parts[0]).size())
{
con << parts[0] << ": built-in command; Use `ls`, `help`, or check hack/Readme.html for more information" << std::endl;
return CR_NOT_IMPLEMENTED;
con.print("DFHack version %s\n", dfhack_version_desc().c_str());
}
Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (plug) {
for (size_t j = 0; j < plug->size();j++)
else
{
const PluginCommand & pcmd = (plug->operator[](j));
if (pcmd.name != parts[0])
continue;
if (pcmd.isHotkeyCommand())
con.color(COLOR_CYAN);
con.print("%s: %s\n",pcmd.name.c_str(), pcmd.description.c_str());
con.reset_color();
if (!pcmd.usage.empty())
con << "Usage:\n" << pcmd.usage << flush;
return CR_OK;
}
}
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(parts[0] + ".rb");
if ( file != "" ) {
string help = getScriptHelp(file, "#");
con.print("%s: %s\n", parts[0].c_str(), help.c_str());
return CR_OK;
}
help_helper(con, parts[0]);
}
con.printerr("Unknown command: %s\n", parts[0].c_str());
return CR_FAILURE;
}
else
else if (builtin == "tags")
{
con.printerr("not implemented yet\n");
return CR_NOT_IMPLEMENTED;
}
tags_helper(con);
}
else if (builtin == "load" || builtin == "unload" || builtin == "reload")
{
@ -906,83 +916,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
}
else if (builtin == "ls" || builtin == "dir")
{
bool all = false;
if (parts.size() && parts[0] == "-a")
{
all = true;
vector_erase_at(parts, 0);
}
if(parts.size())
{
string & plugname = parts[0];
const Plugin * plug = (*plug_mgr)[plugname];
if(!plug)
{
con.printerr("There's no plugin called %s!\n", plugname.c_str());
}
else if (plug->getState() != Plugin::PS_LOADED)
{
con.printerr("Plugin %s is not loaded.\n", plugname.c_str());
}
else if (!plug->size())
{
con.printerr("Plugin %s is loaded but does not implement any commands.\n", plugname.c_str());
}
else for (size_t j = 0; j < plug->size();j++)
{
ls_helper(con, plug->operator[](j));
}
}
else
{
con.print(
"builtin:\n"
" help|?|man - This text or help specific to a plugin.\n"
" ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n"
" cls|clear - Clear the console.\n"
" fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n"
" kill-lua - Stop an active Lua script\n"
" keybinding - Modify bindings of commands to keys\n"
" script FILENAME - Run the commands specified in a file.\n"
" sc-script - Automatically run specified scripts on state change events\n"
" plug [PLUGIN|v] - List plugin state and detailed description.\n"
" load PLUGIN|-all [...] - Load a plugin by name or load all possible plugins.\n"
" unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n"
" reload PLUGIN|-all [...] - Reload a plugin or all loaded plugins.\n"
" enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n"
" type COMMAND - Display information about where a command is implemented\n"
"\n"
"plugins:\n"
);
std::set <sortable> out;
for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it)
{
const Plugin * plug = it->second;
if(!plug->size())
continue;
for (size_t j = 0; j < plug->size();j++)
{
const PluginCommand & pcmd = (plug->operator[](j));
out.insert(sortable(pcmd.isHotkeyCommand(),pcmd.name,pcmd.description));
}
}
for(auto iter = out.begin();iter != out.end();iter++)
{
if ((*iter).recolor)
con.color(COLOR_CYAN);
ls_helper(con, iter->name, iter->description);
con.reset_color();
}
std::map<string, string> scripts;
listAllScripts(scripts, all);
if (!scripts.empty())
{
con.print("\nscripts:\n");
for (auto iter = scripts.begin(); iter != scripts.end(); ++iter)
ls_helper(con, iter->first, iter->second);
}
}
ls_helper(con, parts);
}
else if (builtin == "plug")
{

@ -1,4 +1,4 @@
-- Common startup file for all dfhack plugins with lua support
-- Common startup file for all dfhack scripts and plugins with lua support
-- The global dfhack table is already created by C++ init code.
-- Setup the global environment.
@ -652,8 +652,9 @@ function Script:needs_update()
return (not self.env) or self.mtime ~= dfhack.filesystem.mtime(self.path)
end
function Script:get_flags()
if self.flags_mtime ~= dfhack.filesystem.mtime(self.path) then
self.flags_mtime = dfhack.filesystem.mtime(self.path)
local mtime = dfhack.filesystem.mtime(self.path)
if self.flags_mtime ~= mtime then
self.flags_mtime = mtime
self._flags = {}
local f = io.open(self.path)
local contents = f:read('*all')
@ -814,36 +815,7 @@ end
function dfhack.script_help(script_name, extension)
script_name = script_name or dfhack.current_script_name()
extension = extension or 'lua'
local full_name = script_name .. '.' .. extension
local path = dfhack.internal.findScript(script_name .. '.' .. extension)
or error("Could not find script: " .. full_name)
local begin_seq, end_seq
if extension == 'rb' then
begin_seq = '=begin'
end_seq = '=end'
else
begin_seq = '[====['
end_seq = ']====]'
end
local f = io.open(path) or error("Could not open " .. path)
local in_help = false
local help = ''
for line in f:lines() do
if line:endswith(begin_seq) then
in_help = true
elseif in_help then
if line:endswith(end_seq) then
break
end
if line ~= script_name and line ~= ('='):rep(#script_name) then
help = help .. line .. '\n'
end
end
end
f:close()
help = help:gsub('^\n+', ''):gsub('\n+$', '')
return help
return require('helpdb').get_entry_long_help(script_name)
end
local function _run_command(args, use_console)

@ -326,11 +326,13 @@ local function scan_scripts(old_db, db)
f.path:startswith('internal/') then
goto continue
end
local dot_index = f.path:find('%.[^.]*$')
local script_source = script_path .. '/' .. f.path
update_db(old_db, db,
has_rendered_help(f.path) and
HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT,
f.path:sub(1, #f.path - 4), {script_source=script_source})
f.path:sub(1, dot_index - 1),
{script_source=script_source})
::continue::
end
::skip_path::
@ -384,7 +386,7 @@ end
local function get_db_property(entry_name, property)
ensure_db()
if not db[entry_name] then
error(('entry not found: "%s"'):format(entry_name))
error(('helpdb entry not found: "%s"'):format(entry_name))
end
return db[entry_name][property]
end
@ -415,8 +417,17 @@ end
-- returns whether the given string matches a tag name
function is_tag(str)
if not str or #str == 0 then
return false
end
ensure_db()
return not not tag_index[str]
if type(str) == "string" then str = {str} end
for _,s in ipairs(str) do
if not tag_index[s] then
return false
end
end
return true
end
-- returns the defined tags in alphabetical order
@ -510,7 +521,7 @@ end
-- converts strings into single-element lists containing that string
local function normalize_string_list(l)
if not l then return nil end
if not l or #l == 0 then return nil end
if type(l) == 'string' then
return {l}
end
@ -561,9 +572,19 @@ function search_entries(include, exclude)
end
---------------------------------------------------------------------------
-- list API (outputs to console)
-- print API (outputs to console)
---------------------------------------------------------------------------
-- implements the 'help' builtin command
function help(entry)
ensure_db()
if not db[entry] then
dfhack.printerr(('No help entry found for "%s"'):format(entry))
return
end
print(get_entry_long_help(entry))
end
local function get_max_width(list, min_width)
local width = min_width or 0
for _,item in ipairs(list) do
@ -572,8 +593,8 @@ local function get_max_width(list, min_width)
return width
end
-- prints the defined tags and their descriptions to the console
function list_tags()
-- implements the 'tags' builtin command
function tags()
local tags = get_tags()
local width = get_max_width(tags, 10)
for _,tag in ipairs(tags) do