diff options
| author | Opportunity <[email protected]> | 2020-01-15 11:24:36 +0800 |
|---|---|---|
| committer | Opportunity <[email protected]> | 2020-01-15 11:24:36 +0800 |
| commit | 9862d1b95fa5978eaf24d65f1ca5f151d410a14b (patch) | |
| tree | 2cac28495cfc70402a60d1b244dc4a801d484eb5 | |
| parent | eb1effdccd96425375af083d45a94a790f0c6d56 (diff) | |
Improve options parsing
| -rw-r--r-- | tests/modules/cli/test.lua | 139 | ||||
| -rw-r--r-- | xmake/core/base/cli.lua | 122 | ||||
| -rw-r--r-- | xmake/core/base/colors.lua | 64 | ||||
| -rw-r--r-- | xmake/core/base/dump.lua | 2 | ||||
| -rw-r--r-- | xmake/core/base/option.lua | 508 | ||||
| -rw-r--r-- | xmake/core/base/utils.lua | 10 | ||||
| -rw-r--r-- | xmake/core/main.lua | 40 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/cli.lua | 38 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/hashset.lua | 8 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/option.lua | 4 | ||||
| -rw-r--r-- | xmake/modules/private/utils/complete.lua | 2 | ||||
| -rw-r--r-- | xmake/plugins/lua/xmake.lua | 28 |
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}" } } } |
