local f,A=pcall(require,"\112\108\046\115\105\112")if not f then A=require("\115\105\112")end local Z=A.match_at_start local C,O=table.insert,table.insert A.custom_pattern("\088","\040\037\097\091\037\119\095\037-\093\042\041")local function b(f)return f:gmatch("\040\091\094\n\093\042\041\n")end local function I(f)return f:gsub("\094\037\115\043","")end local function h(f)return(I(f)):gsub("\037\115\043\036","")end local function L(f,A)return f:sub(A,A)end local W={}local G,o,R,m,q,l W.callback=false local F={stdin={io.stdin;"\102\105\108\101-\105\110"};stdout={io.stdout;"\102\105\108\101-\111\117\116"},stderr={io.stderr;"\102\105\108\101-\111\117\116"}}W.show_usage_error=true function W.quit(f,A)if A=="\116\104\114\111\119"then error(f)end if f then io.stderr:write(f.."\n\n")end if not A then io.stderr:write(q)end os.exit(1)end function W.error(f,A)if not W.show_usage_error then A=true elseif W.show_usage_error=="\116\104\114\111\119"then A="\116\104\114\111\119"end W.quit(l..("\058 "..f),A)end function W.open(f,A)local Z,O=io.open(f,A)if not Z then W.error(O,true)end C(G,Z)return Z end function W.assert(f,A)if not f then W.error(A)end end local function j(f,A,Z,C)W.assert(A<=f and Z>=f,C.." \111\117\116 \111\102 \114\097\110\103\101")end local function P(f)local A=tonumber(f)if not A then W.error("\117\110\097\098\108\101 \116\111 \099\111\110\118\101\114\116 \116\111 \110\117\109\098\101\114\058 "..f)end return A end local d={}local i={string=true,number=true,["\102\105\108\101-\105\110"]="\102\105\108\101",["\102\105\108\101-\111\117\116"]="\102\105\108\101";boolean=true}local function e(f,A)if f.converter then A=f.converter(A)end if f.type=="\110\117\109\098\101\114"then A=P(A)elseif i[f.type]=="\102\105\108\101"then A=W.open(A,f.type=="\102\105\108\101-\105\110"and"\114"or"\119")elseif f.type=="\098\111\111\108\101\097\110"then return A end if f.constraint then f.constraint(A)end return A end function W.add_type(f,A,Z)d[f]={converter=A,constraint=Z}end local function M(f)W.assert(#f==1,f.."\058 \115\104\111\114\116 \112\097\114\097\109\101\116\101\114\115 \115\104\111\117\108\100 \098\101 \111\110\101 \099\104\097\114\097\099\116\101\114")end local function V(f,A)local Z,C if not A or A=="\110\117\109\098\101\114"then Z=tonumber(f)end if Z then return Z,"\110\117\109\098\101\114"elseif F[f]then local A=F[f]return A[1],A[2]else if f=="\116\114\117\101"and not A then return true,"\098\111\111\108\101\097\110"end if f:match("\094\091\"\'\093")then f=f:sub(2,-2)end local O=d[A]or{}O.type=A local b=W.show_usage_error W.show_usage_error="\116\104\114\111\119"C,Z=pcall(e,O,f)W.show_usage_error=b if C then return Z,A or"\115\116\114\105\110\103"end return f,A or"\115\116\114\105\110\103"end end function W.process_options_string(f,A)local l={}local F local P=A or _G.arg G={}o={}R={}m={}local function s(f)local A,Z=f:gsub("\094\037\046\037\046\037\046\037\115\042","")return A,Z>0 end local function T(f,A,Z)A=type(A)=="\115\116\114\105\110\103"and A:gsub("\037\087","\095")or A if not f.varargs then l[A]=Z else if not l[A]then l[A]={Z}else C(l[A],Z)end end end q=f for f,A in ipairs(P)do if A=="-\104"or A=="--\104\101\108\112"then return W.quit()end end for f in b(f)do local A={}local O,b,L,G,q f=I(f)local function l(C)return Z(C,f,A)end if l("-\036\118\123\115\104\111\114\116\125\044 --\036\111\123\108\111\110\103\125 \036")or l("-\036\118\123\115\104\111\114\116\125 \036")or l("--\036\111\123\108\111\110\103\125 \036")then if A.long then O=A.long:gsub("\091\094\037\119\037-\093","\095")if#A.rest==1 then O=O..A.rest end if A.short then R[A.short]=O end else O=A.short end if A.short and not W.slack then M(A.short)end A.rest,F=s(A.rest)elseif l("\036\060\123\110\097\109\101\125 \036")then O,q=A.name:match("\040\091\094\037\046\093\043\041\040\046\042\041")O=(O:sub(1,1)):gsub("\037\065","\095")..(O:sub(2)):gsub("\037\087","\095")F=q=="\046\046\046"C(m,O)end if A.rest then f=A.rest A={}local C local I if Z("\036\040\123\100\101\102\125 \036",f,A)or Z("\036\040\123\100\101\102\125",f,A)then local f=h(A.def)local o,R=f:match("\094\040\037\083\043\041\040\046\042\041\036")R=h(R)if o=="\111\112\116\105\111\110\097\108"then o,R=R:match("\094\040\037\083\043\041\040\046\042\041\036")R=h(R)C=true end local m if o=="\100\101\102\097\117\108\116"then m=true if R==""then W.error("\118\097\108\117\101 \109\117\115\116 \102\111\108\108\111\119 \100\101\102\097\117\108\116")end else if Z("\036\102\123\109\105\110\125\046\046\036\102\123\109\097\120\125",o,A)then local f,Z=A.min,A.max L="\110\117\109\098\101\114"G=function(A)j(A,f,Z,O)end elseif not o:match("\124")then L=o else local f=o local A="\124"..(f.."\124")L="\115\116\114\105\110\103"G=function(Z)W.assert(A:find("\124"..(Z.."\124"),1,true),"\118\097\108\117\101 \'"..(Z..("\' \110\111\116 \105\110 "..f)))end end end A.rest=R f=A.rest if m or Z("\100\101\102\097\117\108\116 \036\114\123\114\101\115\116\125",f,A)then I=A.rest b,L=V(A.rest,L)end else b=false L="\098\111\111\108\101\097\110"end local R={type=L,defval=b;defval_str=I;required=b==nil and not C,comment=A.rest or O,constraint=G,varargs=F}F=nil if d[L]then local f=d[L].converter if type(f)=="\115\116\114\105\110\103"then R.type=f else R.converter=f end R.constraint=d[L].constraint elseif not i[L]and L then W.error(L.." \105\115 \117\110\107\110\111\119\110 \116\121\112\101")end o[O]=R end end local z=1 local D=1 local U=1 local n,N,K local H=false local function v(f)local A=f:find("\091\061\058\093")if A then O(P,U+1,f:sub(A+1))f=f:sub(1,A-1)end return f,A end local function k(f)return o[R[f]or f]end while U<=#P do local f=P[U]local A={}if f=="--"then H=true z=#m+1 U=U+1 f=P[U]if not f then break end end if not H and(Z("--\036\083\123\108\111\110\103\125",f,A)or Z("-\036\083\123\115\104\111\114\116\125",f,A))then if A.long then n=v(A.long)elseif#A.short==1 or k(A.short)then n=A.short else local f,Z=v(A.short)if not Z then n=L(f,1)local A=k(n)if A and A.type~="\098\111\111\108\101\097\110"then O(P,U+1,f:sub(2))else for A=2,#f,1 do O(P,(U+A)-1,"-"..L(f,A))end end else n=f end end if R[n]then n=R[n]end if not o[n]and(n=="\104"or n=="\104\101\108\112")then W.quit()end else n=m[z]if not n then n=D N={type="\115\116\114\105\110\103"}o[n]=N D=D+1 else N=o[n]end if not N.varargs then z=z+1 end K=f end N=o[n]if not N then W.error("\117\110\114\101\099\111\103\110\105\122\101\100 \112\097\114\097\109\101\116\101\114\058 "..n)end if N.type~="\098\111\111\108\101\097\110"then if not K then U=U+1 K=P[U]f=K end W.assert(K,n.." \119\097\115 \101\120\112\101\099\116\105\110\103 \097 \118\097\108\117\101")else K=not N.defval end N.used=true K=e(N,K)T(N,n,K)if i[N.type]=="\102\105\108\101"then T(N,n.."\095\110\097\109\101",f)end if W.callback then W.callback(n,f,A)end U=U+1 K=nil end for f,A in pairs(o)do if not A.used then if A.required then W.error("\109\105\115\115\105\110\103 \114\101\113\117\105\114\101\100 \112\097\114\097\109\101\116\101\114\058 "..f)end T(A,f,A.defval)if i[A.type]=="\102\105\108\101"then T(A,f.."\095\110\097\109\101",A.defval_str)end end end return l end if arg then l=arg[0]l=l or rawget(_G,"\076\065\080\080\095\083\067\082\073\080\084")or"\117\110\107\110\111\119\110"l=(l:gsub("\046\043\091\092\047\093","")):gsub("\037\046\037\097\043\036","")else l="\105\110\116\101\114"end setmetatable(W,{__call=function(f,A,Z)return W.process_options_string(A,Z)end})return W
--- Simple command-line parsing using human-readable specification.
-- Supports GNU-style parameters.
--
--      lapp = require 'pl.lapp'
--      local args = lapp [[
--      Does some calculations
--        -o,--offset (default 0.0)  Offset to add to scaled number
--        -s,--scale  (number)  Scaling factor
--        <number> (number) Number to be scaled
--      ]]
--
--      print(args.offset + args.scale * args.number)
--
-- Lines beginning with `'-'` are flags; there may be a short and a long name;
-- lines beginning with `'<var>'` are arguments.  Anything in parens after
-- the flag/argument is either a default, a type name or a range constraint.
--
-- See @{08-additional.md.Command_line_Programs_with_Lapp|the Guide}
--
-- Dependencies: `pl.sip`
-- @module pl.lapp

local status,sip = pcall(require,'pl.sip')
if not status then
    sip = require 'sip'
end
local match = sip.match_at_start
local append,tinsert = table.insert,table.insert

sip.custom_pattern('X','(%a[%w_%-]*)')

local function lines(s) return s:gmatch('([^\n]*)\n') end
local function lstrip(str)  return str:gsub('^%s+','')  end
local function strip(str)  return lstrip(str):gsub('%s+$','') end
local function at(s,k)  return s:sub(k,k) end

local lapp = {}

local open_files,parms,aliases,parmlist,usage,script

lapp.callback = false -- keep Strict happy

local filetypes = {
    stdin = {io.stdin,'file-in'}, stdout = {io.stdout,'file-out'},
    stderr = {io.stderr,'file-out'}
}

--- controls whether to dump usage on error.
-- Defaults to true
lapp.show_usage_error = true

--- quit this script immediately.
-- @string msg optional message
-- @bool no_usage suppress 'usage' display
function lapp.quit(msg,no_usage)
    if no_usage == 'throw' then
        error(msg)
    end
    if msg then
        io.stderr:write(msg..'\n\n')
    end
    if not no_usage then
        io.stderr:write(usage)
    end
    os.exit(1)
end

--- print an error to stderr and quit.
-- @string msg a message
-- @bool no_usage suppress 'usage' display
function lapp.error(msg,no_usage)
    if not lapp.show_usage_error then
        no_usage = true
    elseif lapp.show_usage_error == 'throw' then
        no_usage = 'throw'
    end
    lapp.quit(script..': '..msg,no_usage)
end

--- open a file.
-- This will quit on error, and keep a list of file objects for later cleanup.
-- @string file filename
-- @string[opt] opt same as second parameter of `io.open`
function lapp.open (file,opt)
    local val,err = io.open(file,opt)
    if not val then lapp.error(err,true) end
    append(open_files,val)
    return val
end

--- quit if the condition is false.
-- @bool condn a condition
-- @string msg message text
function lapp.assert(condn,msg)
    if not condn then
        lapp.error(msg)
    end
end

local function range_check(x,min,max,parm)
    lapp.assert(min <= x and max >= x,parm..' out of range')
end

local function xtonumber(s)
    local val = tonumber(s)
    if not val then lapp.error("unable to convert to number: "..s) end
    return val
end

local types = {}

local builtin_types = {string=true,number=true,['file-in']='file',['file-out']='file',boolean=true}

local function convert_parameter(ps,val)
    if ps.converter then
        val = ps.converter(val)
    end
    if ps.type == 'number' then
        val = xtonumber(val)
    elseif builtin_types[ps.type] == 'file' then
        val = lapp.open(val,(ps.type == 'file-in' and 'r') or 'w' )
    elseif ps.type == 'boolean' then
        return val
    end
    if ps.constraint then
        ps.constraint(val)
    end
    return val
end

--- add a new type to Lapp. These appear in parens after the value like
-- a range constraint, e.g. '<ival> (integer) Process PID'
-- @string name name of type
-- @param converter either a function to convert values, or a Lua type name.
-- @func[opt] constraint optional function to verify values, should use lapp.error
-- if failed.
function lapp.add_type (name,converter,constraint)
    types[name] = {converter=converter,constraint=constraint}
end

local function force_short(short)
    lapp.assert(#short==1,short..": short parameters should be one character")
end

-- deducing type of variable from default value;
local function process_default (sval,vtype)
    local val, success
    if not vtype or vtype == 'number' then
        val = tonumber(sval)
    end
    if val then -- we have a number!
        return val,'number'
    elseif filetypes[sval] then
        local ft = filetypes[sval]
        return ft[1],ft[2]
    else
        if sval == 'true' and not vtype then
            return true, 'boolean'
        end
        if sval:match '^["\']' then sval = sval:sub(2,-2) end

        local ps = types[vtype] or {}
        ps.type = vtype

        local show_usage_error = lapp.show_usage_error
        lapp.show_usage_error = "throw"
        success, val = pcall(convert_parameter, ps, sval)
        lapp.show_usage_error = show_usage_error
        if success then
          return val, vtype or 'string'
        end

        return sval,vtype or 'string'
    end
end

--- process a Lapp options string.
-- Usually called as `lapp()`.
-- @string str the options text
-- @tparam {string} args a table of arguments (default is `_G.arg`)
-- @return a table with parameter-value pairs
function lapp.process_options_string(str,args)
    local results = {}
    local varargs
    local arg = args or _G.arg
    open_files = {}
    parms = {}
    aliases = {}
    parmlist = {}

    local function check_varargs(s)
        local res,cnt = s:gsub('^%.%.%.%s*','')
        return res, (cnt > 0)
    end

    local function set_result(ps,parm,val)
        parm = type(parm) == "string" and parm:gsub("%W", "_") or parm -- so foo-bar becomes foo_bar in Lua
        if not ps.varargs then
            results[parm] = val
        else
            if not results[parm] then
                results[parm] = { val }
            else
                append(results[parm],val)
            end
        end
    end

    usage = str

    for _,a in ipairs(arg) do
      if a == "-h" or a == "--help" then
        return lapp.quit()
      end
    end


    for line in lines(str) do
        local res = {}
        local optparm,defval,vtype,constraint,rest
        line = lstrip(line)
        local function check(str)
            return match(str,line,res)
        end

        -- flags: either '-<short>', '-<short>,--<long>' or '--<long>'
        if check '-$v{short}, --$o{long} $' or check '-$v{short} $' or check '--$o{long} $' then
            if res.long then
                optparm = res.long:gsub('[^%w%-]','_')  -- I'm not sure the $o pattern will let anything else through?
                if #res.rest == 1 then optparm = optparm .. res.rest end
                if res.short then aliases[res.short] = optparm  end
            else
                optparm = res.short
            end
            if res.short and not lapp.slack then force_short(res.short) end
            res.rest, varargs = check_varargs(res.rest)
        elseif check '$<{name} $'  then -- is it <parameter_name>?
            -- so <input file...> becomes input_file ...
            optparm,rest = res.name:match '([^%.]+)(.*)'
            -- follow lua legal variable names
            optparm = optparm:sub(1,1):gsub('%A','_') .. optparm:sub(2):gsub('%W', '_')
            varargs = rest == '...'
            append(parmlist,optparm)
        end
        -- this is not a pure doc line and specifies the flag/parameter type
        if res.rest then
            line = res.rest
            res = {}
            local optional
            local defval_str
            -- do we have ([optional] [<type>] [default <val>])?
            if match('$({def} $',line,res) or match('$({def}',line,res) then
                local typespec = strip(res.def)
                local ftype, rest = typespec:match('^(%S+)(.*)$')
                rest = strip(rest)
                if ftype == 'optional' then
                    ftype, rest = rest:match('^(%S+)(.*)$')
                    rest = strip(rest)
                    optional = true
                end
                local default
                if ftype == 'default' then
                    default = true
                    if rest == '' then lapp.error("value must follow default") end
                else -- a type specification
                    if match('$f{min}..$f{max}',ftype,res) then
                        -- a numerical range like 1..10
                        local min,max = res.min,res.max
                        vtype = 'number'
                        constraint = function(x)
                            range_check(x,min,max,optparm)
                        end
                    elseif not ftype:match '|' then -- plain type
                        vtype = ftype
                    else
                        -- 'enum' type is a string which must belong to
                        -- one of several distinct values
                        local enums = ftype
                        local enump = '|' .. enums .. '|'
                        vtype = 'string'
                        constraint = function(s)
                            lapp.assert(enump:find('|'..s..'|', 1, true),
                              "value '"..s.."' not in "..enums
                            )
                        end
                    end
                end
                res.rest = rest
                typespec = res.rest
                -- optional 'default value' clause. Type is inferred as
                -- 'string' or 'number' if there's no explicit type
                if default or match('default $r{rest}',typespec,res) then
                    defval_str = res.rest
                    defval,vtype = process_default(res.rest,vtype)
                end
            else -- must be a plain flag, no extra parameter required
                defval = false
                vtype = 'boolean'
            end
            local ps = {
                type = vtype,
                defval = defval,
                defval_str = defval_str,
                required = defval == nil and not optional,
                comment = res.rest or optparm,
                constraint = constraint,
                varargs = varargs
            }
            varargs = nil
            if types[vtype] then
                local converter = types[vtype].converter
                if type(converter) == 'string' then
                    ps.type = converter
                else
                    ps.converter = converter
                end
                ps.constraint = types[vtype].constraint
            elseif not builtin_types[vtype] and vtype then
                lapp.error(vtype.." is unknown type")
            end
            parms[optparm] = ps
        end
    end
    -- cool, we have our parms, let's parse the command line args
    local iparm = 1
    local iextra = 1
    local i = 1
    local parm,ps,val
    local end_of_flags = false

    local function check_parm (parm)
        local eqi = parm:find '[=:]'
        if eqi then
            tinsert(arg,i+1,parm:sub(eqi+1))
            parm = parm:sub(1,eqi-1)
        end
        return parm,eqi
    end

    local function is_flag (parm)
        return parms[aliases[parm] or parm]
    end

    while i <= #arg do
        local theArg = arg[i]
        local res = {}
        -- after '--' we don't parse args and they end up in
        -- the array part of the result (args[1] etc)
        if theArg == '--' then
            end_of_flags = true
            iparm = #parmlist + 1
            i = i + 1
            theArg = arg[i]
            if not theArg then
                break
            end
        end
        -- look for a flag, -<short flags> or --<long flag>
        if not end_of_flags and (match('--$S{long}',theArg,res) or match('-$S{short}',theArg,res)) then
            if res.long then -- long option
                parm = check_parm(res.long)
            elseif #res.short == 1 or is_flag(res.short) then
                parm = res.short
            else
                local parmstr,eq = check_parm(res.short)
                if not eq then
                    parm = at(parmstr,1)
                    local flag = is_flag(parm)
                    if flag and flag.type ~= 'boolean' then
                    --if isdigit(at(parmstr,2)) then
                        -- a short option followed by a digit is an exception (for AW;))
                        -- push ahead into the arg array
                        tinsert(arg,i+1,parmstr:sub(2))
                    else
                        -- push multiple flags into the arg array!
                        for k = 2,#parmstr do
                            tinsert(arg,i+k-1,'-'..at(parmstr,k))
                        end
                    end
                else
                    parm = parmstr
                end
            end
            if aliases[parm] then parm = aliases[parm] end
            if not parms[parm] and (parm == 'h' or parm == 'help') then
                lapp.quit()
            end
        else -- a parameter
            parm = parmlist[iparm]
            if not parm then
               -- extra unnamed parameters are indexed starting at 1
               parm = iextra
               ps = { type = 'string' }
               parms[parm] = ps
               iextra = iextra + 1
            else
                ps = parms[parm]
            end
            if not ps.varargs then
                iparm = iparm + 1
            end
            val = theArg
        end
        ps = parms[parm]
        if not ps then lapp.error("unrecognized parameter: "..parm) end
        if ps.type ~= 'boolean' then -- we need a value! This should follow
            if not val then
                i = i + 1
                val = arg[i]
                theArg = val
            end
            lapp.assert(val,parm.." was expecting a value")
        else -- toggle boolean flags (usually false -> true)
            val = not ps.defval
        end
        ps.used = true
        val = convert_parameter(ps,val)
        set_result(ps,parm,val)
        if builtin_types[ps.type] == 'file' then
            set_result(ps,parm..'_name',theArg)
        end
        if lapp.callback then
            lapp.callback(parm,theArg,res)
        end
        i = i + 1
        val = nil
    end
    -- check unused parms, set defaults and check if any required parameters were missed
    for parm,ps in pairs(parms) do
        if not ps.used then
            if ps.required then lapp.error("missing required parameter: "..parm) end
            set_result(ps,parm,ps.defval)
            if builtin_types[ps.type] == "file" then
                set_result(ps, parm .. "_name", ps.defval_str)
            end
        end
    end
    return results
end

if arg then
    script = arg[0]
    script = script or rawget(_G,"LAPP_SCRIPT") or "unknown"
    -- strip dir and extension to get current script name
    script = script:gsub('.+[\\/]',''):gsub('%.%a+$','')
else
    script = "inter"
end


setmetatable(lapp, {
    __call = function(tbl,str,args) return lapp.process_options_string(str,args) end,
})


return lapp


