| ------------------------------------------------------------------------------- |
| -- Copyright (c) 2006-2013 Fabien Fleutot and others. |
| -- |
| -- All rights reserved. |
| -- |
| -- This program and the accompanying materials are made available |
| -- under the terms of the Eclipse Public License v1.0 which |
| -- accompanies this distribution, and is available at |
| -- http://www.eclipse.org/legal/epl-v10.html |
| -- |
| -- This program and the accompanying materials are also made available |
| -- under the terms of the MIT public license which accompanies this |
| -- distribution, and is available at http://www.lua.org/license.html |
| -- |
| -- Contributors: |
| -- Fabien Fleutot - API and implementation |
| -- |
| ------------------------------------------------------------------------------- |
| |
| -------------------------------------------------------------------------------- |
| -- 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',...) } |
| |
| local 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 ipairs(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 pairs(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 |
| |
| return clopts |