summaryrefslogtreecommitdiff
path: root/xmake/core/base/cli.lua
blob: f9a73759f673b10f1cd895133f2c98877a8e8f76 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
-- @author      OpportunityLiu
-- @file        cli.lua
--

-- define module
local cli = cli or {}
local segment = cli._segment or {}

-- load modules
local string    = require("base/string")
local hashset   = require("base/hashset")

segment.__index = segment
cli._segment = segment

function segment:__tostring()
    return self.string
end

function segment:__todisplay()
    return string.format("${color.dump.string}%s${reset} ${color.dump.keyword}(%s)${reset}", self.string, self.type)
end

function segment:is(type)
    return self.type == type
end

function cli._make_segment(type, string, argv, argi, obj)
    obj.type = type
    obj.string = string
    obj.argv = argv
    obj.argi = argi
    return setmetatable(obj, segment)
end

function cli._make_arg(value, argv, argi)
    return cli._make_segment("arg", value, argv, argi, { value = value })
end

function cli._make_flag(key, short, argv, argi)
    return cli._make_segment("flag", short and ("-" .. key) or ("--" .. key), argv, argi, { key = key, value = true, short = short or false })
end

function cli._make_option(key, value, short, argv, argi)
    return cli._make_segment("option", short and ("-" .. key .. " " .. value) or ("--" .. key .. "=" .. value), argv, argi, { key = key, value = value, short = short or false })
end

-- parse a argv string, command & sub-command should be omitted before calling this function
-- @see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
function cli.parse(args, flags)
    return cli.parsev(os.argv(args), flags)
end

-- parse a argv array, command & sub-command should be omitted before calling this function
-- @see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
function cli.parsev(argv, flags)

    local parsed = {}
    local raw = false
    local index = 1
    local value = nil
    flags = hashset.from(flags or {})

    while index <= #argv do
        value = argv[index]
        if raw or not value:startswith("-") or #value < 2 then
            -- all args after "--" or first arg, args don"t start with "-", and short args (include a single char "-")
            raw = true
            table.insert(parsed, cli._make_arg(value, argv, index))
        elseif value == "--" then
            -- stop parsing after "--"
            raw = true
            table.insert(parsed, cli._make_segment("sep", "--", argv, index, {}))
        elseif value:startswith("--") then
            -- "--key:value", "--key=value", "--long-flag"
            local sep = value:find("[=:]", 3, false)
            -- ignore namespace, e.g. `--namespace::opt=`
            if sep and value:sub(sep, sep + 1) == "::" then
                sep = value:find("=", 3, false)
            end
            if sep then
                table.insert(parsed, cli._make_option(value:sub(3, sep - 1), value:sub(sep + 1), false, argv, index))
            else
                table.insert(parsed, cli._make_flag(value:sub(3), false, argv, index))
            end
        else
            local strp = 2
            while strp <= #value do
                local ch = value:sub(strp, strp)
                if flags:has(ch) then
                    -- is a flag
                    table.insert(parsed, cli._make_flag(ch, true, argv, index))
                else
                    -- is an option
                    if strp == #value then
                        -- is last char, use next arg as value
                        table.insert(parsed, cli._make_option(ch, argv[index + 1] or "", true, argv, index))
                        index = index + 1
                    else
                        -- is not last char, use remaining as value
                        table.insert(parsed, cli._make_option(ch, value:sub(strp + 1), true, argv, index))
                        strp = #value
                    end
                end
                strp = strp + 1
            end
        end
        index = index + 1
    end
    return parsed
end

-- return module
return cli