From 9862d1b95fa5978eaf24d65f1ca5f151d410a14b Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 11:24:36 +0800 Subject: Improve options parsing --- tests/modules/cli/test.lua | 139 ++++++ xmake/core/base/cli.lua | 122 +++++ xmake/core/base/colors.lua | 64 +++ xmake/core/base/dump.lua | 2 +- xmake/core/base/option.lua | 508 +++++---------------- xmake/core/base/utils.lua | 10 +- xmake/core/main.lua | 42 +- .../core/sandbox/modules/import/core/base/cli.lua | 38 ++ .../sandbox/modules/import/core/base/hashset.lua | 8 +- .../sandbox/modules/import/core/base/option.lua | 4 +- xmake/modules/private/utils/complete.lua | 2 +- xmake/plugins/lua/xmake.lua | 28 +- 12 files changed, 553 insertions(+), 414 deletions(-) create mode 100644 tests/modules/cli/test.lua create mode 100644 xmake/core/base/cli.lua create mode 100644 xmake/core/sandbox/modules/import/core/base/cli.lua 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 "", info.source, line)) + io.write(string.format("dump from %s %s:%s\n", info.name or "", 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") - - -- get project file from the argument option - local opt_projectfile = option.find(xmake._ARGV, "file", "F") + 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 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}" } } } -- cgit v1.3.1 From f4e30b5e7879d4dbfbe4424f5f9156cb6cfdaf50 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 12:06:01 +0800 Subject: Improve dump --- xmake/core/base/dump.lua | 14 ++++++++++++-- xmake/core/base/utils.lua | 21 ++++++--------------- xmake/plugins/lua/xmake.lua | 10 ++++++---- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/xmake/core/base/dump.lua b/xmake/core/base/dump.lua index 3adb54133..5e7ed9b8f 100644 --- a/xmake/core/base/dump.lua +++ b/xmake/core/base/dump.lua @@ -88,17 +88,27 @@ function dump._print_function(func, as_key) end -- print value with default format -function dump._print_default(value) +function dump._print_default_scalar(value) io.write(dump._translate("${reset}${color.dump.default}"), dump._format("text.dump.default_format", "%s", value), dump._translate("${reset}")) end -- print udata value with scalar format function dump._print_udata_scalar(value) + local metatable = debug.getmetatable(value) + local tostringmethod = metatable and (rawget(metatable, "__todisplay") or rawget(metatable, "__tostring")) + if tostringmethod then + value = tostringmethod(value) + end io.write(dump._translate("${reset}${color.dump.udata}"), dump._format("text.dump.udata_format", "%s", value), dump._translate("${reset}")) end -- print table value with scalar format function dump._print_table_scalar(value) + local metatable = debug.getmetatable(value) + local tostringmethod = metatable and (rawget(metatable, "__todisplay") or rawget(metatable, "__tostring")) + if tostringmethod then + value = tostringmethod(value) + end io.write(dump._translate("${reset}${color.dump.table}"), dump._format("text.dump.table_format", "%s", value), dump._translate("${reset}")) end @@ -117,7 +127,7 @@ function dump._print_scalar(value, as_key) elseif type(value) == "table" then dump._print_table_scalar(value) else - dump._print_default(value) + dump._print_default_scalar(value) end end diff --git a/xmake/core/base/utils.lua b/xmake/core/base/utils.lua index ca13ab097..fa42d7e25 100644 --- a/xmake/core/base/utils.lua +++ b/xmake/core/base/utils.lua @@ -29,7 +29,7 @@ local log = require("base/log") local io = require("base/io") local dump = require("base/dump") --- dump value +-- dump values function utils.dump(...) if option.get("quiet") then return ... @@ -49,25 +49,16 @@ function utils.dump(...) if values.n == 0 then return end - local indent = nil - local values_count = values.n - values.n = nil - -- use last input as indent if it is a string - if values_count > 1 and type(values[values_count]) == "string" then - indent = values[values_count] - values[values_count] = nil - values_count = values_count - 1 - end - if values_count == 1 then - dump(values[1], indent or "", diagnosis) + if values.n == 1 then + dump(values[1], "", diagnosis) else - for i = 1, values_count do - dump(values[i], indent or string.format("%2d: ", i), diagnosis) + for i = 1, values.n do + dump(values[i], string.format("%2d: ", i), diagnosis) end end - return table.unpack(values, 1, values_count) + return table.unpack(values, 1, values.n) end -- print string with newline diff --git a/xmake/plugins/lua/xmake.lua b/xmake/plugins/lua/xmake.lua index 57b2bc5f4..e264ba14d 100644 --- a/xmake/plugins/lua/xmake.lua +++ b/xmake/plugins/lua/xmake.lua @@ -54,10 +54,12 @@ task("lua") if script then local args = option.get("arguments") or {} + args.n = #args for i, value in ipairs(args) do if value:startswith('@') then local v, err = string.deserialize(value:sub(2)) if err then + -- for strings that failed to deserialize, regaed it as a normal string, just show a warning message utils.warning(err) else args[i] = v @@ -70,13 +72,13 @@ task("lua") -- 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(args)) + import(path.basename(script), {rootdir = path.directory(script), anonymous = true})(table.unpack(args, 1, args.n)) 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(args)) + import("scripts." .. script, {anonymous = true})(table.unpack(args, 1, args.n)) else -- attempt to find the builtin module @@ -91,11 +93,11 @@ task("lua") if object then -- run builtin modules (xmake lua core.xxx.xxx) vprint("running builtin module: %s", script) - result = table.pack(object(unpack(args))) + result = table.pack(object(table.unpack(args, 1, args.n))) else -- run imported modules (xmake lua core.xxx.xxx) vprint("running imported module: %s", script) - result = table.pack(import(script, {anonymous = true})(unpack(args))) + result = table.pack(import(script, {anonymous = true})(table.unpack(args, 1, args.n))) end if result and result.n ~= 0 then utils.dump(unpack(result, 1, result.n)) end end -- cgit v1.3.1 From d7662a8916400389fca579772bfdaedadd5e640e Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 14:58:36 +0800 Subject: Improve basic parse --- xmake/core/base/option.lua | 68 ++++++++++++++++++++++++++++------------------ xmake/core/base/task.lua | 56 +++++++++++++++++++++++--------------- xmake/core/main.lua | 44 ++++++++++++++---------------- 3 files changed, 97 insertions(+), 71 deletions(-) diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index 8e5377667..c02a29659 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -183,40 +183,35 @@ function option.init(menu) local context = option.save() assert(context) - -- 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 - - -- find the current task - for taskname, taskinfo in pairs(main.tasks) do - - -- ok? - if taskname == task_arg or taskinfo.shortname == task_arg then - -- save this task - context.taskname = taskname - break + -- check command + if xmake._COMMAND then + + -- find the current task + for taskname, taskinfo in pairs(main.tasks) do + + -- ok? + if taskname == xmake._COMMAND or taskinfo.shortname == xmake._COMMAND then + -- save this task + context.taskname = taskname + break + end end - end - -- not found? - if not context.taskname or not menu[context.taskname] then + -- not found? + if not context.taskname or not menu[context.taskname] then - -- print the main menu - option.show_main() + -- print the main menu + option.show_main() - -- invalid task - return false, "invalid task: " .. task_arg + -- invalid task + return false, "invalid task: " .. xmake._COMMAND + end end local options = table.wrap(option.taskmenu().options) -- parse remain parts - local results, err = option.parse(argv, options, {populate_defaults = false}) + local results, err = option.parse(xmake._COMMAND_ARGV, options, { populate_defaults = false }) if not results then option.show_menu(context.taskname) return false, err @@ -281,7 +276,11 @@ function option.parse(argv, options, opt) 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) + if opt.allow_unknown then + results[arg.key] = option.boolean(arg.value) + else + return nil, string.format("Invalid %s: %s", arg.type, arg) + end end elseif arg.type == "arg" then @@ -333,6 +332,23 @@ function option.parse(argv, options, opt) else -- failed + if opt.allow_unknown then + if arg.key then + results[arg.key] = arg.value + else + -- the option + local o = results["$ARGS"] + if not o then + results["$ARGS"] = {} + o = results["$ARGS"] + end + + -- append value + table.insert(o, arg.value) + end + else + return nil, string.format("Invalid %s: %s", arg.type, arg) + end return nil, "invalid argument: " .. arg.value end end diff --git a/xmake/core/base/task.lua b/xmake/core/base/task.lua index 03b87982d..97c8cec25 100644 --- a/xmake/core/base/task.lua +++ b/xmake/core/base/task.lua @@ -32,6 +32,37 @@ local sandbox = require("sandbox/sandbox") local config = require("project/config") local sandbox_os = require("sandbox/modules/os") +function task.common_options() + if not task._COMMON_OPTIONS then + task._COMMON_OPTIONS = + { + {'q', "quiet", "k", nil, "Quiet operation." } + , {'y', "yes", "k", nil, "Input yes by default if need user confirm." } + , {nil, "confirm", "kv", nil, "Input the given result if need user confirm.", + " - y|yes", + " - n|no", + " - d|def"} + , {'v', "verbose", "k", nil, "Print lots of verbose information for users." } + , {nil, "root", "k", nil, "Allow to run xmake as root." } + , {'D', "diagnosis", "k", nil, "Print lots of diagnosis information (backtrace, check info ..) only for developers." + , "And we can append -v to get more whole information." + , " e.g. $ xmake -v -D"} + , {nil, "profile", "k", nil, "Print performance data only for developers." } + , {nil, "version", "k", nil, "Print the version number and exit." } + , {'h', "help", "k", nil, "Print this help message and exit." } + , {} + , {'F', "file", "kv", nil, "Read a given xmake.lua file." } + , {'P', "project", "kv", nil, "Change to the given project directory." + , "Search priority:" + , " 1. The Given Command Argument" + , " 2. The Envirnoment Variable: XMAKE_PROJECT_DIR" + , " 3. The Current Directory" } + , {category = "action"} + } + end + return task._COMMON_OPTIONS +end + -- the directories of tasks function task._directories() @@ -127,28 +158,9 @@ function task._translate_menu(menu) -- add common options, we need avoid repeat because the main/build task will be inserted twice if not menu._common_options then - table.insert(options, 1, {'q', "quiet", "k", nil, "Quiet operation." }) - table.insert(options, 2, {'y', "yes", "k", nil, "Input yes by default if need user confirm." }) - table.insert(options, 3, {nil, "confirm", "kv", nil, "Input the given result if need user confirm.", - " - y|yes", - " - n|no", - " - d|def"}) - table.insert(options, 4, {'v', "verbose", "k", nil, "Print lots of verbose information for users." }) - table.insert(options, 5, {nil, "root", "k", nil, "Allow to run xmake as root." }) - table.insert(options, 6, {'D', "diagnosis", "k", nil, "Print lots of diagnosis information (backtrace, check info ..) only for developers." - , "And we can append -v to get more whole information." - , " e.g. $ xmake -v -D"}) - table.insert(options, 7, {nil, "profile", "k", nil, "Print performance data only for developers." }) - table.insert(options, 8, {nil, "version", "k", nil, "Print the version number and exit." }) - table.insert(options, 9, {'h', "help", "k", nil, "Print this help message and exit." }) - table.insert(options, 10, {}) - table.insert(options, 11, {'F', "file", "kv", nil, "Read a given xmake.lua file." }) - table.insert(options, 12, {'P', "project", "kv", nil, "Change to the given project directory." - , "Search priority:" - , " 1. The Given Command Argument" - , " 2. The Envirnoment Variable: XMAKE_PROJECT_DIR" - , " 3. The Current Directory" }) - table.insert(options, 13, {category = "action"}) + for i, v in ipairs(task.common_options()) do + table.insert(options, i, v) + end menu._common_options = true end end diff --git a/xmake/core/main.lua b/xmake/core/main.lua index a4517d0ca..e0504a376 100644 --- a/xmake/core/main.lua +++ b/xmake/core/main.lua @@ -118,34 +118,32 @@ function main._find_root(projectfile) return projectfile end --- the init function for main -function main._init() +function main._basicparse() - local argv = table.copy(xmake._ARGV) - if argv[1] and not argv[1]:startswith('-') then + -- check command + if xmake._ARGV[1] and not xmake._ARGV[1]:startswith('-') then -- regard it as command name - table.remove(argv, 1) + xmake._COMMAND = xmake._ARGV[1] + xmake._COMMAND_ARGV = table.move(xmake._ARGV, 2, -1, 1, {}) + else + xmake._COMMAND_ARGV = xmake._ARGV end - local pargv = cli.parsev(argv) - -- 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 + -- parse options + local options, err = option.parse(xmake._COMMAND_ARGV, task.common_options(), { allow_unknown = true }) + if not options then + utils.error(err) end + return options.project, options.file +end + +-- the init function for main +function main._init() + + -- get project directory and project file from the argument option + local opt_projectdir, opt_projectfile = main._basicparse() + -- init the project directory local projectdir = opt_projectdir or xmake._PROJECT_DIR if projectdir and not path.is_absolute(projectdir) then @@ -166,7 +164,7 @@ function main._init() -- find the root project file if not os.isfile(projectfile) or (not opt_projectdir and not opt_projectfile) then - projectfile = main._find_root(projectfile) + projectfile = main._find_root(projectfile) end -- update and enter project -- cgit v1.3.1 From 546d83dd435780a6ea69a5b24a12c06b294dd2c0 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 15:16:08 +0800 Subject: Fix --- tests/cli/test.lua | 139 +++++++++++++++++++++++++++++++++++++++++++++ tests/modules/cli/test.lua | 139 --------------------------------------------- xmake/core/base/cli.lua | 2 +- xmake/core/base/option.lua | 5 +- xmake/core/main.lua | 2 +- 5 files changed, 142 insertions(+), 145 deletions(-) create mode 100644 tests/cli/test.lua delete mode 100644 tests/modules/cli/test.lua diff --git a/tests/cli/test.lua b/tests/cli/test.lua new file mode 100644 index 000000000..20bc0c735 --- /dev/null +++ b/tests/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 diff --git a/tests/modules/cli/test.lua b/tests/modules/cli/test.lua deleted file mode 100644 index 8b3fb58b4..000000000 --- a/tests/modules/cli/test.lua +++ /dev/null @@ -1,139 +0,0 @@ -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 index 3e78fcdbc..a76d6db23 100644 --- a/xmake/core/base/cli.lua +++ b/xmake/core/base/cli.lua @@ -119,4 +119,4 @@ end cli._segment = segment -- return module -return cli \ No newline at end of file +return cli diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index c02a29659..a1f628cdb 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -26,8 +26,6 @@ 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 @@ -347,9 +345,8 @@ function option.parse(argv, options, opt) table.insert(o, arg.value) end else - return nil, string.format("Invalid %s: %s", arg.type, arg) + return nil, "invalid argument: " .. arg.value end - return nil, "invalid argument: " .. arg.value end end end diff --git a/xmake/core/main.lua b/xmake/core/main.lua index e0504a376..d54c56228 100644 --- a/xmake/core/main.lua +++ b/xmake/core/main.lua @@ -124,7 +124,7 @@ function main._basicparse() if xmake._ARGV[1] and not xmake._ARGV[1]:startswith('-') then -- regard it as command name xmake._COMMAND = xmake._ARGV[1] - xmake._COMMAND_ARGV = table.move(xmake._ARGV, 2, -1, 1, {}) + xmake._COMMAND_ARGV = table.move(xmake._ARGV, 2, #xmake._ARGV, 1, {}) else xmake._COMMAND_ARGV = xmake._ARGV end -- cgit v1.3.1 From 51a04e4cdf53c5e33913999c5d3c48ae6198307f Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 15:21:03 +0800 Subject: clean up --- xmake/core/main.lua | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/xmake/core/main.lua b/xmake/core/main.lua index d54c56228..1078175d6 100644 --- a/xmake/core/main.lua +++ b/xmake/core/main.lua @@ -23,7 +23,6 @@ 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") @@ -130,19 +129,18 @@ function main._basicparse() end -- parse options - local options, err = option.parse(xmake._COMMAND_ARGV, task.common_options(), { allow_unknown = true }) - if not options then - utils.error(err) - end - - return options.project, options.file + return option.parse(xmake._COMMAND_ARGV, task.common_options(), { allow_unknown = true }) end -- the init function for main function main._init() -- get project directory and project file from the argument option - local opt_projectdir, opt_projectfile = main._basicparse() + local options, err = main._basicparse() + if not options then + return false, err + end + local opt_projectdir, opt_projectfile = options.project, options.file -- init the project directory local projectdir = opt_projectdir or xmake._PROJECT_DIR -- cgit v1.3.1 From 35126b5a38e9833d16c785afe2beab9d87cc3bc2 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 16:33:12 +0800 Subject: change hint --- xmake/core/base/task.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmake/core/base/task.lua b/xmake/core/base/task.lua index 97c8cec25..a3aadb7b0 100644 --- a/xmake/core/base/task.lua +++ b/xmake/core/base/task.lua @@ -46,7 +46,7 @@ function task.common_options() , {nil, "root", "k", nil, "Allow to run xmake as root." } , {'D', "diagnosis", "k", nil, "Print lots of diagnosis information (backtrace, check info ..) only for developers." , "And we can append -v to get more whole information." - , " e.g. $ xmake -v -D"} + , " e.g. $ xmake -vD"} , {nil, "profile", "k", nil, "Print performance data only for developers." } , {nil, "version", "k", nil, "Print the version number and exit." } , {'h', "help", "k", nil, "Print this help message and exit." } -- cgit v1.3.1 From 741857e2c91b67b75e763b17ccdc6909bbc41709 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 17:57:26 +0800 Subject: fix complete --- scripts/register-completions.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/register-completions.ps1 b/scripts/register-completions.ps1 index bae3281f8..f7e3e5da4 100644 --- a/scripts/register-completions.ps1 +++ b/scripts/register-completions.ps1 @@ -8,7 +8,7 @@ Register-ArgumentCompleter -Native -CommandName xmake -ScriptBlock { } $oldenv = $env:XMAKE_SKIP_HISTORY $env:XMAKE_SKIP_HISTORY = 1 - xmake lua private.utils.complete "0" "$complete" | ForEach-Object { + xmake lua private.utils.complete "0" "nospace" "$complete" | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } $env:XMAKE_SKIP_HISTORY = $oldenv -- cgit v1.3.1 From f005776c9b847595225f5e490639fcc4a159f845 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 18:07:08 +0800 Subject: fix complete --- scripts/get.sh | 4 ++-- scripts/register-completions.bash | 2 +- scripts/register-completions.ps1 | 2 +- scripts/register-completions.zsh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/get.sh b/scripts/get.sh index c57f3bf4f..5f6bde3c2 100755 --- a/scripts/get.sh +++ b/scripts/get.sh @@ -170,7 +170,7 @@ if [[ "$SHELL" = */zsh ]]; then _xmake_zsh_complete() { - local completions=("$(XMAKE_SKIP_HISTORY=1 xmake lua private.utils.complete 0 nospace "$words")") + local completions=("$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete 0 nospace "$words")") reply=( "${(ps:\n:)completions}" ) } @@ -185,7 +185,7 @@ elif [[ "$SHELL" = */bash ]]; then local word=${COMP_WORDS[COMP_CWORD]} local completions - completions="$(XMAKE_SKIP_HISTORY=1 xmake lua private.utils.complete "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)" + completions="$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)" if [ $? -ne 0 ]; then completions="" fi diff --git a/scripts/register-completions.bash b/scripts/register-completions.bash index 9d5865cd6..e45ce6489 100644 --- a/scripts/register-completions.bash +++ b/scripts/register-completions.bash @@ -5,7 +5,7 @@ _xmake_bash_complete() local word=${COMP_WORDS[COMP_CWORD]} local completions - completions="$(XMAKE_SKIP_HISTORY=1 xmake lua private.utils.complete "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)" + completions="$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)" if [ $? -ne 0 ]; then completions="" fi diff --git a/scripts/register-completions.ps1 b/scripts/register-completions.ps1 index f7e3e5da4..a9ecd7877 100644 --- a/scripts/register-completions.ps1 +++ b/scripts/register-completions.ps1 @@ -8,7 +8,7 @@ Register-ArgumentCompleter -Native -CommandName xmake -ScriptBlock { } $oldenv = $env:XMAKE_SKIP_HISTORY $env:XMAKE_SKIP_HISTORY = 1 - xmake lua private.utils.complete "0" "nospace" "$complete" | ForEach-Object { + xmake lua --root private.utils.complete "0" "nospace" "$complete" | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } $env:XMAKE_SKIP_HISTORY = $oldenv diff --git a/scripts/register-completions.zsh b/scripts/register-completions.zsh index 82f588d5f..5c16cf334 100644 --- a/scripts/register-completions.zsh +++ b/scripts/register-completions.zsh @@ -2,7 +2,7 @@ _xmake_zsh_complete() { - local completions=("$(XMAKE_SKIP_HISTORY=1 xmake lua private.utils.complete 0 nospace "$words")") + local completions=("$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete 0 nospace "$words")") reply=( "${(ps:\n:)completions}" ) } -- cgit v1.3.1 From 1c1c4bddc5b9f36a6cefeb2edae134645c445a88 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 18:26:31 +0800 Subject: fix --- xmake/core/base/cli.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua index a76d6db23..2ec5d8d42 100644 --- a/xmake/core/base/cli.lua +++ b/xmake/core/base/cli.lua @@ -53,11 +53,11 @@ function cli._make_arg(value, argv, argi) 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 }) + 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', #key == 1 and ('-' .. key .. value) or ('--' .. key .. '=' .. value), argv, argi, { key = key, value = value, short = short or false }) + return cli._make_segment('option', short and ('-' .. key .. value) or ('--' .. key .. '=' .. value), argv, argi, { key = key, value = value, short = short or false }) end function cli.parse(args, ...) -- cgit v1.3.1 From b18e975c2791c308499371173da11cbfa5297650 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Wed, 15 Jan 2020 18:27:47 +0800 Subject: fix --- xmake/core/base/cli.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua index 2ec5d8d42..310335fd4 100644 --- a/xmake/core/base/cli.lua +++ b/xmake/core/base/cli.lua @@ -57,7 +57,7 @@ function cli._make_flag(key, short, argv, argi) 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 }) + return cli._make_segment('option', short and ('-' .. key .. ' ' .. value) or ('--' .. key .. '=' .. value), argv, argi, { key = key, value = value, short = short or false }) end function cli.parse(args, ...) -- cgit v1.3.1 From d72f0495c5a8b860e7abed729f0d874542653066 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Thu, 16 Jan 2020 00:35:46 +0800 Subject: show --- xmake/core/base/cli.lua | 72 +++++++++++++++++++++++++++++++++++++++ xmake/core/base/colors.lua | 19 +++++++++-- xmake/core/base/option.lua | 85 ++++++++++++++++------------------------------ 3 files changed, 118 insertions(+), 58 deletions(-) diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua index 310335fd4..9ce43500b 100644 --- a/xmake/core/base/cli.lua +++ b/xmake/core/base/cli.lua @@ -117,6 +117,78 @@ function cli.parsev(argv, flags) return parsed end +-- @see https://unicode.org/reports/tr14/ +function cli._lastwbr(str, width, wordbreak) + + -- check + assert(#str >= width) + + if wordbreak == "breakall" then + -- To prevent overflow, word may be broken at any character + return width + else + + if str:sub(width + 1, width + 1):find("[%s]") then + -- exact break + return width + end + + local range = str:sub(1, width) + local poss = range:reverse():find("[%s-]") + if poss then + return #range - poss + 1 + end + + -- not found in range, try afterwards + poss = str:find("[%s-]", width + 1) + if poss then + return poss + end + + -- not found in all str + return #str + end +end + +-- break lines +function cli.wordwrap(str, width, opt) + + opt = opt or {} + + -- split to lines + if type(str) == 'table' then + str = table.concat(str, '\n') + end + local lines = tostring(str):split('\n', {plain = true, strict = true}) + + local result = {} + -- handle lines + for _, v in ipairs(lines) do + + -- remove tailing spaces, include '\r', which will be produced by `('l1\r\nl2'):split(...)` + v = v:rtrim() + while #v > width do + + -- find word break chance + local wbr = cli._lastwbr(v, width, opt.wordbreak) + + -- break line + local line = v:sub(1, wbr):rtrim() + table.insert(result, line) + v = v:sub(wbr + 1):ltrim() + + -- prevent empty line + if #v == 0 then v = nil end + end + + -- put remaining parts + table.insert(result, v) + end + + -- ok + return result +end + cli._segment = segment -- return module return cli diff --git a/xmake/core/base/colors.lua b/xmake/core/base/colors.lua index 3dff114b5..de66a9414 100644 --- a/xmake/core/base/colors.lua +++ b/xmake/core/base/colors.lua @@ -139,7 +139,7 @@ colors._keys24 = } -- the escape string -colors._escape = string.char(27) .. '[%sm' +colors._escape = '\x1b[%sm' -- get colorterm setting -- @@ -453,17 +453,20 @@ function colors.table(data, opt) assert(data) - local opt = opt or {} + -- init options + 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) + -- formartted cells local tab = {} local col_width = {} -- 'l' 'r' 'c' local col_align = {} + -- load column options if opt.colunms then for i = 1, table.maxn(opt.colunms or {}) do local v = opt.colunms[i] or {} @@ -472,6 +475,7 @@ function colors.table(data, opt) end end + -- format cells for i, row in ipairs(data) do tab[i] = {} for j, cell in ipairs(row) do @@ -483,25 +487,34 @@ function colors.table(data, opt) end end + -- render cells local empty_cell = { str = "", len = 0 } local rows = {} for i, row in ipairs(tab) do local cells = {} for j = 1, #col_width do + + -- use empty cell if not defined local cell = row[j] or empty_cell + if col_align[j] == 'r' then + -- right align cells[j] = string.rep(' ', col_width[j] - cell.len) .. cell.str elseif col_align[j] == 'c' then + -- centered 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) + --left align, emit tailing spaces for last colunm + cells[j] = cell.str .. ((j == #col_width) and "" or string.rep(' ', col_width[j] - cell.len)) end end rows[i] = table.concat(cells, sep) end + + -- concat rendered rows rows[#rows + 1] = "" return table.concat(rows, '\n') end diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index a1f628cdb..a862a2d40 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -147,18 +147,10 @@ end -- the command line function option.cmdline() - -- make command - local line = "xmake" - local argv = xmake._ARGV - for _, arg in ipairs(argv) do - if arg:find("%s") then - arg = "\"" .. arg .. "\"" - end - line = line .. " " .. arg + if not xmake._ARGS then + xmake._ARGS = os.args(xmake._ARGV) end - - -- ok? - return line + return "xmake " .. xmake._ARGS end -- init the option @@ -693,79 +685,62 @@ function option.show_main() end -- the category task - local categorytask = categories[categoryname] or {} - categories[categoryname] = categorytask + local category = categories[categoryname] or { name = categoryname, tasks = {} } + categories[categoryname] = category -- add task to the category - categorytask[taskname] = taskinfo + category.tasks[taskname] = taskinfo end -- sort categories - local categories_sorted = {} - for categoryname, categorytask in pairs(categories) do - if categoryname == "action" then - table.insert(categories_sorted, 1, {categoryname, categorytask}) - else - table.insert(categories_sorted, {categoryname, categorytask}) + categories = table.values(categories) + table.sort(categories, function (a, b) + if a.name == 'action' then + return true end - end + return a.name < b.name + end) -- dump tasks by categories - for _, categoryinfo in ipairs(categories_sorted) do + local tablecontent = {} + for _, category in ipairs(categories) do -- the category name and task - local categoryname = categoryinfo[1] - local categorytask = categoryinfo[2] - assert(categoryname and categorytask) + assert(category.name and category.tasks) -- print category name - io.print("") - io.print(colors.translate(string.format("${bright}%s%ss: ", string.sub(categoryname, 1, 1):upper(), string.sub(categoryname, 2)))) + table.insert(tablecontent, {}) + table.insert(tablecontent, {string.format("${bright}%s%ss: ", string.sub(category.name, 1, 1):upper(), string.sub(category.name, 2))}) -- the padding spaces local padding = 42 - -- get width of console - local console_width = math.max(os.getwinsize().width, 80) + -- get width of right colunm + local right_width = math.max(os.getwinsize().width, 60) - 41 -- print tasks - for taskname, taskinfo in pairs(categorytask) do + for taskname, taskinfo in pairs(category.tasks) do -- init the task line - local taskline = " " - if taskinfo.shortname then - taskline = taskline .. taskinfo.shortname .. ", " - else - taskline = taskline .. " " + local taskline = string.format("${color.menu.main.task.name} %s%s", + taskinfo.shortname and (taskinfo.shortname .. ", ") or " ", + taskname) + + local taskdesc = cli.wordwrap(taskinfo.description or "", right_width) + table.insert(tablecontent, {taskline, taskdesc[1]}) + for i = 2, #taskdesc do + table.insert(tablecontent, {"", taskdesc[i]}) end - - -- append the task name - taskline = taskline .. taskname - - -- append spaces - for i = (#taskline), padding do - taskline = taskline .. " " - end - - -- append color - taskline = colors.translate("${color.menu.main.task.name}" .. taskline .. "${clear}") - - -- append the task description - if taskinfo.description then - taskline = option._inwidth_append(taskline, taskinfo.description, padding + 1 - 18, console_width, console_width - padding - 1 + 18) - end - - -- print task line - io.print(colors.translate(taskline)) end end + io.write(colors.table(tablecontent, {sep = " ", colunms = {{min_width=40}, {}}})) end -- print options if main.options then option.show_options(main.options, "build") end -end +end -- show the options menu function option.show_options(options, taskname) -- cgit v1.3.1 From d5f108398a0b638f60aaab8089831bb9e67023c4 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Thu, 16 Jan 2020 17:03:20 +0800 Subject: add table --- xmake/core/base/cli.lua | 72 ---- xmake/core/base/colors.lua | 77 ----- xmake/core/base/option.lua | 204 +++--------- xmake/core/base/text.lua | 370 +++++++++++++++++++++ .../core/sandbox/modules/import/core/base/text.lua | 38 +++ 5 files changed, 462 insertions(+), 299 deletions(-) create mode 100644 xmake/core/base/text.lua create mode 100644 xmake/core/sandbox/modules/import/core/base/text.lua diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua index 9ce43500b..310335fd4 100644 --- a/xmake/core/base/cli.lua +++ b/xmake/core/base/cli.lua @@ -117,78 +117,6 @@ function cli.parsev(argv, flags) return parsed end --- @see https://unicode.org/reports/tr14/ -function cli._lastwbr(str, width, wordbreak) - - -- check - assert(#str >= width) - - if wordbreak == "breakall" then - -- To prevent overflow, word may be broken at any character - return width - else - - if str:sub(width + 1, width + 1):find("[%s]") then - -- exact break - return width - end - - local range = str:sub(1, width) - local poss = range:reverse():find("[%s-]") - if poss then - return #range - poss + 1 - end - - -- not found in range, try afterwards - poss = str:find("[%s-]", width + 1) - if poss then - return poss - end - - -- not found in all str - return #str - end -end - --- break lines -function cli.wordwrap(str, width, opt) - - opt = opt or {} - - -- split to lines - if type(str) == 'table' then - str = table.concat(str, '\n') - end - local lines = tostring(str):split('\n', {plain = true, strict = true}) - - local result = {} - -- handle lines - for _, v in ipairs(lines) do - - -- remove tailing spaces, include '\r', which will be produced by `('l1\r\nl2'):split(...)` - v = v:rtrim() - while #v > width do - - -- find word break chance - local wbr = cli._lastwbr(v, width, opt.wordbreak) - - -- break line - local line = v:sub(1, wbr):rtrim() - table.insert(result, line) - v = v:sub(wbr + 1):ltrim() - - -- prevent empty line - if #v == 0 then v = nil end - end - - -- put remaining parts - table.insert(result, v) - end - - -- ok - return result -end - cli._segment = segment -- return module return cli diff --git a/xmake/core/base/colors.lua b/xmake/core/base/colors.lua index de66a9414..c49191cd1 100644 --- a/xmake/core/base/colors.lua +++ b/xmake/core/base/colors.lua @@ -442,83 +442,6 @@ 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) - - -- init options - 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) - - -- formartted cells - local tab = {} - local col_width = {} - -- 'l' 'r' 'c' - local col_align = {} - - -- load column options - 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 - - -- format cells - 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 - - -- render cells - local empty_cell = { str = "", len = 0 } - local rows = {} - for i, row in ipairs(tab) do - local cells = {} - for j = 1, #col_width do - - -- use empty cell if not defined - local cell = row[j] or empty_cell - - if col_align[j] == 'r' then - -- right align - cells[j] = string.rep(' ', col_width[j] - cell.len) .. cell.str - elseif col_align[j] == 'c' then - -- centered - 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 - --left align, emit tailing spaces for last colunm - cells[j] = cell.str .. ((j == #col_width) and "" or string.rep(' ', col_width[j] - cell.len)) - end - end - rows[i] = table.concat(cells, sep) - end - - -- concat rendered rows - rows[#rows + 1] = "" - return table.concat(rows, '\n') -end - -- get theme function colors.theme() return colors._THEME diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index a862a2d40..b9721edec 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -25,6 +25,7 @@ local option = option or {} local cli = require("base/cli") local table = require("base/table") local colors = require("base/colors") +local text = require("base/text") -- ifelse, a? b : c function option._ifelse(a, b, c) @@ -69,51 +70,6 @@ function option._context() end end --- get line length -function option._get_linelen(st) - local poss = st:reverse():find("\n") - if not poss then return (#st) end - local start_pos, _ = poss - return start_pos - 1 -end - --- get last space -function option._get_lastspace(st) - local poss = st:reverse():find("[%s-]") - if not poss then return (#st) end - local start_pos, _ = poss - return (#st) - start_pos + 1 -end - --- append spaces in width -function option._inwidth_append(dst, st, padding, width, remain_width) - - if padding >= width then - return dst .. st - end - - local white_padding = string.rep(" ", padding) - if remain_width == nil then - -- TODO because of colored string, it's wrong sometimes - remain_width = width - option._get_linelen(dst) - end - - if remain_width <= 0 then - return option._inwidth_append(dst .. "\n" .. white_padding, st, padding, width, width - padding) - end - - if (#st) <= remain_width then - return dst .. st - end - - local lastspace = option._get_lastspace(st:sub(1, remain_width)) - if lastspace + 1 > (#st) then - return dst .. st - else - return option._inwidth_append(dst .. st:sub(1, lastspace) .. "\n" .. white_padding, st:sub(lastspace + 1):ltrim(), padding, width, width - padding) - end -end - -- save context function option.save(taskname) @@ -633,7 +589,7 @@ function option.show_menu(task) if taskmenu.options then option.show_options(taskmenu.options, task) end -end +end -- show the main menu function option.show_main() @@ -710,30 +666,26 @@ function option.show_main() -- print category name table.insert(tablecontent, {}) - table.insert(tablecontent, {string.format("${bright}%s%ss: ", string.sub(category.name, 1, 1):upper(), string.sub(category.name, 2))}) - - -- the padding spaces - local padding = 42 - - -- get width of right colunm - local right_width = math.max(os.getwinsize().width, 60) - 41 + table.insert(tablecontent, {{string.format("%s%ss: ", string.sub(category.name, 1, 1):upper(), string.sub(category.name, 2)), style="${reset bright}"}}) -- print tasks for taskname, taskinfo in pairs(category.tasks) do -- init the task line - local taskline = string.format("${color.menu.main.task.name} %s%s", + local taskline = string.format(" %s%s", taskinfo.shortname and (taskinfo.shortname .. ", ") or " ", taskname) - - local taskdesc = cli.wordwrap(taskinfo.description or "", right_width) - table.insert(tablecontent, {taskline, taskdesc[1]}) - for i = 2, #taskdesc do - table.insert(tablecontent, {"", taskdesc[i]}) - end + table.insert(tablecontent, {taskline, taskinfo.description or ""}) end end - io.write(colors.table(tablecontent, {sep = " ", colunms = {{min_width=40}, {}}})) + + -- set table styles + tablecontent.style = {"${color.menu.main.task.name}"} + tablecontent.width = {nil, "auto"} + tablecontent.sep = " " + + -- print table + io.write(text.table(tablecontent)) end -- print options @@ -748,9 +700,6 @@ function option.show_options(options, taskname) -- check assert(options) - -- the padding spaces - local padding = 42 - -- remove repeat empty lines local is_action = false local emptyline_count = 0 @@ -769,27 +718,28 @@ function option.show_options(options, taskname) end end + local tablecontent = {} + -- print header - io.print("") + table.insert(tablecontent, {}) if is_action then - io.print(colors.translate("${bright}Common options: ")) + table.insert(tablecontent, {{"Common options:", style="${reset bright}"}}) else - io.print(colors.translate("${bright}Options: ")) + table.insert(tablecontent, {{"Options:", style="${reset bright}"}}) end -- print options - options = printed_options - for _, opt in ipairs(options) do + for _, opt in ipairs(printed_options) do -- the following options are belong action? show command section -- -- @see core/base/task.lua: translate menu -- if opt.category and opt.category == "action" then - io.print("") - io.print(colors.translate("${bright}Command options (" .. taskname .. "): ")) + table.insert(tablecontent, {}) + table.insert(tablecontent, {{"Command options(" .. taskname .. "):", style="${reset bright}"}}) end - + -- init the option info local option_info = "" @@ -821,104 +771,58 @@ function option.show_options(options, taskname) option_info = option_info .. " ..." end - -- append spaces - for i = (#option_info), padding do - option_info = option_info .. " " + -- get description + local description = table.move(opt, 5, table.maxn(opt), 1, {}) + if #description == 0 then + description[1] = "" end - -- append color - option_info = colors.translate("${color.menu.option.name}" .. option_info .. "${clear}") - - -- get width of console - local console_width = math.max(os.getwinsize().width, 80) - - -- append the option description - local description = opt[5] - if description then - option_info = option._inwidth_append(option_info, description, padding + 1, console_width, console_width - padding - 1) + -- transform description + local desp_strs = {} + for _, v in ipairs(description) do + if type(v) == "function" then + v = v() + end + if type(v) == "string" then + table.insert(desp_strs, v) + elseif type(v) == "table" then + table.move(v, 1, #v, #desp_strs + 1, desp_strs) + end end -- append the default value if default then local defaultval = tostring(default) if type(default) == "boolean" then - defaultval = option._ifelse(default, "y", "n") + defaultval = default and "y" or "n" end - option_info = option._inwidth_append(option_info, " (default: ", padding + 1, console_width) - local origin_width = option._get_linelen(option_info) - option_info = option_info .. "${bright}" - option_info = option._inwidth_append(option_info, defaultval, padding + 1, console_width, console_width - origin_width) - origin_width = option._ifelse(origin_width + #defaultval > console_width, option._get_linelen(option_info), origin_width + (#(tostring(default)))) - option_info = option_info .. "${clear}" - option_info = option._inwidth_append(option_info, ")", padding + 1, console_width, console_width - origin_width) + local def_desp = colors.translate(string.format(" (default: ${bright}%s${clear})", defaultval)) + desp_strs[1] = desp_strs[1] .. def_desp end - -- print option info - io.print(colors.translate(option_info)) - - -- print more description if exists - for i = 6, 64 do - - -- the description, @note some option may be nil - local description = opt[i] - if not description then break end - - -- is function? get results - if type(description) == "function" then - description = description() - end - - -- the description is string? - if type(description) == "string" then - - -- make spaces - local spaces = "" - for i = 0, padding do - spaces = spaces .. " " - end - - -- print this description - io.print(option._inwidth_append(spaces, description, padding + 1, console_width)) - - -- the description is table? - elseif type(description) == "table" then - - -- print all descriptions - for _, v in pairs(description) do - - -- make spaces - local spaces = "" - for i = 0, padding do - spaces = spaces .. " " - end - - -- print this description - io.print(option._inwidth_append(spaces, v, padding + 1, console_width)) - end - end - end - - -- print values + -- append values local values = opt.values if type(values) == "function" then values = values() end if values then - for _, value in ipairs(table.wrap(values)) do - - -- make spaces - local spaces = "" - for i = 0, padding do - spaces = spaces .. " " - end - - -- print this value - io.print(option._inwidth_append(spaces, " - " .. tostring(value), padding + 1, console_width)) + table.insert(desp_strs, " - " .. tostring(value)) end end + + -- insert row + table.insert(tablecontent, {option_info, desp_strs}) end -end + + -- set table styles + tablecontent.style = {"${color.menu.option.name}"} + tablecontent.width = {nil, "auto"} + tablecontent.sep = " " + + -- print table + io.write(text.table(tablecontent)) +end -- return module: option return option diff --git a/xmake/core/base/text.lua b/xmake/core/base/text.lua new file mode 100644 index 000000000..462c757a1 --- /dev/null +++ b/xmake/core/base/text.lua @@ -0,0 +1,370 @@ +--!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 text.lua +-- + +-- define module +local text = text or {} + +-- load modules +local string = require("base/string") +local colors = require("base/colors") +local math = require("base/math") +local dump = require("base/dump") + +-- @see https://unicode.org/reports/tr14/ +function text._lastwbr(str, width, wordbreak) + + -- check + assert(#str >= width) + + if wordbreak == "breakall" then + -- To prevent overflow, word may be broken at any character + return width + else + + if str:sub(width + 1, width + 1):find("[%s]") then + -- exact break + return width + end + + local range = str:sub(1, width) + local poss = range:reverse():find("[%s-]") + if poss then + return #range - poss + 1 + end + + -- not found in range, try afterwards + poss = str:find("[%s-]", width + 1) + if poss then + return poss + end + + -- not found in all str + return #str + end +end + +-- break lines +function text.wordwrap(str, width, opt) + + opt = opt or {} + + -- split to lines + if type(str) == 'table' then + str = table.concat(str, '\n') + end + local lines = tostring(str):split('\n', {plain = true, strict = true}) + + local result = {} + local actual_width = 0 + + -- handle lines + for _, v in ipairs(lines) do + + -- remove tailing spaces, include '\r', which will be produced by `('l1\r\nl2'):split(...)` + v = v:rtrim() + + while #v > width do + + -- find word break chance + local wbr = text._lastwbr(v, width, opt.wordbreak) + + -- break line + local line = v:sub(1, wbr):rtrim() + actual_width = math.max(#line, actual_width) + table.insert(result, line) + v = v:sub(wbr + 1):ltrim() + + -- prevent empty line + if #v == 0 then + v = nil + break + end + end + + -- put remaining parts + if v then + actual_width = math.max(#v, actual_width) + table.insert(result, v) + end + end + + -- ok + return result, actual_width +end + +function text._format_cell(cell, width, opt) + local result = {} + local max_width = 0 + for _, v in ipairs(cell) do + local lines, aw = text.wordwrap(tostring(v), width[2], opt) + table.move(lines, 1, #lines, #result + 1, result) + max_width = math.max(max_width, aw) + end + cell.formatted = result + cell.width = max_width +end + +function text._format_col(col, width, opt) + local max_width = 0 + for i = 1, table.maxn(col) do + local v = col[i] + -- skip span cells + if v and not v.span then + text._format_cell(v, width, opt) + max_width = math.max(max_width, v.width) + end + end + col.width = max_width +end + + +-- make a table with colors +-- +-- @param data table data, array of array of cells with optional styles +-- eg: { +-- {"1", nil, "3"}, -- use nil to make previous cell to span next column +-- {"4", "5", {"line1", "line2", style="${yellow}", align = 'r'}}, -- multi-line content & set style or align for cell +-- {"7", "8", {"9", style="${reset}${red}"}, style="${bright}", align = 'c'}, -- set style or align for row +-- style = {"${underline}"}, -- set style for columns +-- -- or use "${underline}" for all columns +-- width = { 20, {10, 50}, "auto"}, +-- -- 2 numbers - min and max width (nil for not set, eg: {nil, 50}); +-- -- a number - width, num is equivalent to {num, num}; +-- -- nil - no limit, equivalent to {nil, nil} +-- -- "auto" - use remain space of console, only one 'auto' colunm is allowed +-- align = {'l', 'r', 'c'} -- align mode for each column, 'left', 'center' or 'right' +-- sep = "${dim} | ", -- table colunm sepertor, default is ' | ', use '' to hide +-- } +-- priority of style and align: cell > row > col +-- @param opt options for color rendering and word warpping +function text.table(data, opt) + + assert(data) + + -- init options + opt = opt or { patch_reset = false, ignore_unknown = true } + opt.patch_reset = false + local sep = colors.translate(data.sep or ' | ', opt) + local sep_len = #colors.ignore(data.sep or ' | ', opt) + + -- col ordered cells + local cols = {} + local n_row = table.maxn(data) + local n_col = 1 + + -- count cols + for i = 1, n_row do + local row = data[i] + if row == nil then + data[i] = {{""}} + else + n_col = math.max(n_col, table.maxn(row)) + end + end + + -- reorder + for i = 1, n_row do + local row = data[i] + local p_cell = nil + for j = 1, n_col do + local cell = row[j] + if cell ~= nil and type(cell) ~= "table" then + -- wrap cells if needed + cell = {tostring(cell)} + elseif cell == nil and j == 1 then + cell = {""} + end + local col = cols[j] + if not col then + col = {} + cols[j] = col + end + if cell then + col[i] = cell + p_cell = cell + else + p_cell.span = (p_cell.span or 1) + 1 + end + end + end + + -- load column options + data.width = data.width or {} + data.align = data.align or {} + data.style = data.style or {} + + local style = "" + if type(data.style) == "string" then + style = data.style + data.style = {} + end + + -- index of auto col + local auto_col = nil + for i = 1, n_col do + + -- load width + local w = data.width[i] + if w ~= "auto" then + local wl, wu + if w == nil then + wl, wu = 0, math.huge + elseif type(w) == 'number' then + if math.isnan(w) or math.isinf(w) then + wl, wu = 0, math.huge + else + wl, wu = w, w + end + else + wl, wu = w[1], w[2] + end + wl = wl or 0 + wu = wu or math.huge + data.width[i] = {wl, wu} + else + assert(not auto_col, 'Only one "auto" colunm is allowed.') + auto_col = i + end + + -- load align + cols[i].align = (data.align[i] or 'l'):sub(1, 1):lower() + -- load style + cols[i].style = data.style[i] or style + end + + -- format table + + -- 1. format non-auto cols + for i, col in ipairs(cols) do + if i ~= auto_col then + text._format_col(col, data.width[i], opt) + end + end + + if auto_col then + + -- 2. caculate auto col width + local auto_width = os.getwinsize().width + for i = 1, n_col do + if i ~= auto_col then + auto_width = auto_width - cols[i].width + end + end + auto_width = math.max(0, auto_width - sep_len * (n_col - 1)) + data.width[auto_col] = {0,auto_width} + + -- 3. format auto col + text._format_col(cols[auto_col], data.width[auto_col], opt) + end + + -- 4. format span cell + for i, col in ipairs(cols) do + + for j = 1, n_row do + local cell = col[j] + if cell and cell.span then + local w, wl = 0, 0 + for ci = 0, (cell.span - 1) do + -- actual width of spanned cols + w = w + cols[i + ci].width + -- min width of spanned cols + wl = wl + data.width[i + ci][1] + end + text._format_cell(cell, {0, math.max(w, wl) + sep_len * (cell.span - 1)}, opt) + end + end + end + + -- render cells + + -- row ordered cells + local rows = {} + + -- reorder + for i = 1, n_row do + local row = {} + local line = 1 + for j = 1, n_col do + local cell = cols[j][i] + if cell then + assert(cell.formatted) + line = math.max(#cell.formatted, line) + end + row[j] = cell + end + row.line = line + rows[i] = row + end + + local results = {} + local reset = colors.translate("${reset}", opt) + for i, row in ipairs(rows) do + for l = 1, row.line do + local cells = {} + local j = 1 + while j <= n_col do + + local cell = row[j] + assert(cell) + local col = cols[j] + + if l == 1 then + cell.align = cell.align or row.align or col.align + cell.style = colors.translate((col.style or "") .. (row.style or "") .. (cell.style or ""), opt) + end + + local str = cell.formatted[l] or "" + local width = col.width + local span = cell.span or 1 + if cell.span then + for ci = (j + 1), (j + span - 1) do + width = width + cols[ci].width + end + width = width + sep_len * (span - 1) + end + + local padded + if cell.align == 'r' then + -- right align + padded = string.rep(' ', width - #str) .. str + elseif cell.align == 'c' then + -- centered + local padding = width - #str + local lp = math.floor(padding / 2) + local rp = math.ceil(padding / 2) + padded = string.rep(' ', lp) .. str .. string.rep(' ', rp) + else + --left align, emit tailing spaces for last colunm + padded = str .. ((j + span == n_col + 1) and "" or string.rep(' ', width - #str)) + end + table.insert(cells, cell.style .. padded .. reset) + j = j + span + end + table.insert(results, table.concat(cells, sep)) + end + end + + -- concat rendered rows + results[#results + 1] = "" + return table.concat(results, '\n') +end + +-- return module +return text diff --git a/xmake/core/sandbox/modules/import/core/base/text.lua b/xmake/core/sandbox/modules/import/core/base/text.lua new file mode 100644 index 000000000..dbbc9eccc --- /dev/null +++ b/xmake/core/sandbox/modules/import/core/base/text.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 text.lua +-- + +-- load modules +local text = require("base/text") + + +-- define module +local sandbox_text = sandbox_text or {} + +-- inherit some builtin interfaces +for key, value in pairs(text) do + if not key:startswith("_") then + sandbox_text[key] = value + end +end + +-- return module +return sandbox_text + + -- cgit v1.3.1 From 9cca6d065a6d8756876f6877725eeaa1304a6ef7 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Thu, 16 Jan 2020 17:25:48 +0800 Subject: fix style --- xmake/core/base/cli.lua | 22 +++++++++---------- xmake/core/base/option.lua | 4 ++-- xmake/core/base/text.lua | 53 +++++++++++++++++++++++++++++----------------- xmake/core/base/utils.lua | 5 +++-- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua index 310335fd4..ea328c22e 100644 --- a/xmake/core/base/cli.lua +++ b/xmake/core/base/cli.lua @@ -49,15 +49,15 @@ function cli._make_segment(type, string, argv, argi, obj) end function cli._make_arg(value, argv, argi) - return cli._make_segment('arg', value, argv, argi, { value = value }) + 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 }) + 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 }) + return cli._make_segment("option", short and ("-" .. key .. " " .. value) or ("--" .. key .. "=" .. value), argv, argi, { key = key, value = value, short = short or false }) end function cli.parse(args, ...) @@ -74,17 +74,17 @@ function cli.parsev(argv, flags) 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 '-') + 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 '--' + 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) + 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 diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index b9721edec..dc472bccc 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -195,7 +195,7 @@ function option.parse(argv, options, opt) assert(o and ((mode ~= "v" and mode ~= "vs") or name)) -- fill short flags - if o[3] == 'k' and o[1] then + if o[3] == "k" and o[1] then table.insert(flags, o[1]) end end @@ -651,7 +651,7 @@ function option.show_main() -- sort categories categories = table.values(categories) table.sort(categories, function (a, b) - if a.name == 'action' then + if a.name == "action" then return true end return a.name < b.name diff --git a/xmake/core/base/text.lua b/xmake/core/base/text.lua index 462c757a1..965c1daa9 100644 --- a/xmake/core/base/text.lua +++ b/xmake/core/base/text.lua @@ -66,10 +66,10 @@ function text.wordwrap(str, width, opt) opt = opt or {} -- split to lines - if type(str) == 'table' then - str = table.concat(str, '\n') + if type(str) == "table" then + str = table.concat(str, "\n") end - local lines = tostring(str):split('\n', {plain = true, strict = true}) + local lines = tostring(str):split("\n", {plain = true, strict = true}) local result = {} local actual_width = 0 @@ -77,7 +77,7 @@ function text.wordwrap(str, width, opt) -- handle lines for _, v in ipairs(lines) do - -- remove tailing spaces, include '\r', which will be produced by `('l1\r\nl2'):split(...)` + -- remove tailing spaces, include "\r", which will be produced by `("l1\r\nl2"):split(...)` v = v:rtrim() while #v > width do @@ -140,17 +140,17 @@ end -- @param data table data, array of array of cells with optional styles -- eg: { -- {"1", nil, "3"}, -- use nil to make previous cell to span next column --- {"4", "5", {"line1", "line2", style="${yellow}", align = 'r'}}, -- multi-line content & set style or align for cell --- {"7", "8", {"9", style="${reset}${red}"}, style="${bright}", align = 'c'}, -- set style or align for row +-- {"4", "5", {"line1", "line2", style="${yellow}", align = "r"}}, -- multi-line content & set style or align for cell +-- {"7", "8", {"9", style="${reset}${red}"}, style="${bright}", align = "c"}, -- set style or align for row -- style = {"${underline}"}, -- set style for columns -- -- or use "${underline}" for all columns -- width = { 20, {10, 50}, "auto"}, -- -- 2 numbers - min and max width (nil for not set, eg: {nil, 50}); -- -- a number - width, num is equivalent to {num, num}; -- -- nil - no limit, equivalent to {nil, nil} --- -- "auto" - use remain space of console, only one 'auto' colunm is allowed --- align = {'l', 'r', 'c'} -- align mode for each column, 'left', 'center' or 'right' --- sep = "${dim} | ", -- table colunm sepertor, default is ' | ', use '' to hide +-- -- "auto" - use remain space of console, only one "auto" colunm is allowed +-- align = {"l", "r", "c"} -- align mode for each column, "left", "center" or "right" +-- sep = "${dim} | ", -- table colunm sepertor, default is " | ", use "" to hide -- } -- priority of style and align: cell > row > col -- @param opt options for color rendering and word warpping @@ -160,9 +160,8 @@ function text.table(data, opt) -- init options opt = opt or { patch_reset = false, ignore_unknown = true } + data.sep = data.sep or " | " opt.patch_reset = false - local sep = colors.translate(data.sep or ' | ', opt) - local sep_len = #colors.ignore(data.sep or ' | ', opt) -- col ordered cells local cols = {} @@ -214,8 +213,12 @@ function text.table(data, opt) if type(data.style) == "string" then style = data.style data.style = {} + data.sep = style .. data.sep .. "${reset}" end + local sep = colors.translate(data.sep, opt) + local sep_len = #colors.ignore(data.sep, opt) + -- index of auto col local auto_col = nil for i = 1, n_col do @@ -226,7 +229,7 @@ function text.table(data, opt) local wl, wu if w == nil then wl, wu = 0, math.huge - elseif type(w) == 'number' then + elseif type(w) == "number" then if math.isnan(w) or math.isinf(w) then wl, wu = 0, math.huge else @@ -239,12 +242,12 @@ function text.table(data, opt) wu = wu or math.huge data.width[i] = {wl, wu} else - assert(not auto_col, 'Only one "auto" colunm is allowed.') + assert(not auto_col, "Only one 'auto' colunm is allowed.") auto_col = i end -- load align - cols[i].align = (data.align[i] or 'l'):sub(1, 1):lower() + cols[i].align = (data.align[i] or "l"):sub(1, 1):lower() -- load style cols[i].style = data.style[i] or style end @@ -299,6 +302,7 @@ function text.table(data, opt) -- reorder for i = 1, n_row do + local d_row = data[i] or {} local row = {} local line = 1 for j = 1, n_col do @@ -310,6 +314,15 @@ function text.table(data, opt) row[j] = cell end row.line = line + + -- load align + if d_row.align then + row.align = d_row.align:sub(1, 1):lower() + end + + -- load style + row.style = d_row.style or "" + rows[i] = row end @@ -341,18 +354,18 @@ function text.table(data, opt) end local padded - if cell.align == 'r' then + if cell.align == "r" then -- right align - padded = string.rep(' ', width - #str) .. str - elseif cell.align == 'c' then + padded = string.rep(" ", width - #str) .. str + elseif cell.align == "c" then -- centered local padding = width - #str local lp = math.floor(padding / 2) local rp = math.ceil(padding / 2) - padded = string.rep(' ', lp) .. str .. string.rep(' ', rp) + padded = string.rep(" ", lp) .. str .. string.rep(" ", rp) else --left align, emit tailing spaces for last colunm - padded = str .. ((j + span == n_col + 1) and "" or string.rep(' ', width - #str)) + padded = str .. ((j + span == n_col + 1) and "" or string.rep(" ", width - #str)) end table.insert(cells, cell.style .. padded .. reset) j = j + span @@ -363,7 +376,7 @@ function text.table(data, opt) -- concat rendered rows results[#results + 1] = "" - return table.concat(results, '\n') + return table.concat(results, "\n") end -- return module diff --git a/xmake/core/base/utils.lua b/xmake/core/base/utils.lua index fa42d7e25..dda5077d3 100644 --- a/xmake/core/base/utils.lua +++ b/xmake/core/base/utils.lua @@ -28,6 +28,7 @@ local string = require("base/string") local log = require("base/log") local io = require("base/io") local dump = require("base/dump") +local text = require("base/text") -- dump values function utils.dump(...) @@ -319,11 +320,11 @@ function utils.confirm(opt) end function utils.table(data, opt) - utils.printf(colors.table(data, opt)) + utils.printf(text.table(data, opt)) end function utils.vtable(data, opt) - utils.vprintf(colors.table(data, opt)) + utils.vprintf(text.table(data, opt)) end -- return module -- cgit v1.3.1 From 6b150874b14eded0f8754286ce2c579e418c9e94 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Thu, 16 Jan 2020 18:05:10 +0800 Subject: fix --- xmake/core/base/option.lua | 148 +++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 72 deletions(-) diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index dc472bccc..6a99a1cb9 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -27,11 +27,6 @@ local table = require("base/table") local colors = require("base/colors") local text = require("base/text") --- ifelse, a? b : c -function option._ifelse(a, b, c) - if a then return b else return c end -end - -- translate the menu function option._translate(menu) @@ -731,88 +726,97 @@ function option.show_options(options, taskname) -- print options for _, opt in ipairs(printed_options) do - -- the following options are belong action? show command section - -- - -- @see core/base/task.lua: translate menu - -- if opt.category and opt.category == "action" then + + -- the following options are belong action? show command section + -- + -- @see core/base/task.lua: translate menu + -- table.insert(tablecontent, {}) - table.insert(tablecontent, {{"Command options(" .. taskname .. "):", style="${reset bright}"}}) - end + table.insert(tablecontent, {{"Command options (" .. taskname .. "):", style="${reset bright}"}}) + elseif opt[3] == nil then - -- init the option info - local option_info = "" - - -- append the shortname - local shortname = opt[1] - local name = opt[2] - local mode = opt[3] - local default = opt[4] - if shortname then - option_info = option_info .. " -" .. shortname - if mode == "kv" then - option_info = option_info .. " " .. option._ifelse(name, name:upper(), "XXX") - end - end + -- insert empty line + table.insert(tablecontent, {}) + else - -- append the name - if name then - if mode == "v" then - option_info = option_info .. " " .. name - elseif mode == "vs" then - option_info = option_info .. " " .. name .. " ..." + -- init the option info + local option_info + + -- append the shortname + local shortname = opt[1] + local name = opt[2] + local mode = opt[3] + local default = opt[4] + if shortname then + if mode == "kv" then + option_info = " -" .. shortname .. " " .. (name and name:upper() or "XXX") + else + option_info = " -" .. shortname + end else - option_info = option_info .. option._ifelse(shortname, ", --", " --") .. name - end - if mode == "kv" then - option_info = option_info .. "=" .. option._ifelse(type(default) == "boolean", "[y|n]", name:upper()) + option_info = " " end - elseif mode == "v" or mode == "vs" then - option_info = option_info .. " ..." - end - -- get description - local description = table.move(opt, 5, table.maxn(opt), 1, {}) - if #description == 0 then - description[1] = "" - end + -- append the name + if name then + local leading = (shortname and "," or " ") .. (mode:startswith("k") and " --" or " ") + local kv + if mode:startswith("k") then + kv = name + elseif mode == "vs" then + kv = name .. " ..." + else + kv = (name .. "=" .. ((type(default) == "boolean") and "[y|n]" or name:upper())) + end + option_info = option_info .. leading .. kv + elseif mode == "v" or mode == "vs" then + option_info = option_info .. " ..." + end - -- transform description - local desp_strs = {} - for _, v in ipairs(description) do - if type(v) == "function" then - v = v() + -- get description + local description = table.move(opt, 5, table.maxn(opt), 1, {}) + if #description == 0 then + description[1] = "" end - if type(v) == "string" then - table.insert(desp_strs, v) - elseif type(v) == "table" then - table.move(v, 1, #v, #desp_strs + 1, desp_strs) + + -- transform description + local desp_strs = {} + for _, v in ipairs(description) do + if type(v) == "function" then + v = v() + end + if type(v) == "string" then + table.insert(desp_strs, v) + elseif type(v) == "table" then + table.move(v, 1, #v, #desp_strs + 1, desp_strs) + end end - end - -- append the default value - if default then - local defaultval = tostring(default) - if type(default) == "boolean" then - defaultval = default and "y" or "n" + -- append the default value + if default then + local defaultval = tostring(default) + if type(default) == "boolean" then + defaultval = default and "y" or "n" + end + local def_desp = colors.translate(string.format(" (default: ${bright}%s${clear})", defaultval)) + desp_strs[1] = desp_strs[1] .. def_desp end - local def_desp = colors.translate(string.format(" (default: ${bright}%s${clear})", defaultval)) - desp_strs[1] = desp_strs[1] .. def_desp - end - -- append values - local values = opt.values - if type(values) == "function" then - values = values() - end - if values then - for _, value in ipairs(table.wrap(values)) do - table.insert(desp_strs, " - " .. tostring(value)) + -- append values + local values = opt.values + if type(values) == "function" then + values = values() + end + if values then + for _, value in ipairs(table.wrap(values)) do + table.insert(desp_strs, " - " .. tostring(value)) + end end - end - -- insert row - table.insert(tablecontent, {option_info, desp_strs}) + -- insert row + table.insert(tablecontent, {option_info, desp_strs}) + end end -- set table styles -- cgit v1.3.1 From 0ce4e75a38fbe5d4dc7688a4c65a56bf0a8d332d Mon Sep 17 00:00:00 2001 From: Opportunity Date: Thu, 16 Jan 2020 18:25:47 +0800 Subject: improve serialize --- xmake/core/base/serialize.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xmake/core/base/serialize.lua b/xmake/core/base/serialize.lua index 6095fea9a..331e124a0 100644 --- a/xmake/core/base/serialize.lua +++ b/xmake/core/base/serialize.lua @@ -41,7 +41,11 @@ function serialize._keywords() end function serialize._makestring(str, opt) - return string.format("%q", str) + if string.find(str, "\\", 1, true) and not string.find(str, "[%c%]%\n]") then + return string.format("[[%s]]", str) + else + return string.format("%q", str) + end end function serialize._makedefault(val, opt) -- cgit v1.3.1 From 19fef8183e250a49628a4281264753a7da1338dc Mon Sep 17 00:00:00 2001 From: Opportunity Date: Thu, 16 Jan 2020 20:20:39 +0800 Subject: fix complete --- scripts/get.sh | 2 +- scripts/register-completions.bash | 2 +- xmake/actions/build/xmake.lua | 29 ++++++------ xmake/actions/clean/xmake.lua | 5 +- xmake/actions/config/xmake.lua | 11 +++-- xmake/actions/install/xmake.lua | 15 +++--- xmake/actions/package/xmake.lua | 7 +-- xmake/actions/run/xmake.lua | 19 ++++---- xmake/actions/uninstall/xmake.lua | 23 ++++----- xmake/core/base/option.lua | 6 +-- xmake/core/base/text.lua | 13 +++-- xmake/modules/private/utils/complete.lua | 81 +++++++++++++++++++++++--------- 12 files changed, 131 insertions(+), 82 deletions(-) diff --git a/scripts/get.sh b/scripts/get.sh index 5f6bde3c2..a0d49c528 100755 --- a/scripts/get.sh +++ b/scripts/get.sh @@ -185,7 +185,7 @@ elif [[ "$SHELL" = */bash ]]; then local word=${COMP_WORDS[COMP_CWORD]} local completions - completions="$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)" + completions="$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete "${COMP_POINT}" "conf" "${COMP_LINE}" 2>/dev/null)" if [ $? -ne 0 ]; then completions="" fi diff --git a/scripts/register-completions.bash b/scripts/register-completions.bash index e45ce6489..18df8a3b7 100644 --- a/scripts/register-completions.bash +++ b/scripts/register-completions.bash @@ -5,7 +5,7 @@ _xmake_bash_complete() local word=${COMP_WORDS[COMP_CWORD]} local completions - completions="$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete "${COMP_POINT}" "${COMP_LINE}" 2>/dev/null)" + completions="$(XMAKE_SKIP_HISTORY=1 xmake lua --root private.utils.complete "${COMP_POINT}" "conf" "${COMP_LINE}" 2>/dev/null)" if [ $? -ne 0 ]; then completions="" fi diff --git a/xmake/actions/build/xmake.lua b/xmake/actions/build/xmake.lua index aabc852bc..0eb4eb991 100644 --- a/xmake/actions/build/xmake.lua +++ b/xmake/actions/build/xmake.lua @@ -38,24 +38,25 @@ task("build") -- options , options = { - {'b', "build", "k", nil, "Build target. This is default building mode and optional." } - , {'r', "rebuild", "k", nil, "Rebuild the target." } - , {'a', "all", "k", nil, "Build all targets." } + {'b', "build", "k", nil , "Build target. This is default building mode and optional." } + , {'r', "rebuild", "k", nil , "Rebuild the target." } + , {'a', "all", "k", nil , "Build all targets." } , {} - , {'j', "jobs", "kv", tostring(math.ceil(os.cpuinfo().ncpu * 3 / 2)), - "Specifies the number of jobs to build simultaneously." } - , {'w', "warning", "k", false, "Enable the warnings output." } - , {'t', "try", "k", false, "Try building project using third-party buildsystem." } - , {nil, "files", "kv", nil, "Build the given source files.", - "e.g. ", - " - xmake --files=src/main.c", - " - xmake --files='src/*.c' [target]", - " - xmake --files='src/**c|excluded_file.c'", - " - xmake --files='src/main.c" .. path.envsep() .. "src/test.c'" } + , {'j', "jobs", "kv", tostring(math.ceil(os.cpuinfo().ncpu * 3 / 2)), + "Specifies the number of jobs to build simultaneously." } + , {'w', "warning", "k", false , "Enable the warnings output." } + , {'t', "try", "k", false , "Try building project using third-party buildsystem." } + , {nil, "files", "kv", nil , "Build the given source files.", + "e.g. ", + " - xmake --files=src/main.c", + " - xmake --files='src/*.c' [target]", + " - xmake --files='src/**c|excluded_file.c'", + " - xmake --files='src/main.c" .. path.envsep() .. "src/test.c'" } , {} - , {nil, "target", "v", nil, "The target name. It will build all default targets if this parameter is not specified." } + , {nil, "target", "v", nil , "The target name. It will build all default targets if this parameter is not specified." + , values = function () return table.keys(import("core.project.project").targets()) end } } } diff --git a/xmake/actions/clean/xmake.lua b/xmake/actions/clean/xmake.lua index a275d75e9..1b088c181 100644 --- a/xmake/actions/clean/xmake.lua +++ b/xmake/actions/clean/xmake.lua @@ -41,10 +41,11 @@ task("clean") -- options , options = { - {'a', "all", "k", nil, "Clean all auto-generated files by xmake." } + {'a', "all", "k", nil , "Clean all auto-generated files by xmake." } , {} - , {nil, "target", "v", nil, "The target name. It will clean all default targets if this parameter is not specified." } + , {nil, "target", "v", nil , "The target name. It will clean all default targets if this parameter is not specified." + , values = function () return table.keys(import("core.project.project").targets()) end } } } diff --git a/xmake/actions/config/xmake.lua b/xmake/actions/config/xmake.lua index e83e960da..8f1f1d06e 100644 --- a/xmake/actions/config/xmake.lua +++ b/xmake/actions/config/xmake.lua @@ -130,13 +130,14 @@ task("config") end , {category = "Other Configuration"} - , {nil, "debugger", "kv", "auto", "The Debugger" } - , {nil, "ccache", "kv", true, "Enable or disable the c/c++ compiler cache." - , " --ccache=[y|n]" } - , {'o', "buildir", "kv", "build", "Set the build directory." } + , {nil, "debugger", "kv", "auto" , "The Debugger" } + , {nil, "ccache", "kv", true , "Enable or disable the c/c++ compiler cache." + , " --ccache=[y|n]" } + , {'o', "buildir", "kv", "build" , "Set the build directory." } , {} - , {nil, "target", "v", nil, "Configure for the given target." } + , {nil, "target", "v", nil , "Configure for the given target." + , values = function () return table.keys(import("core.project.project").targets()) end } } } diff --git a/xmake/actions/install/xmake.lua b/xmake/actions/install/xmake.lua index 38f013114..4aad1a62f 100644 --- a/xmake/actions/install/xmake.lua +++ b/xmake/actions/install/xmake.lua @@ -41,15 +41,16 @@ task("install") -- options , options = { - {'o', "installdir", "kv", nil, "Set the install directory.", - "e.g.", - " $ xmake install -o /usr/local", - "or $ DESTDIR=/usr/local xmake install", - "or $ INSTALLDIR=/usr/local xmake install" } - , {'a', "all", "k", nil, "Install all targets." } + {'o', "installdir", "kv", nil , "Set the install directory.", + "e.g.", + " $ xmake install -o /usr/local", + "or $ DESTDIR=/usr/local xmake install", + "or $ INSTALLDIR=/usr/local xmake install" } + , {'a', "all", "k", nil , "Install all targets." } , { } - , {nil, "target", "v", nil, "The target name. It will install all default targets if this parameter is not specified." } + , {nil, "target", "v", nil , "The target name. It will install all default targets if this parameter is not specified." + , values = function () return table.keys(import("core.project.project").targets()) end } } } diff --git a/xmake/actions/package/xmake.lua b/xmake/actions/package/xmake.lua index 2a9ad1e53..d50562ad0 100644 --- a/xmake/actions/package/xmake.lua +++ b/xmake/actions/package/xmake.lua @@ -41,10 +41,11 @@ task("package") -- options , options = { - {'o', "outputdir", "kv", nil, "Set the output directory." } - , {'a', "all", "k", nil, "Package all targets." } + {'o', "outputdir", "kv", nil , "Set the output directory." } + , {'a', "all", "k", nil , "Package all targets." } , {} - , {nil, "target", "v", nil, "The target name. It will package all default targets if this parameter is not specified." } + , {nil, "target", "v", nil , "The target name. It will package all default targets if this parameter is not specified." + , values = function () return table.keys(import("core.project.project").targets()) end } } } diff --git a/xmake/actions/run/xmake.lua b/xmake/actions/run/xmake.lua index eb4666d1b..6daf6f328 100644 --- a/xmake/actions/run/xmake.lua +++ b/xmake/actions/run/xmake.lua @@ -41,15 +41,16 @@ task("run") -- options , options = { - {'d', "debug", "k", nil, "Run and debug the given target." } - , {'a', "all", "k", nil, "Run all targets." } - , {'w', "workdir", "kv", nil, "Work directory of running targets, default is folder of targetfile", - "e.g.", - " --workdir=.", - " --workdir=`pwd`" } - , {} - , {nil, "target", "v", nil, "The target name. It will run all default targets if this parameter is not specified." } - , {nil, "arguments", "vs", nil, "The target arguments" } + {'d', "debug", "k", nil , "Run and debug the given target." } + , {'a', "all", "k", nil , "Run all targets." } + , {'w', "workdir", "kv", nil , "Work directory of running targets, default is folder of targetfile", + "e.g.", + " --workdir=.", + " --workdir=`pwd`" } + , {} + , {nil, "target", "v", nil , "The target name. It will run all default targets if this parameter is not specified." + , values = function () return table.keys(import("core.project.project").targets()) end } + , {nil, "arguments", "vs", nil , "The target arguments" } } } diff --git a/xmake/actions/uninstall/xmake.lua b/xmake/actions/uninstall/xmake.lua index c4672fad5..511b61e9e 100644 --- a/xmake/actions/uninstall/xmake.lua +++ b/xmake/actions/uninstall/xmake.lua @@ -41,17 +41,18 @@ task("uninstall") -- options , options = { - {nil, "installdir", "kv", nil, "Set the install directory.", - "e.g.", - " $ xmake uninstall -o /usr/local", - "or $ DESTDIR=/usr/local xmake uninstall", - "or $ INSTALLDIR=/usr/local xmake uninstall" } - , {'p', "prefix", "kv", nil, "Set the prefix directory.", - "e.g.", - " $ xmake uninstall --prefix=local", - "or $ PREFIX=local xmake uninstall" } - , { } - , {nil, "target", "v", nil, "The target name. It will uninstall all default targets if this parameter is not specified." } + {nil, "installdir", "kv", nil , "Set the install directory.", + "e.g.", + " $ xmake uninstall -o /usr/local", + "or $ DESTDIR=/usr/local xmake uninstall", + "or $ INSTALLDIR=/usr/local xmake uninstall" } + , {'p', "prefix", "kv", nil , "Set the prefix directory.", + "e.g.", + " $ xmake uninstall --prefix=local", + "or $ PREFIX=local xmake uninstall" } + , { } + , {nil, "target", "v", nil , "The target name. It will uninstall all default targets if this parameter is not specified." + , values = function () return table.keys(import("core.project.project").targets()) end } } } diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index 6a99a1cb9..c16b400e8 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -804,11 +804,11 @@ function option.show_options(options, taskname) end -- append values - local values = opt.values + local values, ok = opt.values if type(values) == "function" then - values = values() + ok, values = pcall(values) end - if values then + if ok and values then for _, value in ipairs(table.wrap(values)) do table.insert(desp_strs, " - " .. tostring(value)) end diff --git a/xmake/core/base/text.lua b/xmake/core/base/text.lua index 965c1daa9..0583ce7c4 100644 --- a/xmake/core/base/text.lua +++ b/xmake/core/base/text.lua @@ -148,8 +148,9 @@ end -- -- 2 numbers - min and max width (nil for not set, eg: {nil, 50}); -- -- a number - width, num is equivalent to {num, num}; -- -- nil - no limit, equivalent to {nil, nil} --- -- "auto" - use remain space of console, only one "auto" colunm is allowed +-- -- "auto" - use remain space of console, only one "auto" column is allowed -- align = {"l", "r", "c"} -- align mode for each column, "left", "center" or "right" +-- -- or use a string for the whole table -- sep = "${dim} | ", -- table colunm sepertor, default is " | ", use "" to hide -- } -- priority of style and align: cell > row > col @@ -159,7 +160,7 @@ function text.table(data, opt) assert(data) -- init options - opt = opt or { patch_reset = false, ignore_unknown = true } + opt = opt or { ignore_unknown = true } data.sep = data.sep or " | " opt.patch_reset = false @@ -216,6 +217,12 @@ function text.table(data, opt) data.sep = style .. data.sep .. "${reset}" end + local align = "l" + if type(data.align) == "string" then + align = data.align + data.align = {} + end + local sep = colors.translate(data.sep, opt) local sep_len = #colors.ignore(data.sep, opt) @@ -247,7 +254,7 @@ function text.table(data, opt) end -- load align - cols[i].align = (data.align[i] or "l"):sub(1, 1):lower() + cols[i].align = (data.align[i] or align):sub(1, 1):lower() -- load style cols[i].style = data.style[i] or style end diff --git a/xmake/modules/private/utils/complete.lua b/xmake/modules/private/utils/complete.lua index a1e8480df..70c54e056 100644 --- a/xmake/modules/private/utils/complete.lua +++ b/xmake/modules/private/utils/complete.lua @@ -23,6 +23,11 @@ import("core.base.option") import("core.base.task") local use_spaces = true +local raw_words = {} +local word = "" +local position = 0 +local has_space = false +local reenter = false function _print_candidate(is_complate, ...) local candidate = format(...) @@ -63,12 +68,25 @@ function _complete_option(options, segs, name) local current_options = try { function() - return option.raw_parse(segs, options, { populate_defaults = false }) + return option.raw_parse(segs, options, { populate_defaults = false, allow_unknown = true }) end } -- current options is invalid if not current_options then return end + -- current context is wrong + if not reenter and (current_options.file or current_options.project) then + local args = {"lua", "--root", "private.utils.complete", tostring(position), use_spaces and "reenter" or "nospace-reenter", table.unpack(raw_words) } + if current_options.file then + table.insert(args, 3, "--file=" .. current_options.file) + end + if current_options.project then + table.insert(args, 3, "--project=" .. current_options.project) + end + os.execv("xmake", args) + return + end + local state = 0 if name == "-" or name == "--" then name = "" @@ -112,28 +130,7 @@ function _complete_option(options, segs, name) end end -function main(position, config_use_spaces, ...) - local words = {...} - if config_use_spaces == "nospace" then - use_spaces = false - else - table.insert(words, 1, config_use_spaces) - end - - local word = table.concat(words, " ") or "" - position = tonumber(position) or 0 - local has_space = word:endswith(" ") or position > #word - word = word:trim() - - if is_host("windows") then - if word:lower():startswith("xmake.exe") then - word = "xmake" .. word:sub(#"xmake.exe" + 1) - end - end - - if word:lower():startswith("xmake ") then - word = word:sub(#"xmake " + 1) - end +function _complete() local tasks = {} local shortnames = {} @@ -167,4 +164,42 @@ function main(position, config_use_spaces, ...) if not has_space then segs[#segs] = nil end _complete_option(tasks[task_name].options, segs, incomplete_option) +end + +function main(pos, config, ...) + + raw_words = {...} + local words = {...} + + local is_config = false + if config:find("nospace", 1, true) then + use_spaces = false + is_config = true + end + if config:find("reenter", 1, true) then + reenter = true + is_config = true + end + + if not is_config then + table.insert(words, 1, config) + end + + word = table.concat(words, " ") or "" + position = tonumber(pos) or 0 + has_space = word:endswith(" ") or position > #word + word = word:trim() + + -- normailize word to "xmake ..." + if is_host("windows") then + if word:lower():startswith("xmake.exe") then + word = "xmake" .. word:sub(#"xmake.exe" + 1) + end + end + + if word:lower():startswith("xmake ") then + word = word:sub(#"xmake " + 1) + end + + _complete() end \ No newline at end of file -- cgit v1.3.1 From 1e36d6b2290a8ad67c34b5a549dcbf87f99d31f1 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Fri, 17 Jan 2020 11:55:43 +0800 Subject: improve dump --- xmake/core/base/bytes.lua | 15 ++-- xmake/core/base/dump.lua | 58 +++------------- xmake/core/base/hashset.lua | 17 ++++- xmake/core/base/todisplay.lua | 114 +++++++++++++++++++++++++++++++ xmake/core/sandbox/modules/todisplay.lua | 23 +++++++ 5 files changed, 169 insertions(+), 58 deletions(-) create mode 100644 xmake/core/base/todisplay.lua create mode 100644 xmake/core/sandbox/modules/todisplay.lua diff --git a/xmake/core/base/bytes.lua b/xmake/core/base/bytes.lua index 894f701d4..bdf9b80d6 100644 --- a/xmake/core/base/bytes.lua +++ b/xmake/core/base/bytes.lua @@ -23,10 +23,11 @@ local bytes = bytes or {} local _instance = _instance or {} -- load modules -local bit = require('bit') -local ffi = require('ffi') -local os = require("base/os") -local utils = require("base/utils") +local bit = require('bit') +local ffi = require('ffi') +local os = require("base/os") +local utils = require("base/utils") +local todisplay = require("base/todisplay") -- define ffi interfaces ffi.cdef[[ @@ -409,8 +410,8 @@ function _instance:__concat(other) return new end --- tostring(bytes) -function _instance:__tostring() +-- todisplay(bytes) +function _instance:__todisplay() local parts = {} local size = self:size() if size > 8 then @@ -419,7 +420,7 @@ function _instance:__tostring() for i = 1, size do parts[i] = "0x" .. bit.tohex(self[i], 2) end - return " 8 and "..>" or ">") + return "bytes${reset}(" .. todisplay(self:size()) .. ") <${color.dump.number}" .. table.concat(parts, " ") .. (self:size() > 8 and "${reset} ..>" or "${reset}>") end -- new an bytes instance diff --git a/xmake/core/base/dump.lua b/xmake/core/base/dump.lua index 5e7ed9b8f..dbfdc2e3f 100644 --- a/xmake/core/base/dump.lua +++ b/xmake/core/base/dump.lua @@ -22,7 +22,8 @@ local dump = dump or {} -- load modules -local colors = require("base/colors") +local colors = require("base/colors") +local todisplay = require("base/todisplay") -- format string with theme colors function dump._format(fmtkey, fmtdefault, ...) @@ -48,15 +49,9 @@ end function dump._print_string(str, as_key) local quote = (not as_key) or (not str:match("^[a-zA-Z_][a-zA-Z0-9_]*$")) if quote then - io.write(dump._translate("${reset}${color.dump.string_quote}\"${reset}${color.dump.string}")) + io.write(dump._translate([[${reset}${color.dump.string_quote}"${reset}${color.dump.string}]]), str, dump._translate([[${reset}${color.dump.string_quote}"${reset}]])) else - io.write(dump._translate("${reset}${color.dump.string}")) - end - io.write(str) - if quote then - io.write(dump._translate("${reset}${color.dump.string_quote}\"${reset}")) - else - io.write(dump._translate("${reset}")) + io.write(dump._translate("${reset}${color.dump.string}"), str, dump._translate("${reset}")) end end @@ -67,49 +62,21 @@ end -- print number function dump._print_number(num) - io.write(dump._translate("${reset}${color.dump.number}"), tostring(num), dump._translate("${reset}")) + io.write(dump._translate(todisplay(num))) end -- print function function dump._print_function(func, as_key) - io.write(dump._translate("${reset}${color.dump.function}")) if as_key then - io.write(dump._format("text.dump.default_format", "%s", func)) + io.write(dump._translate("${reset}${color.dump.function}"), dump._format("text.dump.default_format", "%s", func), dump._translate("${reset}")) else - local funcinfo = debug.getinfo(func) - local srcinfo = funcinfo.short_src - if funcinfo.linedefined >= 0 then - srcinfo = srcinfo .. ":" .. funcinfo.linedefined - end - local funcname = funcinfo.name and (funcinfo.name .. " ") or "" - io.write(dump._translate("function ${bright}"), funcname, dump._translate("${reset}${dim}"), srcinfo) + io.write(dump._translate(todisplay(func))) end - io.write(dump._translate("${reset}")) end -- print value with default format function dump._print_default_scalar(value) - io.write(dump._translate("${reset}${color.dump.default}"), dump._format("text.dump.default_format", "%s", value), dump._translate("${reset}")) -end - --- print udata value with scalar format -function dump._print_udata_scalar(value) - local metatable = debug.getmetatable(value) - local tostringmethod = metatable and (rawget(metatable, "__todisplay") or rawget(metatable, "__tostring")) - if tostringmethod then - value = tostringmethod(value) - end - io.write(dump._translate("${reset}${color.dump.udata}"), dump._format("text.dump.udata_format", "%s", value), dump._translate("${reset}")) -end - --- print table value with scalar format -function dump._print_table_scalar(value) - local metatable = debug.getmetatable(value) - local tostringmethod = metatable and (rawget(metatable, "__todisplay") or rawget(metatable, "__tostring")) - if tostringmethod then - value = tostringmethod(value) - end - io.write(dump._translate("${reset}${color.dump.table}"), dump._format("text.dump.table_format", "%s", value), dump._translate("${reset}")) + io.write(dump._translate(todisplay(value))) end -- print scalar value @@ -122,10 +89,6 @@ function dump._print_scalar(value, as_key) dump._print_string(value, as_key) elseif type(value) == "function" then dump._print_function(value, as_key) - elseif type(value) == "userdata" then - dump._print_udata_scalar(value) - elseif type(value) == "table" then - dump._print_table_scalar(value) else dump._print_default_scalar(value) end @@ -290,10 +253,7 @@ function dump._print_table(value, first_indent, remain_indent, printed_set) local metatable = debug.getmetatable(value) 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 - return dump._print_table_scalar(strrep) - end + return dump._print_default_scalar(value) end local inner_indent = remain_indent .. " " diff --git a/xmake/core/base/hashset.lua b/xmake/core/base/hashset.lua index 93acbee89..49e4f094e 100644 --- a/xmake/core/base/hashset.lua +++ b/xmake/core/base/hashset.lua @@ -23,10 +23,23 @@ local hashset = hashset or {} local hashset_impl = hashset.__index or {} -- load modules -local table = require("base/table") +local table = require("base/table") +local todisplay = require("base/todisplay") -- representaion for nil key -hashset._NIL = setmetatable({}, {__tostring = function() return "nil" end }) +hashset._NIL = setmetatable({}, { __todisplay = function() return "${color.dump.keyword}nil${reset}" end, __tostring = function() return "${color.dump.keyword}nil${reset}" end }) + +function hashset:__todisplay() + return string.format("hashset${reset}(%s) {%s}", todisplay(self._SIZE), table.concat(table.imap(table.keys(self._DATA), function (i, k) + if i > 10 then + return nil + elseif i == 10 and self._SIZE ~= 10 then + return "..." + else + return todisplay(k) + end + end), ", ")) +end function hashset._to_key(key) if key == nil then diff --git a/xmake/core/base/todisplay.lua b/xmake/core/base/todisplay.lua new file mode 100644 index 000000000..1a2d59d43 --- /dev/null +++ b/xmake/core/base/todisplay.lua @@ -0,0 +1,114 @@ +--!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 todisplay.lua +-- + +-- define module +local todisplay = todisplay or {} + +-- load modules +local colors = require("base/colors") + +-- format string with theme colors +function todisplay._format(fmtkey, fmtdefault, ...) + local theme = colors.theme() + return string.format((theme and theme:get(fmtkey)) or fmtdefault, ...) +end + +-- print keyword +function todisplay._print_keyword(keyword) + return string.format("${reset}${color.dump.keyword}%s${reset}", keyword) +end + +-- print string +function todisplay._print_string(str) + return string.format([[${reset}${color.dump.string_quote}"${reset}${color.dump.string}%s${reset}${color.dump.string_quote}"${reset}]], str) +end + +-- print number +function todisplay._print_number(num) + return string.format("${reset}${color.dump.number}%g${reset}", num) +end + +-- print function +function todisplay._print_function(func) + local funcinfo = debug.getinfo(func) + local srcinfo = funcinfo.short_src + if funcinfo.linedefined >= 0 then + srcinfo = srcinfo .. ":" .. funcinfo.linedefined + end + local funcname = funcinfo.name and (funcinfo.name .. " ") or "" + return string.format("${reset}${color.dump.function}function ${bright}%s${reset}${dim}%s${reset}", funcname, srcinfo) +end + +-- print value with default format +function todisplay._print_default_scalar(value, style, formatkey) + local metatable = debug.getmetatable(value) + if metatable then + local __todisplay = rawget(metatable, "__todisplay") + local __tostring = rawget(metatable, "__tostring") + if __todisplay then + local ok, str = pcall(__todisplay, value) + if ok then + value = str + -- disable format + formatkey = nil + end + elseif __tostring then + local ok, str = pcall(__todisplay, value) + if ok then + value = str + end + end + end + if formatkey then + value = todisplay._format(formatkey, "%s", value) + end + return string.format("${reset}%s%s${reset}", style, value) +end + +-- print udata value with scalar format +function todisplay._print_udata_scalar(value) + return todisplay._print_default_scalar(value, "${color.dump.udata}", "text.dump.udata_format") +end + +-- print table value with scalar format +function todisplay._print_table_scalar(value) + return todisplay._print_default_scalar(value, "${color.dump.table}", "text.dump.table_format") +end + +-- print scalar value +function todisplay._print_scalar(value) + if type(value) == "nil" or type(value) == "boolean" then + return todisplay._print_keyword(value) + elseif type(value) == "number" then + return todisplay._print_number(value) + elseif type(value) == "string" then + return todisplay._print_string(value) + elseif type(value) == "function" then + return todisplay._print_function(value) + elseif type(value) == "userdata" then + return todisplay._print_udata_scalar(value) + elseif type(value) == "table" then + return todisplay._print_table_scalar(value) + else + return todisplay._print_default_scalar(value, "${color.dump.default}", "text.dump.default_format") + end +end + +return todisplay._print_scalar diff --git a/xmake/core/sandbox/modules/todisplay.lua b/xmake/core/sandbox/modules/todisplay.lua new file mode 100644 index 000000000..97824c9bc --- /dev/null +++ b/xmake/core/sandbox/modules/todisplay.lua @@ -0,0 +1,23 @@ +--!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 ruki +-- @file todisplay.lua +-- + +-- load module +return require("base/todisplay") + -- cgit v1.3.1 From 9ec8c57fe8aebcc6212543ce03392d13a865e802 Mon Sep 17 00:00:00 2001 From: Opportunity Date: Fri, 17 Jan 2020 11:58:19 +0800 Subject: use to display --- xmake/core/base/bytes.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xmake/core/base/bytes.lua b/xmake/core/base/bytes.lua index bdf9b80d6..e2749f64e 100644 --- a/xmake/core/base/bytes.lua +++ b/xmake/core/base/bytes.lua @@ -433,8 +433,8 @@ setmetatable(bytes, { __call = function (_, ...) return bytes.new(...) end, - __tostring = function() - return "" + __todisplay = function() + return todisplay(bytes.new) end }) -- cgit v1.3.1 From 8098258ec6c24647698d64a38df1c89c3845e3bb Mon Sep 17 00:00:00 2001 From: Opportunity Date: Fri, 17 Jan 2020 12:14:05 +0800 Subject: fix color --- xmake/core/base/bytes.lua | 8 ++++---- xmake/core/base/hashset.lua | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xmake/core/base/bytes.lua b/xmake/core/base/bytes.lua index e2749f64e..9b32b0de4 100644 --- a/xmake/core/base/bytes.lua +++ b/xmake/core/base/bytes.lua @@ -214,7 +214,7 @@ function _instance:dump() if p + 0x20 <= e then -- dump offset - line = line .. string.format("${yellow}%08X ${green}", p) + line = line .. string.format("${color.dump.anchor}%08X ${color.dump.number}", p) -- dump data for i = 0, 0x20 - 1 do @@ -228,7 +228,7 @@ function _instance:dump() line = line .. " " -- dump characters - line = line .. "${magenta}" + line = line .. "${color.dump.string}" for i = 0, 0x20 - 1 do local v = self[p + i + 1] if v > 0x1f and v < 0x7f then @@ -251,7 +251,7 @@ function _instance:dump() local padding = n - 0x20 -- dump offset - line = line .. string.format("${yellow}%08X ${green}", p) + line = line .. string.format("${color.dump.anchor}%08X ${color.dump.number}", p) if padding >= 9 then padding = padding - 9 end @@ -278,7 +278,7 @@ function _instance:dump() end -- dump characters - line = line .. "${magenta}" + line = line .. "${color.dump.string}" for i = 0, left - 1 do local v = self[p + i + 1] if v > 0x1f and v < 0x7f then diff --git a/xmake/core/base/hashset.lua b/xmake/core/base/hashset.lua index 49e4f094e..7122c1b3f 100644 --- a/xmake/core/base/hashset.lua +++ b/xmake/core/base/hashset.lua @@ -27,7 +27,7 @@ local table = require("base/table") local todisplay = require("base/todisplay") -- representaion for nil key -hashset._NIL = setmetatable({}, { __todisplay = function() return "${color.dump.keyword}nil${reset}" end, __tostring = function() return "${color.dump.keyword}nil${reset}" end }) +hashset._NIL = setmetatable({}, { __todisplay = function() return "${color.dump.keyword}nil${reset}" end, __tostring = function() return "symbol(nil)" end }) function hashset:__todisplay() return string.format("hashset${reset}(%s) {%s}", todisplay(self._SIZE), table.concat(table.imap(table.keys(self._DATA), function (i, k) -- cgit v1.3.1 From dc25ac7a87256a0470f523017ee790b29e9b6f2d Mon Sep 17 00:00:00 2001 From: Opportunity Date: Fri, 17 Jan 2020 13:34:23 +0800 Subject: add iterator --- xmake/core/base/hashset.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/xmake/core/base/hashset.lua b/xmake/core/base/hashset.lua index 7122c1b3f..a30bd69f7 100644 --- a/xmake/core/base/hashset.lua +++ b/xmake/core/base/hashset.lua @@ -27,7 +27,7 @@ local table = require("base/table") local todisplay = require("base/todisplay") -- representaion for nil key -hashset._NIL = setmetatable({}, { __todisplay = function() return "${color.dump.keyword}nil${reset}" end, __tostring = function() return "symbol(nil)" end }) +hashset._NIL = setmetatable({}, { __todisplay = function() return "${reset}${color.dump.keyword}nil${reset}" end, __tostring = function() return "symbol(nil)" end }) function hashset:__todisplay() return string.format("hashset${reset}(%s) {%s}", todisplay(self._SIZE), table.concat(table.imap(table.keys(self._DATA), function (i, k) @@ -108,6 +108,19 @@ function hashset_impl:to_array() return result end +-- iterate keys of hashtable +-- for _, key in instance:keys() do ... end +function hashset_impl:keys() + return function (table, key) + local k, _ = next(table._DATA, key) + if k == hashset._NIL then + return k, nil + else + return k, k + end + end, self, nil +end + -- get size of hashset function hashset_impl:size() return self._SIZE -- cgit v1.3.1