add argparse int checking methods, more docs

develop
myk002 2022-08-03 00:01:25 -07:00 committed by Myk
parent 43dfd27bb4
commit a4c8535977
6 changed files with 139 additions and 11 deletions

@ -3029,6 +3029,18 @@ parameters.
function also verifies that the coordinates are valid for the current map and 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). 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 dumper
====== ======

@ -154,11 +154,20 @@ function numberList(arg, arg_name, list_length)
return strings return strings
end end
-- throws if val is not a nonnegative integer; otherwise returns val function positiveInt(arg, arg_name)
local function check_nonnegative_int(val, 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 if not val or val < 0 or val ~= math.floor(val) then
arg_error(arg_name, arg_error(arg_name,
'expected non-negative integer; got "%s"', tostring(val)) 'expected non-negative integer; got "%s"', tostring(arg))
end end
return val return val
end end

@ -185,6 +185,12 @@ if(BUILD_SUPPORTED)
# see instructions for adding "external" plugins at the end of this file. # see instructions for adding "external" plugins at the end of this file.
endif() 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) macro(subdirlist result subdir)
file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/) file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/)
set(dirlist "") set(dirlist "")

@ -1,6 +1,8 @@
// This is an example plugin that just documents and implements all the plugin // This is an example plugin that documents and implements all the plugin
// callbacks and features. You can compile it, load it, run it, and see the // callbacks and features. You can include it in the regular build by setting
// debug messages get printed to the console. // 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 // 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 // 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); REQUIRE_GLOBAL(world);
// logging levels can be dynamically controlled with the `debugfilter` command. // 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 { namespace DFHack {
// for configuration-related logging // for configuration-related logging
DBG_DECLARE(skeleton, status, DebugCategory::LINFO); DBG_DECLARE(skeleton, status, DebugCategory::LDEBUG);
// for logging during the periodic scan // run `debugfilter set debug skeleton onupdate` to see logging in plugin_onupdate
DBG_DECLARE(skeleton, cycle, DebugCategory::LINFO); DBG_DECLARE(skeleton, onupdate, DebugCategory::LINFO);
// for command-related logging
DBG_DECLARE(skeleton, command, DebugCategory::LDEBUG);
} }
command_result command_callback1(color_ostream &out, vector<string> &parameters); static command_result command_callback1(color_ostream &out, vector<string> &parameters);
// run when the plugin is loaded // run when the plugin is loaded
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) {
DEBUG(status,out).print("initializing %s\n", plugin_name);
// For in-tree plugins, don't use the "usage" parameter of PluginCommand. // 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 // Instead, add an .rst file with the same name as the plugin to the
// docs/plugins/ directory. // docs/plugins/ directory.
@ -67,6 +75,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginC
// run when the plugin is unloaded // run when the plugin is unloaded
DFhackCExport command_result plugin_shutdown(color_ostream &out) { DFhackCExport command_result plugin_shutdown(color_ostream &out) {
DEBUG(status,out).print("shutting down %s\n", plugin_name);
// You *MUST* kill all threads you created before this returns. // You *MUST* kill all threads you created before this returns.
// If everything fails, just return CR_FAILURE. Your plugin will be // If everything fails, just return CR_FAILURE. Your plugin will be
// in a zombie state, but things won't crash. // in a zombie state, but things won't crash.
@ -77,6 +87,8 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) {
// run when the `enable` or `disable` command is run with this plugin name as // run when the `enable` or `disable` command is run with this plugin name as
// an argument // an argument
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
DEBUG(status,out).print("%s from the API\n", enable ? "enabled" : "disabled");
// you have to maintain the state of the is_enabled variable yourself. it // you have to maintain the state of the is_enabled variable yourself. it
// doesn't happen automatically. // doesn't happen automatically.
is_enabled = enable; is_enabled = enable;
@ -87,6 +99,8 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
// Invoked with DF suspended, and always before the matching plugin_onupdate. // Invoked with DF suspended, and always before the matching plugin_onupdate.
// More event codes may be added in the future. // More event codes may be added in the future.
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
DEBUG(status,out).print("game state changed: %d\n", event);
if (is_enabled) { if (is_enabled) {
switch (event) { switch (event) {
case SC_UNKNOWN: case SC_UNKNOWN:
@ -111,12 +125,16 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
break; break;
} }
} }
return CR_OK; return CR_OK;
} }
// Whatever you put here will be done in each game frame refresh. Don't abuse it. // Whatever you put here will be done in each game frame refresh. Don't abuse it.
// Note that if the plugin implements the enabled API, this function is only called
// if the plugin is enabled.
DFhackCExport command_result plugin_onupdate (color_ostream &out) { DFhackCExport command_result plugin_onupdate (color_ostream &out) {
// whetever. You don't need to suspend DF execution here. DEBUG(onupdate,out).print("onupdate called\n");
return CR_OK; return CR_OK;
} }
@ -126,11 +144,15 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) {
// is loaded or unloaded while a world is active, plugin_save_data or // is loaded or unloaded while a world is active, plugin_save_data or
// plugin_load_data will be called immediately. // plugin_load_data will be called immediately.
DFhackCExport command_result plugin_save_data (color_ostream &out) { DFhackCExport command_result plugin_save_data (color_ostream &out) {
DEBUG(status,out).print("save or unload is imminent; time to persist state\n");
// Call functions in the Persistence module here. // Call functions in the Persistence module here.
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_load_data (color_ostream &out) { DFhackCExport command_result plugin_load_data (color_ostream &out) {
DEBUG(status,out).print("world is loading; time to load persisted state\n");
// Call functions in the Persistence module here. // Call functions in the Persistence module here.
return CR_OK; return CR_OK;
} }
@ -201,6 +223,9 @@ static bool get_options(color_ostream &out,
// from a different thread and need to explicity suspend the core if they // from a different thread and need to explicity suspend the core if they
// interact with Lua or DF game state (most commands do at least one of these). // interact with Lua or DF game state (most commands do at least one of these).
static command_result command_callback1(color_ostream &out, vector<string> &parameters) { static command_result command_callback1(color_ostream &out, vector<string> &parameters) {
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 // I'll say it again: always suspend the core in command callbacks unless
// all your data is local. // all your data is local.
CoreSuspender suspend; CoreSuspender suspend;

@ -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

@ -265,3 +265,33 @@ function test.coords()
end) end)
end) 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