From aac0961cca3d6a93fe5a8102beabb803f6e4dc5a Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Sun, 26 Aug 2012 17:47:37 +0300 Subject: [PATCH 01/12] Optional dest. Options with '-'. parse_args with optional parameter optdesc.dest optional, defaulting to the last option name. Change '-' to '_' to support a.name notation in Lua Allow option name with '-' or '_' inside. parse_args() take an optional table list to parse. default to global 'arg' as before. --- lmod/pythonic/optparse.lua | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 5bde91e..0da9d08 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -44,10 +44,14 @@ API opt.add_options{shortflag, longflag, action=action, metavar=metavar, dest=dest, help=help} 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 - 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 @@ -113,12 +117,15 @@ local function OptionParser(t) 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() 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) @@ -166,7 +173,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 From 6c52645cbe1e72fbcac94cc353a200acee6679a9 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Sun, 26 Aug 2012 18:02:57 +0300 Subject: [PATCH 02/12] Updated _VERSION --- lmod/pythonic/optparse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 0da9d08..55e7aac 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -89,7 +89,7 @@ LICENSE --]] - local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.3.20111128'} + local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.3.20120826'} local ipairs = ipairs local unpack = unpack From 5267518669ab81874c5d4634a0b4adc6ee284ca7 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Sun, 26 Aug 2012 23:40:35 +0300 Subject: [PATCH 03/12] Added 'default' option --- lmod/pythonic/optparse.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 55e7aac..5892b7e 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -41,7 +41,7 @@ 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' @@ -164,6 +164,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 @@ -193,8 +199,12 @@ local function OptionParser(t) maxwidth = math.max(maxwidth, #flags_str(optdesc)) end for _,optdesc in ipairs(option_descriptions) do + local help = optdesc.help or '' + if optdesc.default then + help = help .. (' (default: %s)'):format(optdesc.default) + 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 From de15312815e42b6bc4266f7be17acaf9b62627b2 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 00:16:15 +0300 Subject: [PATCH 04/12] Added 'choices' handling --- lmod/pythonic/optparse.lua | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 5892b7e..31ac36e 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -91,13 +91,8 @@ LICENSE local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.3.20120826'} -local ipairs = ipairs -local unpack = unpack -local io = io -local table = table -local os = os -local arg = arg - +local arg,io,ipairs,math,os,table,tostring,unpack + = arg,io,ipairs,math,os,table,tostring,unpack local function OptionParser(t) local usage = t.usage @@ -112,6 +107,12 @@ 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 + function o.add_option(optdesc) option_descriptions[#option_descriptions+1] = optdesc for _,v in ipairs(optdesc) do @@ -144,6 +145,10 @@ local function OptionParser(t) i = i + 1 val = arg[i] if not val then o.fail('option requires an argument ' .. v) 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 @@ -200,8 +205,18 @@ local function OptionParser(t) end for _,optdesc in ipairs(option_descriptions) do local help = optdesc.help or '' - if optdesc.default then - help = help .. (' (default: %s)'):format(optdesc.default) + if optdesc.choices or optdesc.default then + 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: %s)'):format(optdesc.default) + end end io.stdout:write(" " .. ('%-'..maxwidth..'s '):format(flags_str(optdesc)) .. help .. "\n") From ce60041ae4e98f5f717007ec0e82dcf249d60637 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 00:23:17 +0300 Subject: [PATCH 05/12] Fixed help formatting (choice w/o default) --- lmod/pythonic/optparse.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 31ac36e..a6c3dfd 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -206,6 +206,7 @@ local function OptionParser(t) 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 @@ -215,8 +216,9 @@ local function OptionParser(t) end if optdesc.choices and optdesc.default then help = help..' ; ' end if optdesc.default then - help = help .. ('default: %s)'):format(optdesc.default) + help = help .. 'default: '.. tostring(optdesc.default) end + help = help .. ')' end io.stdout:write(" " .. ('%-'..maxwidth..'s '):format(flags_str(optdesc)) .. help .. "\n") From caf4108b09c92e23ff767cf09e3bd62680e024e3 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 12:06:42 +0300 Subject: [PATCH 06/12] Implemented numeric types. Check default type. --- lmod/pythonic/optparse.lua | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index a6c3dfd..9679ec8 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -44,10 +44,11 @@ API 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. + 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(arglist) --> options, args @@ -113,6 +114,11 @@ local function OptionParser(t) 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 @@ -121,6 +127,9 @@ local function OptionParser(t) 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(_arg) -- expand options (e.g. "--input=file" -> "--input", "file") @@ -145,6 +154,18 @@ 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 From be71310cd0687079f313519117e2886018e07c42 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 12:11:58 +0300 Subject: [PATCH 07/12] Updated version number, local declarations --- lmod/pythonic/optparse.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 9679ec8..28d9f2e 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -90,10 +90,10 @@ LICENSE --]] - local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.3.20120826'} + local M = {_TYPE='module', _NAME='pythonic.optparse', _VERSION='0.4.20120827'} -local arg,io,ipairs,math,os,table,tostring,unpack - = arg,io,ipairs,math,os,table,tostring,unpack +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 From 2b94b5bf95409facf1698155eb34086b9a33f9ee Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 12:52:01 +0300 Subject: [PATCH 08/12] dont require arg[0] --- lmod/pythonic/optparse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lmod/pythonic/optparse.lua b/lmod/pythonic/optparse.lua index 28d9f2e..378ce54 100755 --- a/lmod/pythonic/optparse.lua +++ b/lmod/pythonic/optparse.lua @@ -217,7 +217,7 @@ 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 From da987f24182237321873bd20e2b501732f2096c0 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 13:21:14 +0300 Subject: [PATCH 09/12] Added testcases --- test/test.lua | 54 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/test/test.lua b/test/test.lua index 3b15d33..599c403 100644 --- a/test/test.lua +++ b/test/test.lua @@ -1,7 +1,57 @@ package.path = './lmod/?.lua;'..package.path -local OP = require 'pythonic.optparse' +arg = { '-d', '-l', 'DEBUG' , 'localhost', '60000', [0] = 'testdeamon'} -- set global arg --- TODO: add tests +local OP = require 'optparse' + +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.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(#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' } +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(#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') print 'DONE' From 3d45d322df09885a54c562af46359550901066bb Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Mon, 27 Aug 2012 13:22:18 +0300 Subject: [PATCH 10/12] 'pythonic.optparse' --- test/test.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.lua b/test/test.lua index 599c403..13ca34d 100644 --- a/test/test.lua +++ b/test/test.lua @@ -2,7 +2,7 @@ package.path = './lmod/?.lua;'..package.path arg = { '-d', '-l', 'DEBUG' , 'localhost', '60000', [0] = 'testdeamon'} -- set global arg -local OP = require 'optparse' +local OP = require 'pythonic.optparse' 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' } From bd15e16e500c78993b9fcf033619499529ff8c01 Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Tue, 28 Aug 2012 14:32:32 +0300 Subject: [PATCH 11/12] Test float --- test/test.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test.lua b/test/test.lua index 13ca34d..517a015 100644 --- a/test/test.lua +++ b/test/test.lua @@ -11,6 +11,7 @@ opt.add_option{'-l', '--log-level', choices = {'DEBUG', 'INFO', 'WARN', 'ERR 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 @@ -20,17 +21,20 @@ 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' } +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.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') From da21f19985d2c3dbe9caec0f759e8cfe56cc261b Mon Sep 17 00:00:00 2001 From: hjelmeland Date: Tue, 28 Aug 2012 14:38:21 +0300 Subject: [PATCH 12/12] One more numeric test --- test/test.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test.lua b/test/test.lua index 517a015..b269425 100644 --- a/test/test.lua +++ b/test/test.lua @@ -57,5 +57,6 @@ 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'