local P,E,C=_G.io,_G.package,_G.require local u=C("\112\108\046\117\116\105\108\115")local F=C("\112\108\046\112\097\116\104")local Z={}function Z.script_name()if _G.arg and _G.arg[0]then return _G.arg[0]end return u.raise("\078\111 \115\099\114\105\112\116 \110\097\109\101 \102\111\117\110\100")end function Z.require_here(P,C)local u=Z.script_name()if not F.isabs(u)then u=F.join(F.currentdir(),u)end if not C then local P=F.link_attrib(u)if P and P.mode=="\108\105\110\107"then P=P.target if not F.isabs(P)then P=F.join(F.dirname(u),P)end u=P end end u=F.normpath(F.dirname(u))if u:sub(-1,-1)~=F.sep then u=u..F.sep end if P then if F.is_windows then P=P:gsub("\047","\092")end if F.isabs(P)then u=P..F.sep else u=u..(P..F.sep)end end local r=F.is_windows and"\100\108\108"or"\115\111"local s=E.path:find("\094\059")and""or"\059"local G=E.cpath:find("\094\059")and""or"\059"E.path=("\037\115\063\046\108\117\097\059\037\115\063\037\115\105\110\105\116\046\108\117\097\037\115\037\115"):format(u,u,F.sep,s,E.path)E.cpath=("\037\115\063\046\037\115\037\115\037\115"):format(u,r,G,E.cpath)return u end function Z.appfile(P)local E,C=Z.script_name()if not E then return u.raise(C)end local r=F.basename(E)local s=F.splitext(r)local G=F.join(F.expanduser("~"),"\046"..s)if not F.isdir(G)then local P=F.mkdir(G)if not P then return u.raise("\099\097\110\110\111\116 \099\114\101\097\116\101 "..G)end end return F.join(G,P)end function Z.platform()if F.is_windows then return"\087\105\110\100\111\119\115"else local E=P.popen("\117\110\097\109\101")local C=E:read()if C=="\068\097\114\119\105\110"then C="\079\083\088"end E:close()return C end end function Z.lua()local P=_G.arg if not P then return u.raise("\110\111\116 \105\110 \097 \109\097\105\110 \112\114\111\103\114\097\109")end local E={}local C=-1 while true do table.insert(E,1,P[C])if not P[C-1]then return u.quote_arg(E),P[C]end C=C-1 end end function Z.parse_args(P,E,C)if not P then P=_G.arg if not P then u.raise("\078\111\116 \105\110 \097 \109\097\105\110 \112\114\111\103\114\097\109\058 \'\097\114\103\' \110\111\116 \102\111\117\110\100")end end local F={}for P,E in pairs(E or{})do if type(P)=="\110\117\109\098\101\114"then P=E end F[P]=true end local Z if not C then Z=setmetatable({},{__index=function(P,E)return E end})else Z={}for P,E in pairs(C)do if type(P)=="\110\117\109\098\101\114"then P=E end if type(E)=="\115\116\114\105\110\103"then E={E}end if type(E)=="\116\097\098\108\101"then for E,C in ipairs(E)do Z[C]=P end end Z[P]=P end do local P={}for E,C in pairs(F)do if not Z[E]then Z[E]=E P[E]=true else P[Z[E]]=true end end F=P end end for P,E in pairs(Z)do if F[E]then F[P]=true end end local r={}local s={}local G=1 while G<=#P do local E=P[G]local C=E:match("\094-\040\046\043\041")local A if not C then r[#r+1]=E else if C:find("\094-")then A=true C=C:sub(2)end if F[C]then if G==#P or P[G+1]:find("\094-")then return u.raise("\110\111 \118\097\108\117\101 \102\111\114 \'"..(C.."\'"))end s[Z[C]]=P[G+1]G=G+1 else local P,E=u.splitv(C,"\091\061\058\093",false,2)P=P or C E=E or true if not A then if#P>1 then if P:find("\046\037\100\043")then E=P:sub(2)P=P:sub(1,1)else for E=1,#P,1 do local C=P:sub(E,E)if not Z[C]then return u.raise("\117\110\107\110\111\119\110 \102\108\097\103 \'"..(C.."\'"))else C=Z[C]end s[C]=true end E=nil end else E=E or true end end if E then if not Z[P]then return u.raise("\117\110\107\110\111\119\110 \102\108\097\103 \'"..(P.."\'"))else P=Z[P]end s[P]=E end end end G=G+1 end return s,r end return Z
--- Application support functions.
-- See @{01-introduction.md.Application_Support|the Guide}
--
-- Dependencies: `pl.utils`, `pl.path`
-- @module pl.app

