Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature request: simple option parsing #6

Open
ncopa opened this issue Jan 1, 2014 · 7 comments
Open

feature request: simple option parsing #6

ncopa opened this issue Jan 1, 2014 · 7 comments

Comments

@ncopa
Copy link
Contributor

ncopa commented Jan 1, 2014

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

Thanks!

@stevedonovan
Copy link
Owner

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?

@ncopa
Copy link
Contributor Author

ncopa commented Jan 28, 2014

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.

@stevedonovan
Copy link
Owner

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!

@ncopa
Copy link
Contributor Author

ncopa commented Jan 28, 2014

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
Copy link
Contributor Author

ncopa commented Jan 28, 2014

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.

@stevedonovan
Copy link
Owner

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)

@ncopa
Copy link
Contributor Author

ncopa commented Jan 28, 2014

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants