From a4c853597754efacb9eb9f430f906035067b052f Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 3 Aug 2022 00:01:25 -0700 Subject: [PATCH] add argparse int checking methods, more docs --- docs/Lua API.rst | 12 +++++++++ library/lua/argparse.lua | 15 +++++++++--- plugins/CMakeLists.txt | 6 +++++ plugins/examples/skeleton.cpp | 41 +++++++++++++++++++++++++------ plugins/lua/skeleton.lua | 46 +++++++++++++++++++++++++++++++++++ test/library/argparse.lua | 30 +++++++++++++++++++++++ 6 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 plugins/lua/skeleton.lua diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 55a217757..d1dbc2856 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3029,6 +3029,18 @@ parameters. function also verifies that the coordinates are valid for the current map and throws if they are not (unless ``skip_validation`` is set to true). +* ``argparse.positiveInt(arg, arg_name)`` + + Throws if ``tonumber(arg)`` is not a positive integer; otherwise returns + ``tonumber(arg)``. If ``arg_name`` is specified, it is used to make error + messages more useful. + +* ``argparse.nonnegativeInt(arg, arg_name)`` + + Throws if ``tonumber(arg)`` is not a non-negative integer; otherwise returns + ``tonumber(arg)``. If ``arg_name`` is specified, it is used to make error + messages more useful. + dumper ====== diff --git a/library/lua/argparse.lua b/library/lua/argparse.lua index ee170c190..a143f454c 100644 --- a/library/lua/argparse.lua +++ b/library/lua/argparse.lua @@ -154,11 +154,20 @@ function numberList(arg, arg_name, list_length) return strings end --- throws if val is not a nonnegative integer; otherwise returns val -local function check_nonnegative_int(val, arg_name) +function positiveInt(arg, arg_name) + local val = tonumber(arg) + if not val or val <= 0 or val ~= math.floor(val) then + arg_error(arg_name, + 'expected positive integer; got "%s"', tostring(arg)) + end + return val +end + +function nonnegativeInt(arg, arg_name) + local val = tonumber(arg) if not val or val < 0 or val ~= math.floor(val) then arg_error(arg_name, - 'expected non-negative integer; got "%s"', tostring(val)) + 'expected non-negative integer; got "%s"', tostring(arg)) end return val end diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6a34117b9..1e2019091 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -185,6 +185,12 @@ if(BUILD_SUPPORTED) # see instructions for adding "external" plugins at the end of this file. endif() +# this is the skeleton plugin. If you want to make your own, make a copy and then change it +option(BUILD_SKELETON "Build the skeleton plugin." OFF) +if(BUILD_SKELETON) + dfhack_plugin(skeleton examples/skeleton.cpp LINK_LIBRARIES lua) +endif() + macro(subdirlist result subdir) file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/) set(dirlist "") diff --git a/plugins/examples/skeleton.cpp b/plugins/examples/skeleton.cpp index e1f552d31..27621af4a 100644 --- a/plugins/examples/skeleton.cpp +++ b/plugins/examples/skeleton.cpp @@ -1,6 +1,8 @@ -// This is an example plugin that just documents and implements all the plugin -// callbacks and features. You can compile it, load it, run it, and see the -// debug messages get printed to the console. +// This is an example plugin that documents and implements all the plugin +// callbacks and features. You can include it in the regular build by setting +// the BUILD_SKELETON option in CMake to ON. Play with loading and unloading +// the plugin in various game states (e.g. with and without a world loaded), +// and see the debug messages get printed to the console. // // See the other example plugins in this directory for plugins that are // configured for specific use cases (but don't come with as many comments as @@ -44,17 +46,23 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); // logging levels can be dynamically controlled with the `debugfilter` command. +// Actual plugins will likely want to set the default level to LINFO or LWARNING +// instead of the LDEBUG used here. namespace DFHack { // for configuration-related logging - DBG_DECLARE(skeleton, status, DebugCategory::LINFO); - // for logging during the periodic scan - DBG_DECLARE(skeleton, cycle, DebugCategory::LINFO); + DBG_DECLARE(skeleton, status, DebugCategory::LDEBUG); + // run `debugfilter set debug skeleton onupdate` to see logging in plugin_onupdate + DBG_DECLARE(skeleton, onupdate, DebugCategory::LINFO); + // for command-related logging + DBG_DECLARE(skeleton, command, DebugCategory::LDEBUG); } -command_result command_callback1(color_ostream &out, vector ¶meters); +static command_result command_callback1(color_ostream &out, vector ¶meters); // run when the plugin is loaded DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(status,out).print("initializing %s\n", plugin_name); + // For in-tree plugins, don't use the "usage" parameter of PluginCommand. // Instead, add an .rst file with the same name as the plugin to the // docs/plugins/ directory. @@ -67,6 +75,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector ¶meters) { + DEBUG(command,out).print("%s command called with %zu parameters\n", + plugin_name, parameters.size()); + // I'll say it again: always suspend the core in command callbacks unless // all your data is local. CoreSuspender suspend; diff --git a/plugins/lua/skeleton.lua b/plugins/lua/skeleton.lua new file mode 100644 index 000000000..60af229cc --- /dev/null +++ b/plugins/lua/skeleton.lua @@ -0,0 +1,46 @@ +local _ENV = mkmodule('plugins.skeleton') + +local argparse = require('argparse') +local utils = require('utils') + +local VALID_FORMATS = utils.invert{'pretty', 'normal', 'ugly'} + +local function do_commandline(opts, args) + print(('called with %d arguments:'):format(#args)) + for _,arg in ipairs(args) do + print(' ' .. arg) + end + + local positionals = argparse.processArgsGetopt(args, { + {'t', 'ticks', hasArg=true, + handler=function(arg) opts.ticks = + argparse.check_positive_int(arg, 'ticks') end}, + {'s', 'start', hasArg=true, + handler=function(arg) utils.assign( + opts.start, argpars.coors(arg, 'start')) end}, + {'h', 'help', handler=function() opts.help = true end}, + {'f', 'format', hasArg=true, + handler=function(arg) opts.format = arg end}, + {'z', 'cur-zlevel', handler=function() use_zlevel = true end}, + }) + + if positionals[1] == 'help' then opts.help = true end + if opts.help then return end + + if positionals[1] == 'now' then + opts.now = true + end + + if #opts.format > 0 and not VALID_FORMATS[opts.format] then + qerror(('invalid format name: "%s"'):format(opts.format)) + end +end + +function parse_commandline(opts, ...) + do_commandline(opts, {...}) + + print('populated options data structure:') + printall_recurse(opts) +end + +return _ENV diff --git a/test/library/argparse.lua b/test/library/argparse.lua index 4e5c48acc..5e85e6465 100644 --- a/test/library/argparse.lua +++ b/test/library/argparse.lua @@ -265,3 +265,33 @@ function test.coords() end) end) end + +function test.positiveInt() + expect.eq(5, argparse.positiveInt(5)) + expect.eq(5, argparse.positiveInt('5')) + expect.eq(5, argparse.positiveInt('5.0')) + expect.eq(1, argparse.positiveInt('1')) + + expect.error_match('expected positive integer', + function() argparse.positiveInt('0') end) + expect.error_match('expected positive integer', + function() argparse.positiveInt('5.01') end) + expect.error_match('expected positive integer', + function() argparse.positiveInt(-1) end) +end + +function test.nonnegativeInt() + expect.eq(5, argparse.nonnegativeInt(5)) + expect.eq(5, argparse.nonnegativeInt('5')) + expect.eq(5, argparse.nonnegativeInt('5.0')) + expect.eq(1, argparse.nonnegativeInt('1')) + expect.eq(0, argparse.nonnegativeInt('0')) + expect.eq(0, argparse.nonnegativeInt('-0')) + + expect.error_match('expected non%-negative integer', + function() argparse.nonnegativeInt('-0.01') end) + expect.error_match('expected non%-negative integer', + function() argparse.nonnegativeInt(-5) end) + expect.error_match('expected non%-negative integer', + function() argparse.nonnegativeInt(-1) end) +end