local io,package,require = _G.io, _G.package, _G.require
local utils = require 'pl.utils'
local path = require 'pl.path'

local app = {}

--- return the name of the current script running.
-- The name will be the name as passed on the command line
-- @return string filename
function app.script_name()
    if _G.arg and _G.arg[0] then
        return _G.arg[0]
    end
    return utils.raise("No script name found")
end

--- prefixes the current script's path to the Lua module path.
-- Applies to both the source and the binary module paths. It makes it easy for
-- the main file of a multi-file program to access its modules in the same directory.
-- `base` allows these modules to be put in a specified subdirectory, to allow for
-- cleaner deployment and resolve potential conflicts between a script name and its
-- library directory.
--
-- Note: the path is prefixed, so it is searched first when requiring modules.
-- @string base optional base directory (absolute, or relative path).
-- @bool nofollow always use the invocation's directory, even if the invoked file is a symlink
-- @treturn string the current script's path with a trailing slash
function app.require_here (base, nofollow)
    local p = app.script_name()
    if not path.isabs(p) then
        p = path.join(path.currentdir(),p)
    end
    if not nofollow then
      local t = path.link_attrib(p)
      if t and t.mode == 'link' then
        t = t.target
        if not path.isabs(t) then
          t = path.join(path.dirname(p), t)
        end
        p = t
      end
    end
    p = path.normpath(path.dirname(p))
    if p:sub(-1,-1) ~= path.sep then
        p = p..path.sep
    end
    if base then
        if path.is_windows then
            base = base:gsub('/','\\')
        end
        if path.isabs(base) then
            p = base .. path.sep
        else
            p = p..base..path.sep
        end
    end
    local so_ext = path.is_windows and 'dll' or 'so'
    local lsep = package.path:find '^;' and '' or ';'
    local csep = package.cpath:find '^;' and '' or ';'
    package.path = ('%s?.lua;%s?%sinit.lua%s%s'):format(p,p,path.sep,lsep,package.path)
    package.cpath = ('%s?.%s%s%s'):format(p,so_ext,csep,package.cpath)
    return p
end

--- return a suitable path for files private to this application.
-- These will look like '~/.SNAME/file', with '~' as with expanduser and
-- SNAME is the name of the script without .lua extension.
-- If the directory does not exist, it will be created.
-- @string file a filename (w/out path)
-- @return a full pathname, or nil
-- @return cannot create directory error
-- @usage
-- -- when run from a script called 'testapp' (on Windows):
-- local app = require 'pl.app'
-- print(app.appfile 'test.txt')
-- -- C:\Documents and Settings\steve\.testapp\test.txt
function app.appfile(file)
    local sfullname, err = app.script_name()
    if not sfullname then return utils.raise(err) end
    local sname = path.basename(sfullname)
    local name = path.splitext(sname)
    local dir = path.join(path.expanduser('~'),'.'..name)
    if not path.isdir(dir) then
        local ret = path.mkdir(dir)
        if not ret then return utils.raise('cannot create '..dir) end
    end
    return path.join(dir,file)
end

--- return string indicating operating system.
-- @return 'Windows','OSX' or whatever uname returns (e.g. 'Linux')
function app.platform()
    if path.is_windows then
        return 'Windows'
    else
        local f = io.popen('uname')
        local res = f:read()
        if res == 'Darwin' then res = 'OSX' end
        f:close()
        return res
    end
end

--- return the full command-line used to invoke this script.
-- It will not include the scriptname itself, see `app.script_name`.
-- @return command-line
-- @return name of Lua program used
-- @usage
-- -- execute:  lua -lluacov -e 'print(_VERSION)' myscript.lua
--
-- -- myscript.lua
-- print(require("pl.app").lua())  --> "lua -lluacov -e 'print(_VERSION)'", "lua"
function app.lua()
    local args = _G.arg
    if not args then
        return utils.raise "not in a main program"
    end

    local cmd = {}
    local i = -1
    while true do
        table.insert(cmd, 1, args[i])
        if not args[i-1] then
            return utils.quote_arg(cmd), args[i]
        end
        i = i - 1
    end
end

