1 --------------------------------------------------------------------------------
2 -- Command Line OPTionS handler
3 -- ============================
5 -- This lib generates parsers for command-line options. It encourages
6 -- the following of some common idioms: I'm pissed off by Unix tools
7 -- which sometimes will let you concatenate single letters options,
8 -- sometimes won't, will prefix long name options with simple dashes
9 -- instead of doubles, etc.
11 --------------------------------------------------------------------------------
14 -- * add a generic way to unparse options ('grab everything')
16 -- * when a short options that takes a param isn't the last element of a series
17 -- of shorts, take the remaining of the sequence as that param, e.g. -Ifoo
18 -- * let unset strings/numbers with +
19 -- * add a ++ long counterpart to +
22 -{ extension 'match' }
25 local short, long, param_func = { }, { }
26 local legal_types = table.transpose{
27 'boolean','string','number','string*','number*','nil', '*' }
29 -----------------------------------------------------------------------------
30 -- Fill short and long name indexes, and check its validity
31 -----------------------------------------------------------------------------
32 for x in ivalues(cfg) do
34 if xtype=='table' then
35 if not x.type then x.type='nil' end
36 if not legal_types[x.type] then error ("Invalid type name "..x.type) end
38 if short[x.short] then error ("multiple definitions for option "..x.short)
39 else short[x.short] = x end
42 if long[x.long] then error ("multiple definitions for option "..x.long)
43 else long[x.long] = x end
45 elseif xtype=='function' then
46 if param_func then error "multiple parameters handler in clopts"
51 -----------------------------------------------------------------------------
52 -- Print a help message, summarizing how to use the command line
53 -----------------------------------------------------------------------------
54 local function print_usage(msg)
55 if msg then print(msg,'\n') end
56 print(cfg.usage or "Options:\n")
57 for x in values(cfg) do
58 if type(x) == 'table' then
60 if x.type=='boolean' then
61 if x.short then opts = { '-'..x.short..'/+'..x.short } end
62 if x.long then table.insert (opts, '--'..x.long..'/++'..x.long) end
64 if x.short then opts = { '-'..x.short..' <'..x.type..'>' } end
65 if x.long then table.insert (opts, '--'..x.long..' <'..x.type..'>' ) end
67 printf(" %s: %s", table.concat(opts,', '), x.usage or '<undocumented>')
73 -- Unless overridden, -h and --help display the help msg
74 local default_help = { action = | | print_usage() or os.exit(0);
75 long='help';short='h';type='nil'}
76 if not short.h then short.h = default_help end
77 if not long.help then long.help = default_help end
79 -----------------------------------------------------------------------------
80 -- Helper function for options parsing. Execute the attached action and/or
81 -- register the config in cfg.
83 -- * cfg is the table which registers the options
84 -- * dict the name->config entry hash table that describes options
85 -- * flag is the prefix '-', '--' or '+'
86 -- * opt is the option name
87 -- * i the current index in the arguments list
88 -- * args is the arguments list
89 -----------------------------------------------------------------------------
90 local function actionate(cfg, dict, flag, opt, i, args)
91 local entry = dict[opt]
92 if not entry then print_usage ("invalid option "..flag..opt); return false; end
93 local etype, name = entry.type, entry.name or entry.long or entry.short
95 | 'string' | 'number' | 'string*' | 'number*' ->
96 if flag=='+' or flag=='++' then
97 print_usage ("flag "..flag.." is reserved for boolean options, not for "..opt)
100 local arg = args[i+1]
102 print_usage ("missing parameter for option "..flag..opt)
105 if etype:strmatch '^number' then
108 print_usage ("option "..flag..opt.." expects a number argument")
111 if etype:strmatch '%*$' then
112 if not cfg[name] then cfg[name]={ } end
113 table.insert(cfg[name], arg)
114 else cfg[name] = arg end
115 if entry.action then entry.action(arg) end
118 local arg = flag=='-' or flag=='--'
120 if entry.action then entry.action(arg) end
124 if entry.action then entry.action() end
127 local arg = table.isub(args, i+1, #args)
129 if entry.action then entry.action(arg) end
131 | _ -> assert( false, 'undetected bad type for clopts action')
135 -----------------------------------------------------------------------------
136 -- Parse a list of commands: the resulting function
137 -----------------------------------------------------------------------------
138 local function parse(...)
140 if not ... then return cfg end
141 local args = type(...)=='table' and ... or {...}
142 local i, i_max = 1, #args
144 local arg, flag, opt, opts = args[i]
145 --printf('beginning of loop: i=%i/%i, arg=%q', i, i_max, arg)
147 i=actionate (cfg, short, '-', '', i, args)
148 -{ `Goto 'continue' }
151 -----------------------------------------------------------------------
152 -- double dash option
153 -----------------------------------------------------------------------
154 flag, opt = arg:strmatch "^(%-%-)(.*)"
156 i=actionate (cfg, long, flag, opt, i, args)
157 -{ `Goto 'continue' }
160 -----------------------------------------------------------------------
161 -- double plus option
162 -----------------------------------------------------------------------
163 flag, opt = arg:strmatch "^(%+%+)(.*)"
165 i=actionate (cfg, long, flag, opt, i, args)
166 -{ `Goto 'continue' }
169 -----------------------------------------------------------------------
170 -- single plus or single dash series of short options
171 -----------------------------------------------------------------------
172 flag, opts = arg:strmatch "^([+-])(.+)"
174 local j_max, i2 = opts:len()
177 --printf ('parsing short opt %q', opt)
178 i2 = actionate (cfg, short, flag, opt, i, args)
179 if i2 ~= i+1 and j < j_max then
180 error ('short option '..opt..' needs a param of type '..short[opt])
184 -{ `Goto 'continue' }
187 -----------------------------------------------------------------------
188 -- handler for non-option parameter
189 -----------------------------------------------------------------------
190 if param_func then param_func(args[i]) end
191 if cfg.params then table.insert(cfg.params, args[i])
192 else cfg.params = { args[i] } end
195 -{ `Label 'continue' }
196 if not i then return false end