Skip to content

Added features #1

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
89 changes: 72 additions & 17 deletions lmod/pythonic/optparse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,18 @@ API

Create command line parser.

opt.add_options{shortflag, longflag, action=action, metavar=metavar, dest=dest, help=help}
opt.add_options{shortflag, longflag, action=action, metavar=metavar, dest=dest, help=help, default=default}

Add command line option specification. This may be called multiple times.
action: 'store'|'store_true'|'store_false'. Default is 'store'
dest: name of returned option table field. Default is the last option-name.
'-' is replaced with '_'
metavar: name used in help text
type: 'int'|'float'|'number'. Default is string

opt.parse_args() --> options, args
opt.parse_args(arglist) --> options, args

Perform argument parsing.
Perform argument parsing. arglist is optional, defaults to global varable 'arg'

DEPENDENCIES

Expand Down Expand Up @@ -85,15 +90,10 @@ LICENSE

--]]

local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.3.20111128'}
local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.4.20120827'}

local ipairs = ipairs
local unpack = unpack
local io = io
local table = table
local os = os
local arg = arg

local arg,assert,io,ipairs,math,os,table,tonumber,tostring,type,unpack
= arg,assert,io,ipairs,math,os,table,tonumber,tostring,type,unpack

local function OptionParser(t)
local usage = t.usage
Expand All @@ -108,17 +108,34 @@ local function OptionParser(t)
os.exit(1)
end

local function is_in_list(list,val)
for _,v in ipairs(list) do
if v == val then return true end
end
end

local function luatype(otype)
if (otype == 'int' or otype == 'float' or otype == 'number' ) then return 'number' end
return 'string'
end

