2 -- Simple command-line parsing using human-readable specification
3 -----------------------------
5 --~ local args = require ('lapp') [[
6 --~ Testing parameter handling
7 --~ -p Plain flag (defaults to false)
8 --~ -q,--quiet Plain flag with GNU-style optional long name
9 --~ -o (string) Required string option
10 --~ -n (number) Required number option
11 --~ -s (default 1.0) Option that takes a number, but will default
12 --~ <start> (number) Required number argument
13 --~ <input> (default stdin) A parameter which is an input file
14 --~ <output> (default stdout) One that is an output file
16 --~ for k,v in pairs(args) do
19 -------------------------------
20 --~ > args -pq -o help -n 2 2.3
21 --~ input file (781C1B78)
24 --~ output file (781C1B98)
29 --------------------------------
33 local append
= table.insert
41 stdin
= {io
.stdin
,'file-in'}, stdout
= {io
.stdout
,'file-out'},
42 stderr
= {io
.stderr
,'file-out'}
45 local function quit(msg
,no_usage
)
47 io
.stderr
:write(msg
..'\n\n')
50 io
.stderr
:write(usage
)
55 local function error(msg
,no_usage
)
56 quit(arg
[0]:gsub('.+[\\/]','')..':'..msg
,no_usage
)
59 local function ltrim(line
)
60 return line
:gsub('^%s*','')
63 local function rtrim(line
)
64 return line
:gsub('%s*$','')
67 local function trim(s
)
68 return ltrim(rtrim(s
))
71 local function open (file
,opt
)
72 local val
,err
= io
.open(file
,opt
)
73 if not val
then error(err
,true) end
74 append(open_files
,val
)
78 local function xassert(condn
,msg
)
84 local function range_check(x
,min,max,parm
)
85 xassert(min <= x
and max >= x
,parm
..' out of range')
88 local function xtonumber(s
)
89 local val
= tonumber(s
)
90 if not val
then error("unable to convert to number: "..s
) end
94 local function is_filetype(type)
95 return type == 'file-in' or type == 'file-out'
100 local function convert_parameter(ps
,val
)
102 val
= ps
.converter(val
)
104 if ps
.type == 'number' then
106 elseif is_filetype(ps
.type) then
107 val
= open(val
,(ps
.type == 'file-in' and 'r') or 'w' )
108 elseif ps
.type == 'boolean' then
111 if ps
.constraint
then
117 function lapp
.add_type (name
,converter
,constraint
)
118 types
[name
] = {converter
=converter
,constraint
=constraint
}
121 local function force_short(short
)
122 xassert(#short
==1,short
..": short parameters should be one character")
125 function process_options_string(str
)
129 local function check_varargs(s
)
130 local res
,cnt
= s
:gsub('%.%.%.$','')
135 local function set_result(ps
,parm
,val
)
136 if not ps
.varargs
then
139 if not res
[parm
] then
142 append(res
[parm
],val
)
149 for line
in str
:gfind('([^\n]*)\n') do
150 local optspec
,optparm
,i1
,i2
,defval
,vtype
,constraint
152 -- flags: either -<short> or -<short>,<long>
153 i1
,i2
,optspec
= line
:find('^%-(%S+)')
155 optspec
= check_varargs(optspec
)
156 local short
,long
= optspec
:match('([^,]+),(.+)')
158 optparm
= long
:sub(3)
159 aliases
[short
] = optparm
165 else -- is it <parameter_name>?
166 i1
,i2
,optparm
= line
:find('(%b<>)')
168 -- so <input file...> becomes input_file ...
169 optparm
= check_varargs(optparm
:sub(2,-2)):gsub('%A','_')
170 append(parmlist
,optparm
)
173 if i1
then -- this is not a pure doc line
176 line
= ltrim(line
:sub(i2
+1))
177 -- do we have (default <val>) or (<type>)?
178 i1
,i2
,typespec
= line
:find('^%s*(%b())')
180 typespec
= trim(typespec
:sub(2,-2)) -- trim the parens and any space
181 sval
= typespec
:match('default%s+(.+)')
183 local val
= tonumber(sval
)
184 if val
then -- we have a number!
187 elseif filetypes
[sval
] then
188 local ft
= filetypes
[sval
]
196 local min,max = typespec
:match
'([^%.]+)%.%.(.+)'
197 if min then -- it's (min..max)
201 constraint
= function(x
)
202 range_check(x
,min,max,optparm
)
204 else -- () just contains type of required parameter
208 else -- must be a plain flag, no extra parameter required
215 required
= defval
== nil,
216 comment
= line
:sub((i2
or last_i2
)+1) or optparm
,
217 constraint
= constraint
,
221 local converter
= types
[vtype
].converter
222 if type(converter
) == 'string' then
225 ps
.converter
= converter
227 ps
.constraint
= types
[vtype
].constraint
232 -- cool, we have our parms, let's parse the command line args
238 -- look for a flag, -<short flags> or --<long flag>
239 local i1
,i2
,dash
,parmstr
= arg
[i
]:find('^(%-+)(%a.*)')
240 if i1
then -- we have a flag
241 if #dash
== 2 then -- long option
244 if #parmstr
== 1 then
246 else -- multiple flags after a '-',?
247 parm
= parmstr
:sub(1,1)
248 if parmstr
:find('^%a%d+') then
249 -- a short option followed by a digit? (exception for AW ;))
250 -- push ahead into the arg array
251 table.insert(arg
,i
+1,parmstr
:sub(2))
253 -- push multiple flags into the arg array!
254 for k
= 2,#parmstr
do
255 table.insert(arg
,i
+k
-1,'-'..parmstr
:sub(k
,k
))
260 if parm
== 'h' or parm
== 'help' then
263 if aliases
[parm
] then parm
= aliases
[parm
] end
265 if not ps
then error("unrecognized parameter: "..parm
) end
266 if ps
.type ~= 'boolean' then -- we need a value! This should follow
269 xassert(val
,parm
.." was expecting a value")
272 parm
= parmlist
[iparm
]
274 -- extra unnamed parameters are indexed starting at 1
277 ps
= { type = 'string' }
281 if not ps
.varargs
then
287 val
= convert_parameter(ps
,val
)
288 set_result(ps
,parm
,val
)
289 if is_filetype(ps
.type) then
290 set_result(ps
,parm
..'_name',arg
[i
])
292 if lapp
.callback
then
293 lapp
.callback(parm
,arg
[i
],res
)
297 -- check unused parms, set defaults and check if any required parameters were missed
298 for parm
,ps
in pairs(parms
) do
300 if ps
.required
then error("missing required parameter: "..parm
) end
301 set_result(ps
,parm
,ps
.defval
)
308 __call
= function(tbl
,str
) return process_options_string(str
) end,