Microlight icon indicating copy to clipboard operation
Microlight copied to clipboard

feature request: simple option parsing

Open ncopa opened this issue 12 years ago • 7 comments

It would be nice to have some simple option parsing function included in microlight.

Thanks!

ncopa avatar Jan 01 '14 07:01 ncopa

With Microlight the really hard thing is knowing what to include ;)

Here is the simple command-line parser from Penlight (pl.app module):

--- parse command-line arguments into flags and parameters.
-- Understands GNU-style command-line flags; short (`-f`) and long (`--flag`).
-- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`);
-- note that a number value can be given without a space.
-- Multiple short args can be combined like so: ( `-abcd`).
-- @param args an array of strings (default is the global `arg`)
-- @param flags_with_values any flags that take values, e.g. <code>{out=true}</code>
-- @return a table of flags (flag=value pairs)
-- @return an array of parameters
-- @raise if args is nil, then the global `args` must be available!
function app.parse_args (args,flags_with_values)
    if not args then
        args = _G.arg
        if not args then error "Not in a main program: 'arg' not found" end
    end
    flags_with_values = flags_with_values or {}
    local _args = {}
    local flags = {}
    local i = 1
    while i <= #args do
        local a = args[i]
        local v = a:match('^-(.+)')
        local is_long
        if v then -- we have a flag
            if v:find '^-' then
                is_long = true
                v = v:sub(2)
            end
            if flags_with_values[v] then
                if i == #_args or args[i+1]:find '^-' then
                    return nil, "no value for '"..v.."'"
                end
                flags[v] = args[i+1]
                i = i + 1
            else
                -- a value can also be indicated with =
                local var,val =  utils.splitv (v,'=')
                var = var or v
                val = val or true
                if not is_long then
                    if #var > 1 then
                        if var:find '.%d+' then -- short flag, number value
                            val = var:sub(2)
                            var = var:sub(1,1)
                        else -- multiple short flags
                            for i = 1,#var do
                                flags[var:sub(i,i)] = true
                            end
                            val = nil -- prevents use of var as a flag below
                        end
                    else  -- single short flag (can have value, defaults to true)
                        val = val or true
                    end
                end
                if val then
                    flags[var] = val
                end
            end
        else
            _args[#_args+1] = a
        end
        i = i + 1
    end
    return flags,_args
end

Easy to adapt for our purposes, maybe write out utils.splitv as an explicit string.match.

But I'm not sure if it belongs in such a little library?

stevedonovan avatar Jan 05 '14 13:01 stevedonovan

lua microlight is awesome because it does not include lots of useless things, so yes, if it belongs in microlight or not is an important question.

I think it might belong there because option parsing is basically needed for every standalone Lua program in one form or other.

I miss some form of validation for valid flags too.

ncopa avatar Jan 28 '14 09:01 ncopa

That's exactly the question. It's short - and my collaborators think it should be even shorter! But Penlight is a big beast. To use pl.app, you need its dependencies. Maybe what we need is for this little 'recipe' to be a Lua snippet (http://snippets.luacode.org/). Then people can grab exactly what they need, and no more.

It's true that parsing args is something every Lua script needs to do, and it's a pain doing it by hand every time!

stevedonovan avatar Jan 28 '14 09:01 stevedonovan

Here is a function that uses the 'options' part of the usage text as validation. It only does shortopt though:

local function parse_opts(opthelp, raw_args)
        local valid_opts = {}
        local opts = {}
        local args = {}
        local moreopts = true
        for optc, separator in opthelp:gmatch("%s+%-(%a)(%s+)") do
                valid_opts[optc] = { hasarg = (separator == " ") }
        end

        local i = 1
        while i <= #raw_args do
                local a = raw_args[i]
                i = i + 1
                if a == "--" then
                        moreopts = false
                elseif moreopts and a:sub(1,1) == "-" then
                        for j = 2, #a do
                                local opt = a:sub(j,j)
                                if not valid_opts[opt] then
                                        return nil, "-"..opt, "invalid option"
                                end
                                if valid_opts[opt].hasarg then
                                        opts[opt] = raw_args[i]
                                        i = i + 1
                                else
                                        opts[opt] = true
                                end
                                if not opts[opt] then
                                        return nil, "-"..opt, "optarg required"
                                end
                        end
                else
                        args[#args + 1] = a
                end
        end
        return opts, args
end

Example usage:

opthelp = [[
 -d DIR   set directory
 -f       some flag
 -h       show this help and exit
]]

opts, args, errmsg = parse_opts(opthelp, arg)
if opts == nil then
    io.stderr:write(("%s: %s\n"):format(errmsg, args))
    io.stderr:write(("Usage: %s [OPTIONS]\n"..
        "Options:\n%s"):format(arg[0], opthelp))
    os.exit(1)
end

Not suggesting that you add this as is (support for long opts would be nice), but it would be nice if we could use parts of the help text for validation.

ncopa avatar Jan 28 '14 10:01 ncopa

But Penlight is a big beast. To use pl.app, you need its dependencies. Maybe what we need is for this little 'recipe' to be a Lua snippet

Other possibility would be to split up Penlight and you pick the parts of it you need.

ncopa avatar Jan 28 '14 10:01 ncopa

I did think of that. But most modules depend on others. I raised the possibility of making them all available as separate LuaRocks packages, for instance, but Hisham wisely felt that it would be going backward - there's already so many little Lua modules doing similar things. But what I can do is make a straightforward version available as a standalone module - still part of the Microlight project.

I've just seen your code that uses the actual options text. That's the brilliant thing about Lua, you can easily re-use data which is also meant for the human.

Have a look at how pl.lapp does it from Penlight: http://stevedonovan.github.io/Penlight/api/modules/pl.lapp.html

(lapp,lua only depends on sip.lua, because I knew someone would want this without the rest of Penlight)

stevedonovan avatar Jan 28 '14 10:01 stevedonovan

yes, I was inspired by lapp and what I am looking for is some simplified version of lapp which does not need support for various types (numbers strings) or default values.

What I think lapp does right is that the actualy help text is the source of the validation. You don't need list the valid options twice like you do with getopt(3). (one in the optstring and one in the help/usage text)

ncopa avatar Jan 28 '14 12:01 ncopa