function o.add_option(optdesc)
option_descriptions[#option_descriptions+1] = optdesc
for _,v in ipairs(optdesc) do
option_of[v] = optdesc
end
local dest = optdesc.dest or optdesc[#optdesc]:match('^%-+(.*)') -- fallback to last option
optdesc.dest = dest:gsub('-','_')
optdesc.metavar = optdesc.metavar or dest:upper()
if optdesc.default then
assert( type( optdesc.default) == luatype(optdesc.type), 'default type mismatch, option '..optdesc[#optdesc] )
end
end
function o.parse_args()
function o.parse_args(_arg)
-- expand options (e.g. "--input=file" -> "--input", "file")
local arg = {unpack(arg)}
local arg = {unpack(_arg or arg)}
for i=#arg,1,-1 do local v = arg[i]
local flag, val = v:match('^(%-%-%w+)=(.*)')
local flag, val = v:match('^(%-%-[%w_-]+)=(.*)')
if flag then
arg[i] = flag
table.insert(arg, i+1, val)
Expand All @@ -137,6 +154,22 @@ local function OptionParser(t)
i = i + 1
val = arg[i]
if not val then o.fail('option requires an argument ' .. v) end
if luatype(optdesc.type) == 'number' then
local num = tonumber(val)
if num then
if optdesc.type == 'int' and (num ~= math.floor(num)) then
o.fail(('option %s: number %s not an int'):format(v,val))
num = nil
end
else
o.fail(('option %s: %s not a %s'):format(v,val,optdesc.type))
end
val = num
end
if optdesc.choices and not is_in_list(optdesc.choices, val) then
o.fail(('illegal value for option %s: %s'):format(v,val))
val = nil
end
elseif action == 'store_true' then
val = true
elseif action == 'store_false' then
Expand All @@ -157,6 +190,12 @@ local function OptionParser(t)
io.stdout:write(t.version .. "\n")
os.exit()
end
for _,optdesc in ipairs(option_descriptions) do
local name = optdesc.dest
if optdesc.default and (options[name] == nil) then
options[name] = optdesc.default
end
end
return options, args
end

Expand All @@ -166,7 +205,7 @@ local function OptionParser(t)
for _,flag in ipairs(optdesc) do
local sflagend
if action == nil or action == 'store' then
local metavar = optdesc.metavar or optdesc.dest:upper()
local metavar = optdesc.metavar
sflagend = #flag == 2 and ' ' .. metavar
or '=' .. metavar
else
Expand All @@ -178,16 +217,32 @@ local function OptionParser(t)
end

function o.print_help()
io.stdout:write("Usage: " .. usage:gsub('%%prog', arg[0]) .. "\n")
io.stdout:write("Usage: " .. usage:gsub('%%prog', arg[0] or '') .. "\n")
io.stdout:write("\n")
io.stdout:write("Options:\n")
local maxwidth = 0
for _,optdesc in ipairs(option_descriptions) do
maxwidth = math.max(maxwidth, #flags_str(optdesc))
end
for _,optdesc in ipairs(option_descriptions) do
local help = optdesc.help or ''
if optdesc.choices or optdesc.default then
if #help>0 then help = help .. ' ' end
help = help .. '('
if optdesc.choices then
for i,v in ipairs(optdesc.choices) do
if i>1 then help = help.. '|' end
help = help..tostring(v)
end
end
if optdesc.choices and optdesc.default then help = help..' ; ' end
if optdesc.default then
help = help .. 'default: '.. tostring(optdesc.default)
end
help = help .. ')'
end
io.stdout:write(" " .. ('%-'..maxwidth..'s '):format(flags_str(optdesc))
.. optdesc.help .. "\n")
.. help .. "\n")
end
end
if t.add_help_option == nil or t.add_help_option == true then
Expand Down
57 changes: 56 additions & 1 deletion test/test.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,62 @@
package.path = './lmod/?.lua;'..package.path

arg = { '-d', '-l', 'DEBUG' , 'localhost', '60000', [0] = 'testdeamon'} -- set global arg

local OP = require 'pythonic.optparse'

-- TODO: add tests
local opt = OP.OptionParser{usage="%prog [options] address port \n", version='0.0', add_help_option=false}
opt.add_option{'-h', '--help', help = 'show this help message and exit', action='store_true' }
opt.add_option{'-d', '--daemon', help = 'Run as daemon', action='store_true' }
opt.add_option{'-l', '--log-level', choices = {'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'}, default = 'INFO', metavar='LEVEL' }
opt.add_option{ '--logfile', help = 'Name of logfile', metavar='FILE' }
opt.add_option{'-c', '--config', help = 'Name of configfile', default = '/etc/opt/test', metavar='FILE' }
opt.add_option{'-s', '--check-interval',help = 'Seconds between checking config', type='int', dest='ci', default = 15 , metavar='SEC'}
opt.add_option{ '--check-float', help = 'Check floating raft', type='float', metavar='liters'}
--opt.print_help()

local options, args = opt.parse_args() -- use global arg

assert(options.log_level=='DEBUG')
assert(options.config=='/etc/opt/test') -- default value
assert(options.daemon==true) -- -d
assert(options.logfile == nil)
assert(options.ci == 15) -- dest, default
assert(options.check_float == nil)
assert(#args == 2)
assert(args[1] == 'localhost')
assert(args[2] == '60000')

-- Next case, now passing arg as paramenter
options, args = opt.parse_args{ 'localhost', '60001', '-l', 'FATAL', '-s', '60' ,
'--config', '/home/test/config', '--logfile', '/var/log/test', '--check-float=3.14' }
assert(options.log_level=='FATAL')
assert(options.config=='/home/test/config') -- config
assert(options.logfile == '/var/log/test')
assert(options.daemon==nil)
assert(options.ci == 60) -- dest, default
assert(options.check_float == 3.14) -- float and --name=var syntax
assert(#args == 2)
assert(args[1] == 'localhost')
assert(args[2] == '60001')


-- harness for error cases:
opt.fail=error
local function check_errorcase(args, errtext)
local res, err = pcall(opt.parse_args, args)
assert(res==false, 'Illegal parameter not caught')
if errtext then
assert(err:find(errtext, nil, true), 'Unexpected error text')
else
print("'"..err.."'")
end
end

-- test error cases:
check_errorcase({ '--bogous', 'foo'}, 'invalid option --bogous')
check_errorcase({ '-l' }, 'option requires an argument -l')
check_errorcase({ '-l', 'DEADLY' }, 'illegal value for option -l: DEADLY')
check_errorcase({ '-s', '6f' }, 'option -s: 6f not a int')
check_errorcase({ '-s', '3.14' }, 'option -s: number 3.14 not an int')

print 'DONE'