--- 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`),
-- a number value can be given without a space. If the flag is marked
-- as having a value, then a space-separated value is also accepted (`-i hello`),
-- see the `flags_with_values` argument).
--
-- Multiple short args can be combined like so: ( `-abcd`).
--
-- When specifying the `flags_valid` parameter, its contents can also contain
-- aliasses, to convert short/long flags to the same output name. See the
-- example below.
--
-- Note: if a flag is repeated, the last value wins.
-- @tparam {string} args an array of strings (default is the global `arg`)
-- @tab flags_with_values any flags that take values, either list or hash
-- table e.g. `{ out=true }` or `{ "out" }`.
-- @tab flags_valid (optional) flags that are valid, either list or hashtable.
-- If not given, everything
-- will be accepted(everything in `flags_with_values` will automatically be allowed)
-- @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!
-- @usage
-- -- Simple form:
-- local flags, params = app.parse_args(nil,
--      { "hello", "world" },  -- list of flags taking values
--      { "l", "a", "b"})      -- list of allowed flags (value ones will be added)
--
-- -- More complex example using aliasses:
-- local valid = {
--     long = "l",           -- if 'l' is specified, it is reported as 'long'
--     new = { "n", "old" }, -- here both 'n' and 'old' will go into 'new'
-- }
-- local values = {
--     "value",   -- will automatically be added to the allowed set of flags
--     "new",     -- will mark 'n' and 'old' as requiring a value as well
-- }
-- local flags, params = app.parse_args(nil, values, valid)
--
-- -- command:  myapp.lua -l --old:hello --value world param1 param2
-- -- will yield:
-- flags = {
--     long = true,     -- input from 'l'
--     new = "hello",   -- input from 'old'
--     value = "world", -- allowed because it was in 'values', note: space separated!
-- }
-- params = {
--     [1] = "param1"
--     [2] = "param2"
-- }
function app.parse_args (args,flags_with_values, flags_valid)
    if not args then
        args = _G.arg
        if not args then utils.raise "Not in a main program: 'arg' not found" end
    end

    local with_values = {}
    for k,v in pairs(flags_with_values or {}) do
        if type(k) == "number" then
            k = v
        end
        with_values[k] = true
    end

    local valid
    if not flags_valid then
        -- if no allowed flags provided, we create a table that always returns
        -- the keyname, no matter what you look up
        valid = setmetatable({},{ __index = function(_, key) return key end })
    else
        valid = {}
        for k,aliasses in pairs(flags_valid) do
            if type(k) == "number" then         -- array/list entry
                k = aliasses
            end
            if type(aliasses) == "string" then  -- single alias
                aliasses = { aliasses }
            end
            if type(aliasses) == "table" then   -- list of aliasses
                -- it's the alternate name, so add the proper mappings
                for i, alias in ipairs(aliasses) do
                    valid[alias] = k
                end
            end
            valid[k] = k
        end
        do
            local new_with_values = {}  -- needed to prevent "invalid key to 'next'" error
            for k,v in pairs(with_values) do
                if not valid[k] then
                    valid[k] = k   -- add the with_value entry as a valid one
                    new_with_values[k] = true
                else
                    new_with_values[valid[k]] = true  --set, but by its alias
                end
            end
            with_values = new_with_values
        end
    end

    -- now check that all flags with values are reported as such under all
    -- of their aliasses
    for k, main_alias in pairs(valid) do
        if with_values[main_alias] then
            with_values[k] = true
        end
    end

    local _args = {}
    local flags = {}
    local i = 1
    while i <= #args do
        local a = args[i]
        local v = a:match('^-(.+)')
        local is_long
        if not v then
            -- we have a parameter
            _args[#_args+1] = a
        else
            -- it's a flag
            if v:find '^-' then
                is_long = true
                v = v:sub(2)
            end
            if with_values[v] then
                if i == #args or args[i+1]:find '^-' then
                    return utils.raise ("no value for '"..v.."'")
                end
                flags[valid[v]] = args[i+1]
                i = i + 1
            else
                -- a value can also be indicated with = or :
                local var,val =  utils.splitv (v,'[=:]', false, 2)
                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
                                local f = var:sub(i,i)
                                if not valid[f] then
                                    return utils.raise("unknown flag '"..f.."'")
                                else
                                    f = valid[f]
                                end
                                flags[f] = 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
                    if not valid[var] then
                        return utils.raise("unknown flag '"..var.."'")
                    else
                        var = valid[var]
                    end
                    flags[var] = val
                end
            end
        end
        i = i + 1
    end
    return flags,_args
end

return app
