205 lines
8.0 KiB
Plaintext
Executable File
205 lines
8.0 KiB
Plaintext
Executable File
--------------------------------------------------------------------------------
|
|
-- Command Line OPTionS handler
|
|
-- ============================
|
|
--
|
|
-- This lib generates parsers for command-line options. It encourages
|
|
-- the following of some common idioms: I'm pissed off by Unix tools
|
|
-- which sometimes will let you concatenate single letters options,
|
|
-- sometimes won't, will prefix long name options with simple dashes
|
|
-- instead of doubles, etc.
|
|
--
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- TODO:
|
|
-- * add a generic way to unparse options ('grab everything')
|
|
-- * doc
|
|
-- * when a short options that takes a param isn't the last element of a series
|
|
-- of shorts, take the remaining of the sequence as that param, e.g. -Ifoo
|
|
-- * let unset strings/numbers with +
|
|
-- * add a ++ long counterpart to +
|
|
--
|
|
|
|
-{ extension 'match' }
|
|
|
|
function clopts(cfg)
|
|
local short, long, param_func = { }, { }
|
|
local legal_types = table.transpose{
|
|
'boolean','string','number','string*','number*','nil', '*' }
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Fill short and long name indexes, and check its validity
|
|
-----------------------------------------------------------------------------
|
|
for x in ivalues(cfg) do
|
|
local xtype = type(x)
|
|
if xtype=='table' then
|
|
if not x.type then x.type='nil' end
|
|
if not legal_types[x.type] then error ("Invalid type name "..x.type) end
|
|
if x.short then
|
|
if short[x.short] then error ("multiple definitions for option "..x.short)
|
|
else short[x.short] = x end
|
|
end
|
|
if x.long then
|
|
if long[x.long] then error ("multiple definitions for option "..x.long)
|
|
else long[x.long] = x end
|
|
end
|
|
elseif xtype=='function' then
|
|
if param_func then error "multiple parameters handler in clopts"
|
|
else param_func=x end
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Print a help message, summarizing how to use the command line
|
|
-----------------------------------------------------------------------------
|
|
local function print_usage(msg)
|
|
if msg then print(msg,'\n') end
|
|
print(cfg.usage or "Options:\n")
|
|
for x in values(cfg) do
|
|
if type(x) == 'table' then
|
|
local opts = { }
|
|
if x.type=='boolean' then
|
|
if x.short then opts = { '-'..x.short..'/+'..x.short } end
|
|
if x.long then table.insert (opts, '--'..x.long..'/++'..x.long) end
|
|
else
|
|
if x.short then opts = { '-'..x.short..' <'..x.type..'>' } end
|
|
if x.long then table.insert (opts, '--'..x.long..' <'..x.type..'>' ) end
|
|
end
|
|
printf(" %s: %s", table.concat(opts,', '), x.usage or '<undocumented>')
|
|
end
|
|
end
|
|
print''
|
|
end
|
|
|
|
-- Unless overridden, -h and --help display the help msg
|
|
local default_help = { action = | | print_usage() or os.exit(0);
|
|
long='help';short='h';type='nil'}
|
|
if not short.h then short.h = default_help end
|
|
if not long.help then long.help = default_help end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Helper function for options parsing. Execute the attached action and/or
|
|
-- register the config in cfg.
|
|
--
|
|
-- * cfg is the table which registers the options
|
|
-- * dict the name->config entry hash table that describes options
|
|
-- * flag is the prefix '-', '--' or '+'
|
|
-- * opt is the option name
|
|
-- * i the current index in the arguments list
|
|
-- * args is the arguments list
|
|
-----------------------------------------------------------------------------
|
|
local function actionate(cfg, dict, flag, opt, i, args)
|
|
local entry = dict[opt]
|
|
if not entry then print_usage ("invalid option "..flag..opt); return false; end
|
|
local etype, name = entry.type, entry.name or entry.long or entry.short
|
|
match etype with
|
|
| 'string' | 'number' | 'string*' | 'number*' ->
|
|
if flag=='+' or flag=='++' then
|
|
print_usage ("flag "..flag.." is reserved for boolean options, not for "..opt)
|
|
return false
|
|
end
|
|
local arg = args[i+1]
|
|
if not arg then
|
|
print_usage ("missing parameter for option "..flag..opt)
|
|
return false
|
|
end
|
|
if etype:strmatch '^number' then
|
|
arg = tonumber(arg)
|
|
if not arg then
|
|
print_usage ("option "..flag..opt.." expects a number argument")
|
|
end
|
|
end
|
|
if etype:strmatch '%*$' then
|
|
if not cfg[name] then cfg[name]={ } end
|
|
table.insert(cfg[name], arg)
|
|
else cfg[name] = arg end
|
|
if entry.action then entry.action(arg) end
|
|
return i+2
|
|
| 'boolean' ->
|
|
local arg = flag=='-' or flag=='--'
|
|
cfg[name] = arg
|
|
if entry.action then entry.action(arg) end
|
|
return i+1
|
|
| 'nil' ->
|
|
cfg[name] = true;
|
|
if entry.action then entry.action() end
|
|
return i+1
|
|
| '*' ->
|
|
local arg = table.isub(args, i+1, #args)
|
|
cfg[name] = arg
|
|
if entry.action then entry.action(arg) end
|
|
return #args+1
|
|
| _ -> assert( false, 'undetected bad type for clopts action')
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Parse a list of commands: the resulting function
|
|
-----------------------------------------------------------------------------
|
|
local function parse(...)
|
|
local cfg = { }
|
|
if not ... then return cfg end
|
|
local args = type(...)=='table' and ... or {...}
|
|
local i, i_max = 1, #args
|
|
while i <= i_max do
|
|
local arg, flag, opt, opts = args[i]
|
|
--printf('beginning of loop: i=%i/%i, arg=%q', i, i_max, arg)
|
|
if arg=='-' then
|
|
i=actionate (cfg, short, '-', '', i, args)
|
|
-{ `Goto 'continue' }
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- double dash option
|
|
-----------------------------------------------------------------------
|
|
flag, opt = arg:strmatch "^(%-%-)(.*)"
|
|
if opt then
|
|
i=actionate (cfg, long, flag, opt, i, args)
|
|
-{ `Goto 'continue' }
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- double plus option
|
|
-----------------------------------------------------------------------
|
|
flag, opt = arg:strmatch "^(%+%+)(.*)"
|
|
if opt then
|
|
i=actionate (cfg, long, flag, opt, i, args)
|
|
-{ `Goto 'continue' }
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- single plus or single dash series of short options
|
|
-----------------------------------------------------------------------
|
|
flag, opts = arg:strmatch "^([+-])(.+)"
|
|
if opts then
|
|
local j_max, i2 = opts:len()
|
|
for j = 1, j_max do
|
|
opt = opts:sub(j,j)
|
|
--printf ('parsing short opt %q', opt)
|
|
i2 = actionate (cfg, short, flag, opt, i, args)
|
|
if i2 ~= i+1 and j < j_max then
|
|
error ('short option '..opt..' needs a param of type '..short[opt])
|
|
end
|
|
end
|
|
i=i2
|
|
-{ `Goto 'continue' }
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- handler for non-option parameter
|
|
-----------------------------------------------------------------------
|
|
if param_func then param_func(args[i]) end
|
|
if cfg.params then table.insert(cfg.params, args[i])
|
|
else cfg.params = { args[i] } end
|
|
i=i+1
|
|
|
|
-{ `Label 'continue' }
|
|
if not i then return false end
|
|
end -- </while>
|
|
return cfg
|
|
end
|
|
|
|
return parse
|
|
end
|
|
|
|
|