summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOpportunity <[email protected]>2020-01-15 11:24:36 +0800
committerOpportunity <[email protected]>2020-01-15 11:24:36 +0800
commit9862d1b95fa5978eaf24d65f1ca5f151d410a14b (patch)
tree2cac28495cfc70402a60d1b244dc4a801d484eb5
parenteb1effdccd96425375af083d45a94a790f0c6d56 (diff)
Improve options parsing
-rw-r--r--tests/modules/cli/test.lua139
-rw-r--r--xmake/core/base/cli.lua122
-rw-r--r--xmake/core/base/colors.lua64
-rw-r--r--xmake/core/base/dump.lua2
-rw-r--r--xmake/core/base/option.lua508
-rw-r--r--xmake/core/base/utils.lua10
-rw-r--r--xmake/core/main.lua40
-rw-r--r--xmake/core/sandbox/modules/import/core/base/cli.lua38
-rw-r--r--xmake/core/sandbox/modules/import/core/base/hashset.lua8
-rw-r--r--xmake/core/sandbox/modules/import/core/base/option.lua4
-rw-r--r--xmake/modules/private/utils/complete.lua2
-rw-r--r--xmake/plugins/lua/xmake.lua28
12 files changed, 552 insertions, 413 deletions
diff --git a/tests/modules/cli/test.lua b/tests/modules/cli/test.lua
new file mode 100644
index 000000000..8b3fb58b4
--- /dev/null
+++ b/tests/modules/cli/test.lua
@@ -0,0 +1,139 @@
+import("core.base.cli")
+
+function test_args(t)
+ local parsed = cli.parse("abc def")
+ t:are_equal(#parsed, 2)
+ t:are_equal(parsed[1].type, "arg")
+ t:are_equal(parsed[1].value, "abc")
+ t:are_equal(parsed[2].type, "arg")
+ t:are_equal(parsed[2].value, "def")
+end
+
+function test_args_escaped(t)
+ local parsed = cli.parse([[a\\bc "def \"g"]])
+ t:are_equal(#parsed, 2)
+ t:are_equal(parsed[1].type, "arg")
+ t:are_equal(parsed[1].value, "a\\bc")
+ t:are_equal(parsed[2].type, "arg")
+ t:are_equal(parsed[2].value, "def \"g")
+end
+
+function test_long(t)
+ local parsed = cli.parse([[--long-flag --long-option="1 3" --long-option:=2 args]])
+ t:are_equal(#parsed, 4)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "long-flag")
+ t:are_equal(parsed[2].type, "option")
+ t:are_equal(parsed[2].key, "long-option")
+ t:are_equal(parsed[2].value, "1 3")
+ t:are_equal(parsed[3].type, "option")
+ t:are_equal(parsed[3].key, "long-option")
+ t:are_equal(parsed[3].value, "=2")
+end
+
+function test_raw(t)
+ local parsed = cli.parse([[--long-flag -- --long-option="1 3" --long-option:=2 args -rx]])
+ t:are_equal(#parsed, 6)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "long-flag")
+ t:are_equal(parsed[2].type, "sep")
+ t:are_equal(parsed[3].type, "arg")
+ t:are_equal(parsed[3].value, "--long-option=1 3")
+ t:are_equal(parsed[4].type, "arg")
+ t:are_equal(parsed[4].value, "--long-option:=2")
+ t:are_equal(parsed[5].type, "arg")
+ t:are_equal(parsed[5].value, "args")
+ t:are_equal(parsed[6].type, "arg")
+ t:are_equal(parsed[6].value, "-rx")
+end
+
+function test_short1(t)
+ local parsed = cli.parse([[-rx args -args]], {})
+ t:are_equal(#parsed, 3)
+ t:are_equal(parsed[1].type, "option")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[1].value, "x")
+ t:are_equal(parsed[3].type, "arg")
+ t:are_equal(parsed[3].value, "-args")
+end
+
+function test_short2(t)
+ local parsed = cli.parse([[-r x args args]], {})
+ t:are_equal(#parsed, 3)
+ t:are_equal(parsed[1].type, "option")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[1].value, "x")
+end
+
+function test_short3(t)
+ local parsed = cli.parse([[-r"x d" args args]], {})
+ t:are_equal(#parsed, 3)
+ t:are_equal(parsed[1].type, "option")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[1].value, "x d")
+end
+
+function test_short4(t)
+ local parsed = cli.parse([["-rx d" args args]], {})
+ t:are_equal(#parsed, 3)
+ t:are_equal(parsed[1].type, "option")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[1].value, "x d")
+end
+
+function test_short5(t)
+ local parsed = cli.parse([[-r "x d" args args]], {})
+ t:are_equal(#parsed, 3)
+ t:are_equal(parsed[1].type, "option")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[1].value, "x d")
+end
+
+
+function test_short_flags1(t)
+ local parsed = cli.parse([[-rx args args]], {"r"})
+ t:are_equal(#parsed, 3)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[2].type, "option")
+ t:are_equal(parsed[2].key, "x")
+ t:are_equal(parsed[2].value, "args")
+end
+
+function test_short_flags2(t)
+ local parsed = cli.parse([[-r x args args]], {"r"})
+ t:are_equal(#parsed, 4)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[2].type, "arg")
+ t:are_equal(parsed[2].value, "x")
+end
+
+function test_short_flags3(t)
+ local parsed = cli.parse([[-r"x d" args args]], {"r"})
+ t:are_equal(#parsed, 4)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[2].type, "option")
+ t:are_equal(parsed[2].key, "x")
+ t:are_equal(parsed[2].value, " d")
+end
+
+function test_short_flags4(t)
+ local parsed = cli.parse([["-rx d" args args]], {"r"})
+ t:are_equal(#parsed, 4)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[2].type, "option")
+ t:are_equal(parsed[2].key, "x")
+ t:are_equal(parsed[2].value, " d")
+end
+
+function test_short_flags5(t)
+ local parsed = cli.parse([[-r "x d" args args]], {"r"})
+ t:are_equal(#parsed, 4)
+ t:are_equal(parsed[1].type, "flag")
+ t:are_equal(parsed[1].key, "r")
+ t:are_equal(parsed[2].type, "arg")
+ t:are_equal(parsed[2].value, "x d")
+end \ No newline at end of file
diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua
new file mode 100644
index 000000000..3e78fcdbc
--- /dev/null
+++ b/xmake/core/base/cli.lua
@@ -0,0 +1,122 @@
+--!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-2020, 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
+
+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', #key == 1 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', #key == 1 and ('-' .. key .. value) or ('--' .. key .. '=' .. value), argv, argi, { key = key, value = value, short = short or false })
+end
+
+function cli.parse(args, ...)
+ return cli.parsev(os.argv(args), ...)
+end
+
+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)
+ 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
+
+cli._segment = segment
+-- return module
+return cli \ No newline at end of file
diff --git a/xmake/core/base/colors.lua b/xmake/core/base/colors.lua
index d74a0c411..3dff114b5 100644
--- a/xmake/core/base/colors.lua
+++ b/xmake/core/base/colors.lua
@@ -442,6 +442,70 @@ function colors.ignore(str)
return colors.translate(str, {plain = true})
end
+-- make a table with colors
+--
+-- @param data table data, array of array of strings with colors, eg: {{"1", "2", "3"}, {"4", "5", "6"}}
+-- @param opt options
+-- plain: false
+-- sep: table colunm sepertor, default is ' | '
+-- colunms: colunm attributes, eg: {{align = 'left', min_width = 80}, {align = 'center'}, {align = 'right', min_width = 120}}
+function colors.table(data, opt)
+
+ assert(data)
+
+ local opt = opt or {}
+ if opt.sep == nil then opt.sep = ' | ' end
+ opt.patch_reset = true
+ opt.ignore_unknown = true
+ local sep = colors.translate(opt.sep, opt)
+
+ local tab = {}
+ local col_width = {}
+ -- 'l' 'r' 'c'
+ local col_align = {}
+
+ if opt.colunms then
+ for i = 1, table.maxn(opt.colunms or {}) do
+ local v = opt.colunms[i] or {}
+ col_width[i] = v.min_width or 0
+ col_align[i] = (v.align or 'l'):sub(1, 1):lower()
+ end
+ end
+
+ for i, row in ipairs(data) do
+ tab[i] = {}
+ for j, cell in ipairs(row) do
+ local str = tostring(cell)
+ local value = colors.translate(str, opt)
+ local len = opt.plain and #value or #colors.ignore(str)
+ tab[i][j] = { str = value, len = len }
+ col_width[j] = math.max(len, col_width[j] or 0)
+ end
+ end
+
+ local empty_cell = { str = "", len = 0 }
+ local rows = {}
+ for i, row in ipairs(tab) do
+ local cells = {}
+ for j = 1, #col_width do
+ local cell = row[j] or empty_cell
+ if col_align[j] == 'r' then
+ cells[j] = string.rep(' ', col_width[j] - cell.len) .. cell.str
+ elseif col_align[j] == 'c' then
+ local padding = col_width[j] - cell.len
+ local lp = math.floor(padding / 2)
+ local rp = math.ceil(padding / 2)
+ cells[j] = string.rep(' ', lp) .. cell.str .. string.rep(' ', rp)
+ else
+ cells[j] = cell.str .. string.rep(' ', col_width[j] - cell.len)
+ end
+ end
+ rows[i] = table.concat(cells, sep)
+ end
+ rows[#rows + 1] = ""
+ return table.concat(rows, '\n')
+end
+
-- get theme
function colors.theme()
return colors._THEME
diff --git a/xmake/core/base/dump.lua b/xmake/core/base/dump.lua
index 5b61ba276..3adb54133 100644
--- a/xmake/core/base/dump.lua
+++ b/xmake/core/base/dump.lua
@@ -278,7 +278,7 @@ function dump._print_table(value, first_indent, remain_indent, printed_set)
printed_set, first_level = dump._get_printed_set(printed_set, value)
io.write(first_indent)
local metatable = debug.getmetatable(value)
- local tostringmethod = metatable and rawget(metatable, "__tostring")
+ local tostringmethod = metatable and (rawget(metatable, "__todisplay") or rawget(metatable, "__tostring"))
if not first_level and tostringmethod then
local ok, strrep = pcall(tostringmethod, value, value)
if ok then
diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua
index dd9df2aa8..8e5377667 100644
--- a/xmake/core/base/option.lua
+++ b/xmake/core/base/option.lua
@@ -22,9 +22,12 @@
local option = option or {}
-- load modules
+local cli = require("base/cli")
local table = require("base/table")
local colors = require("base/colors")
+local dump = require("base/dump")
+
-- ifelse, a? b : c
function option._ifelse(a, b, c)
if a then return b else return c end
@@ -180,377 +183,111 @@ function option.init(menu)
local context = option.save()
assert(context)
- -- parse _ARGV
- local argv = xmake._ARGV
- local argkv_end = false
- local _iter, _s, _k = ipairs(argv)
- while true do
-
- -- the idx and arg
- local idx, arg = _iter(_s, _k)
-
- -- end?
- _k = idx
- if idx == nil then break end
-
- -- parse key and value
- local key, value
- local i = arg:find("=", 1, true)
-
- -- key=value?
- if i and not argkv_end then
- key = arg:sub(1, i - 1)
- value = arg:sub(i + 1)
- -- only key?
- else
- key = arg
- value = true
- end
+ -- parse _ARGV
+ local argv = table.copy(xmake._ARGV)
+ local task_arg = "build"
+ if argv[1] and not argv[1]:startswith('-') then
+ -- regard it as command name
+ task_arg = argv[1]
+ table.remove(argv, 1)
+ end
- -- --key?
- local prefix = 0
- if not argkv_end and key:startswith("--") then
- key = key:sub(3)
- prefix = 2
- -- -kvalue?
- elseif not argkv_end and key:startswith("-") and #key > 2 then
- value = key:sub(3)
- key = key:sub(2, 2)
- prefix = 1
- -- -k?
- elseif not argkv_end and key:startswith("-") then
- key = key:sub(2)
- prefix = 1
- end
+ -- find the current task
+ for taskname, taskinfo in pairs(main.tasks) do
- -- check key
- if prefix and #key == 0 then
- option.show_menu(context.taskname)
- return false, "invalid option: " .. arg
+ -- ok?
+ if taskname == task_arg or taskinfo.shortname == task_arg then
+ -- save this task
+ context.taskname = taskname
+ break
end
+ end
- -- --key=value or -kvalue or -k value or -k?
- if prefix ~= 0 then
-
- -- find this option
- local opt = nil
- local longname = nil
- for _, o in ipairs(option.taskmenu().options) do
-
- -- check
- assert(o)
-
- -- the short name
- local shortname = o[1]
-
- -- the long name
- longname = o[2]
-
- -- --key?
- if prefix == 2 and key == longname then
- opt = o
- break
- -- k?
- elseif prefix == 1 and key == shortname then
- opt = o
- break
- end
- end
-
- -- not found?
- if not opt then
- option.show_menu(context.taskname)
- return false, "invalid option: " .. arg
- end
-
- -- -k value or -kvalue? continue to get the value
- if prefix == 1 and opt[3] == "kv" then
- if type(value) ~= "string" then
- idx, arg = _iter(_s, _k)
- _k = idx
- if idx == nil or (arg:startswith("-") and not arg:find("%s")) then
- option.show_menu(context.taskname)
- return false, "invalid option: " .. option._ifelse(idx, arg, key)
- end
- value = arg
- end
- end
-
- -- check mode
- if (opt[3] == "k" and type(value) ~= "boolean") or (opt[3] == "kv" and type(value) ~= "string") then
- option.show_menu(context.taskname)
- return false, "invalid option: " .. arg
- end
-
- -- value is "true" or "false", translate it
- value = option.boolean(value)
-
- -- save option
- context.options[longname] = value
-
- -- task?
- elseif idx == 1 then
-
- -- find the current task
- for taskname, taskinfo in pairs(main.tasks) do
-
- -- ok?
- if taskname == key or taskinfo.shortname == key then
- -- save this task
- context.taskname = taskname
- break
- end
- end
-
- -- not found?
- if not context.taskname or not menu[context.taskname] then
-
- -- print the main menu
- option.show_main()
-
- -- invalid task
- return false, "invalid task: " .. key
- end
-
- -- value?
- else
-
- -- stop to parse key-value arguments
- argkv_end = true
-
- -- find a value option with name
- local opt = nil
- for _, o in ipairs(option.taskmenu().options) do
-
- -- the mode
- local mode = o[3]
-
- -- the name
- local name = o[2]
-
- -- check
- assert(o and ((mode ~= "v" and mode ~= "vs") or name))
-
- -- is value and with name?
- if mode == "v" and name and not context.options[name] then
- opt = o
- break
- -- is values and with name?
- elseif mode == "vs" and name then
- opt = o
- break
- end
- end
-
- -- ok? save this value with name opt[2]
- if opt then
-
- -- the mode
- local mode = opt[3]
-
- -- the name
- local name = opt[2]
-
- -- save value
- if mode == "v" then
- context.options[name] = key
- elseif mode == "vs" then
- -- the option
- local o = context.options[name]
- if not o then
- context.options[name] = {}
- o = context.options[name]
- end
+ -- not found?
+ if not context.taskname or not menu[context.taskname] then
- -- append value
- table.insert(o, key)
- end
- else
-
- -- print menu
- option.show_menu(context.taskname)
+ -- print the main menu
+ option.show_main()
- -- invalid option
- return false, "invalid option: " .. arg
- end
- end
+ -- invalid task
+ return false, "invalid task: " .. task_arg
end
- -- init the default value
- for _, o in ipairs(table.wrap(option.taskmenu().options)) do
-
- -- the long name
- local longname = o[2]
+ local options = table.wrap(option.taskmenu().options)
- -- key=value?
- if o[3] == "kv" then
+ -- parse remain parts
+ local results, err = option.parse(argv, options, {populate_defaults = false})
+ if not results then
+ option.show_menu(context.taskname)
+ return false, err
+ end
- -- the key
- local key = longname or o[1]
- assert(key)
+ -- finish parsing
+ context.options = results
- -- save the default value
- context.defaults[key] = o[4]
- -- value with name?
- elseif o[3] == "v" and longname then
- -- save the default value
- context.defaults[longname] = o[4]
- end
- end
+ -- init the default value
+ option.populate_defaults(options, context.defaults)
-- ok
return true
end
--- find the value of a given name from the arguments
--- only for kv mode and need not check it using menu
---
-function option.find(argv, name, shortname)
-
- -- check
- assert(argv and (name or shortname))
-
- -- find it
- local nextvalue = false
- for _, arg in ipairs(argv) do
-
- -- get this value
- if nextvalue then return arg end
-
- -- --name=value?
- if name and arg:startswith("--" .. name .. "=") then
-
- -- get value
- local i = arg:find("=", 1, true)
- if i then return arg:sub(i + 1) end
-
- -- -shortname value?
- elseif shortname and arg == ("-" .. shortname) then
-
- -- get value
- nextvalue = true
- end
- end
-end
-
-- parse arguments with the given options
-function option.parse(argv, options)
+function option.parse(argv, options, opt)
-- check
assert(argv and options)
+ opt = opt or { populate_defaults = true }
-- parse arguments
local results = {}
- local argkv_end = false
- local _iter, _s, _k = ipairs(argv)
- while true do
-
- -- the idx and arg
- local idx, arg = _iter(_s, _k)
-
- -- end?
- _k = idx
- if idx == nil then break end
+ local flags = {}
+ for _, o in ipairs(options) do
- -- parse key and value
- local key, value
- local i = arg:find("=", 1, true)
+ -- the mode
+ local mode = o[3]
- -- key=value?
- if i and not argkv_end then
- key = arg:sub(1, i - 1)
- value = arg:sub(i + 1)
- -- only key?
- else
- key = arg
- value = true
- end
+ -- the name
+ local name = o[2]
- -- --key?
- local prefix = 0
- if not argkv_end and key:startswith("--") then
- key = key:sub(3)
- prefix = 2
- -- -kvalue?
- elseif not argkv_end and key:startswith("-") and #key > 2 then
- value = key:sub(3)
- key = key:sub(2, 2)
- prefix = 1
- -- -k?
- elseif not argkv_end and key:startswith("-") then
- key = key:sub(2)
- prefix = 1
- end
+ -- check
+ assert(o and ((mode ~= "v" and mode ~= "vs") or name))
- -- check key
- if prefix and #key == 0 then
- return nil, "invalid option: " .. arg
+ -- fill short flags
+ if o[3] == 'k' and o[1] then
+ table.insert(flags, o[1])
end
+ end
- -- --key=value or -kvalue or -k value or -k?
- if prefix ~= 0 then
-
- -- find this option
- local opt = nil
- local longname = nil
- for _, o in ipairs(options) do
-
- -- check
- assert(o)
-
- -- the short name
- local shortname = o[1]
+ -- run parser
+ local pargs = cli.parsev(argv, flags)
- -- the long name
- longname = o[2]
+ -- save parse results
+ for i, arg in ipairs(pargs) do
+ if arg.type == "option" or arg.type == "flag" then
- -- --key?
- if prefix == 2 and key == longname then
- opt = o
- break
- -- k?
- elseif prefix == 1 and key == shortname then
- opt = o
+ -- find option or flag
+ local name_idx = arg.short and 1 or 2
+ local match_opt = nil
+ for _, o in pairs(options) do
+ local name = o[name_idx]
+ if name == arg.key then
+ match_opt = o
break
end
end
- -- not found?
- if not opt then
- return nil, "invalid option: " .. arg
- end
-
- -- -k value or -kvalue? continue to get the value
- if prefix == 1 and opt[3] == "kv" then
- if type(value) ~= "string" then
- idx, arg = _iter(_s, _k)
- _k = idx
- if idx == nil or (arg:startswith("-") and not arg:find("%s")) then
- return nil, "invalid option: " .. option._ifelse(idx, arg, key)
- end
- value = arg
- end
- end
-
- -- check mode
- if (opt[3] == "k" and type(value) ~= "boolean") or (opt[3] == "kv" and type(value) ~= "string") then
- return nil, "invalid option: " .. arg
- end
-
- -- value is "true" or "false", translate it
- value = option.boolean(value)
-
-- save option
- results[longname] = value
-
- -- value?
- else
+ if match_opt and ((arg.type == "option" and match_opt[3] ~= "k") or (arg.type == "flag" and match_opt[3] == "k")) then
+ results[match_opt[2] or match_opt[1]] = option.boolean(arg.value)
+ else
+ return nil, string.format("Invalid %s: %s", arg.type, arg)
+ end
- -- stop to parse key-value arguments
- argkv_end = true
+ elseif arg.type == "arg" then
-- find a value option with name
- local opt = nil
+ local match_opt = nil
for _, o in ipairs(options) do
-- the mode
@@ -559,32 +296,29 @@ function option.parse(argv, options)
-- the name
local name = o[2]
- -- check
- assert(o and ((mode ~= "v" and mode ~= "vs") or name))
-
-- is value and with name?
if mode == "v" and name and not results[name] then
- opt = o
- break
+ match_opt = o
+ break
-- is values and with name?
elseif mode == "vs" and name then
- opt = o
+ match_opt = o
break
end
end
-- ok? save this value with name opt[2]
- if opt then
+ if match_opt then
-- the mode
- local mode = opt[3]
+ local mode = match_opt[3]
-- the name
- local name = opt[2]
+ local name = match_opt[2]
-- save value
if mode == "v" then
- results[name] = key
+ results[name] = arg.value
elseif mode == "vs" then
-- the option
local o = results[name]
@@ -594,18 +328,32 @@ function option.parse(argv, options)
end
-- append value
- table.insert(o, key)
+ table.insert(o, arg.value)
end
else
-
+
-- failed
- return nil, "invalid option: " .. arg
+ return nil, "invalid argument: " .. arg.value
end
-
end
end
-- init the default value
+ if opt.populate_defaults then
+ option.populate_defaults(options, results)
+ end
+
+ -- ok
+ return results
+end
+
+-- fill defined with option's default value, in place
+function option.populate_defaults(options, defined)
+
+ -- check
+ assert(options and defined)
+
+ -- populate the default value
for _, o in ipairs(options) do
-- the long name
@@ -614,27 +362,30 @@ function option.parse(argv, options)
-- key=value?
if o[3] == "kv" then
+ local shortname = o[1]
-- the key
- local key = longname or o[1]
+ local key = longname or shortname
assert(key)
- -- save the default value
- if results[key] == nil then
- results[key] = o[4]
+ -- move value to key if needed
+ if shortname and defined[shortname] ~= nil then
+ defined[key], defined[shortname] = defined[shortname], nil
+ end
+
+ -- save the default value
+ if defined[key] == nil then
+ defined[key] = o[4]
end
-- value with name?
elseif o[3] == "v" and longname then
- -- save the default value
- if results[longname] == nil then
- results[longname] = o[4]
+ -- save the default value
+ if defined[longname] == nil then
+ defined[longname] = o[4]
end
end
end
-
- -- ok
- return results
end
@@ -648,7 +399,7 @@ function option.taskmenu(task)
-- check
assert(option._MENU)
-
+
-- the current task
task = task or option.taskname() or "main"
@@ -749,32 +500,7 @@ function option.defaults(task)
-- get the default options for the given task
local defaults = {}
- if taskmenu then
- for _, o in ipairs(taskmenu.options) do
-
- -- the long name
- local longname = o[2]
-
- -- key=value?
- if o[3] == "kv" then
-
- -- the key
- local key = longname or o[1]
- assert(key)
-
- -- save the default value
- defaults[key] = o[4]
-
- -- value with name?
- elseif o[3] == "v" and longname then
-
- -- save the default value
- defaults[longname] = o[4]
- end
- end
- end
-
- -- ok?
+ option.populate_defaults(taskmenu.options, defaults)
return defaults
end
@@ -982,12 +708,12 @@ function option.show_main()
-- print category name
io.print("")
io.print(colors.translate(string.format("${bright}%s%ss: ", string.sub(categoryname, 1, 1):upper(), string.sub(categoryname, 2))))
-
+
-- the padding spaces
local padding = 42
-- get width of console
- local console_width = os.getwinsize()["width"]
+ local console_width = math.max(os.getwinsize().width, 80)
-- print tasks
for taskname, taskinfo in pairs(categorytask) do
@@ -999,7 +725,7 @@ function option.show_main()
else
taskline = taskline .. " "
end
-
+
-- append the task name
taskline = taskline .. taskname
@@ -1044,7 +770,7 @@ function option.show_options(options, taskname)
for _, opt in ipairs(options) do
if not opt[1] and not opt[2] then
emptyline_count = emptyline_count + 1
- else
+ else
emptyline_count = 0
end
if emptyline_count < 2 then
@@ -1067,13 +793,13 @@ function option.show_options(options, taskname)
options = printed_options
for _, opt in ipairs(options) do
- -- the following options are belong action? show sub-command section
+ -- the following options are belong action? show command section
--
- -- @see core/base/task.lua: translate menu
+ -- @see core/base/task.lua: translate menu
--
if opt.category and opt.category == "action" then
io.print("")
- io.print(colors.translate("${bright}Sub-command options (" .. taskname .. "): "))
+ io.print(colors.translate("${bright}Command options (" .. taskname .. "): "))
end
-- init the option info
@@ -1116,7 +842,7 @@ function option.show_options(options, taskname)
option_info = colors.translate("${color.menu.option.name}" .. option_info .. "${clear}")
-- get width of console
- local console_width = os.getwinsize()["width"]
+ local console_width = math.max(os.getwinsize().width, 80)
-- append the option description
local description = opt[5]
diff --git a/xmake/core/base/utils.lua b/xmake/core/base/utils.lua
index d85d1ecd9..ca13ab097 100644
--- a/xmake/core/base/utils.lua
+++ b/xmake/core/base/utils.lua
@@ -42,7 +42,7 @@ function utils.dump(...)
local info = debug.getinfo(2)
local line = info.currentline
if not line or line < 0 then line = info.linedefined end
- io.write(string.format("dump form %s %s:%s\n", info.name or "<anonymous>", info.source, line))
+ io.write(string.format("dump from %s %s:%s\n", info.name or "<anonymous>", info.source, line))
end
local values = table.pack(...)
@@ -327,5 +327,13 @@ function utils.confirm(opt)
return confirm
end
+function utils.table(data, opt)
+ utils.printf(colors.table(data, opt))
+end
+
+function utils.vtable(data, opt)
+ utils.vprintf(colors.table(data, opt))
+end
+
-- return module
return utils
diff --git a/xmake/core/main.lua b/xmake/core/main.lua
index 4aa1c0d45..a4517d0ca 100644
--- a/xmake/core/main.lua
+++ b/xmake/core/main.lua
@@ -23,6 +23,7 @@ local main = main or {}
-- load modules
local os = require("base/os")
+local cli = require("base/cli")
local log = require("base/log")
local path = require("base/path")
local utils = require("base/utils")
@@ -120,11 +121,30 @@ end
-- the init function for main
function main._init()
- -- get project directory from the argument option
- local opt_projectdir = option.find(xmake._ARGV, "project", "P")
+ local argv = table.copy(xmake._ARGV)
+ if argv[1] and not argv[1]:startswith('-') then
+ -- regard it as command name
+ table.remove(argv, 1)
+ end
+ local pargv = cli.parsev(argv)
- -- get project file from the argument option
- local opt_projectfile = option.find(xmake._ARGV, "file", "F")
+ -- get project directory and project file from the argument option
+ local opt_projectdir, opt_projectfile
+ for _, arg in ipairs(pargv) do
+ if arg.type == 'option' then
+ if (arg.short and arg.key == 'P') or arg.key == 'project' then
+ if opt_projectdir then
+ return nil, "Duplicate arguments for PROJECT"
+ end
+ opt_projectdir = arg.value
+ elseif (arg.short and arg.key == 'F') or arg.key == 'file' then
+ if opt_projectfile then
+ return nil, "Duplicate arguments for FILE"
+ end
+ opt_projectfile = arg.value
+ end
+ end
+ end
-- init the project directory
local projectdir = opt_projectdir or xmake._PROJECT_DIR
@@ -165,16 +185,22 @@ function main._init()
else
os.addenv("PATH", os.programdir())
end
+
+ return true
end
-- the main entry function
function main.entry()
- -- init
- main._init()
+ -- init
+ local ok, errors = main._init()
+ if not ok then
+ utils.error(errors)
+ return -1
+ end
-- load global configuration
- local ok, errors = global.load()
+ ok, errors = global.load()
if not ok then
utils.error(errors)
return -1
diff --git a/xmake/core/sandbox/modules/import/core/base/cli.lua b/xmake/core/sandbox/modules/import/core/base/cli.lua
new file mode 100644
index 000000000..9bc0ac133
--- /dev/null
+++ b/xmake/core/sandbox/modules/import/core/base/cli.lua
@@ -0,0 +1,38 @@
+--!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-2020, TBOOX Open Source Group.
+--
+-- @author OpportunityLiu
+-- @file cli.lua
+--
+
+-- load modules
+local cli = require("base/cli")
+
+
+-- define module
+local sandbox_cli = sandbox_cli or {}
+
+-- inherit some builtin interfaces
+for key, value in pairs(cli) do
+ if not key:startswith("_") then
+ sandbox_cli[key] = value
+ end
+end
+
+-- return module
+return sandbox_cli
+
+
diff --git a/xmake/core/sandbox/modules/import/core/base/hashset.lua b/xmake/core/sandbox/modules/import/core/base/hashset.lua
index aad22af40..fdfa54337 100644
--- a/xmake/core/sandbox/modules/import/core/base/hashset.lua
+++ b/xmake/core/sandbox/modules/import/core/base/hashset.lua
@@ -26,9 +26,11 @@ local hashset = require("base/hashset")
local sandbox_hashset = sandbox_hashset or {}
-- inherit some builtin interfaces
-sandbox_hashset.new = hashset.new
-sandbox_hashset.of = hashset.of
-sandbox_hashset.from = hashset.from
+for key, value in pairs(hashset) do
+ if not key:startswith("_") then
+ sandbox_hashset[key] = value
+ end
+end
-- return module
return sandbox_hashset
diff --git a/xmake/core/sandbox/modules/import/core/base/option.lua b/xmake/core/sandbox/modules/import/core/base/option.lua
index e51b26b03..cd00ea213 100644
--- a/xmake/core/sandbox/modules/import/core/base/option.lua
+++ b/xmake/core/sandbox/modules/import/core/base/option.lua
@@ -57,13 +57,13 @@ function sandbox_core_base_option.defaults()
end
-- parse arguments with the given options
-function sandbox_core_base_option.raw_parse(argv, options)
+function sandbox_core_base_option.raw_parse(argv, options, opt)
-- check
assert(argv and options)
-- parse it
- local results, errors = option.parse(argv, options)
+ local results, errors = option.parse(argv, options, opt)
if not results then
raise(errors)
end
diff --git a/xmake/modules/private/utils/complete.lua b/xmake/modules/private/utils/complete.lua
index 35d9c67af..a1e8480df 100644
--- a/xmake/modules/private/utils/complete.lua
+++ b/xmake/modules/private/utils/complete.lua
@@ -63,7 +63,7 @@ function _complete_option(options, segs, name)
local current_options = try
{
function()
- return option.raw_parse(segs, options)
+ return option.raw_parse(segs, options, { populate_defaults = false })
end
}
-- current options is invalid
diff --git a/xmake/plugins/lua/xmake.lua b/xmake/plugins/lua/xmake.lua
index 1fc96c724..57b2bc5f4 100644
--- a/xmake/plugins/lua/xmake.lua
+++ b/xmake/plugins/lua/xmake.lua
@@ -53,18 +53,30 @@ task("lua")
-- get script
if script then
+ local args = option.get("arguments") or {}
+ for i, value in ipairs(args) do
+ if value:startswith('@') then
+ local v, err = string.deserialize(value:sub(2))
+ if err then
+ utils.warning(err)
+ else
+ args[i] = v
+ end
+ end
+ end
+
-- import and run script
if path.extension(script) == ".lua" and os.isfile(script) then
-- run the given lua script file (xmake lua /tmp/script.lua)
vprint("running given lua script file: %s", path.relative(script))
- import(path.basename(script), {rootdir = path.directory(script), anonymous = true})(unpack(option.get("arguments") or {}))
+ import(path.basename(script), {rootdir = path.directory(script), anonymous = true})(unpack(args))
elseif os.isfile(path.join(os.scriptdir(), "scripts", script .. ".lua")) then
-- run builtin lua script (xmake lua echo "hello xmake")
vprint("running builtin lua script: %s", script)
- import("scripts." .. script, {anonymous = true})(unpack(option.get("arguments") or {}))
+ import("scripts." .. script, {anonymous = true})(unpack(args))
else
-- attempt to find the builtin module
@@ -79,13 +91,13 @@ task("lua")
if object then
-- run builtin modules (xmake lua core.xxx.xxx)
vprint("running builtin module: %s", script)
- result = object(unpack(option.get("arguments") or {}))
+ result = table.pack(object(unpack(args)))
else
-- run imported modules (xmake lua core.xxx.xxx)
vprint("running imported module: %s", script)
- result = import(script, {anonymous = true})(unpack(option.get("arguments") or {}))
+ result = table.pack(import(script, {anonymous = true})(unpack(args)))
end
- if result ~= nil then utils.dump(result) end
+ if result and result.n ~= 0 then utils.dump(unpack(result, 1, result.n)) end
end
else
-- enter interactive mode
@@ -115,9 +127,11 @@ task("lua")
" - xmake lua (enter interactive mode)",
" - xmake lua /tmp/script.lua",
" - xmake lua echo 'hello xmake'",
- " - xmake lua core.xxx.xxx",
+ " - xmake lua core.xxx.xxx",
" - xmake lua -c 'print(...)' hello xmake!" }
- , {nil, "arguments", "vs", nil, "The script arguments." }
+ , {nil, "arguments", "vs", nil, "The script arguments, use '@' to enable deserializing.",
+ "e.g.",
+ " - xmake lua lib.detect.find_tool tar @{version=true}" }
}
}