diff options
| author | ruki <[email protected]> | 2020-01-17 22:15:32 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2020-01-17 22:15:32 +0800 |
| commit | a859e101ee88ec25fd948a08d2a02e7621241e7f (patch) | |
| tree | b6558e583ac20627222139a28f2c9c87c3af3c19 /xmake/core | |
| parent | ffce1ede94e7873e7ab248b05d468e84f6c205d0 (diff) | |
| parent | dc25ac7a87256a0470f523017ee790b29e9b6f2d (diff) | |
Merge pull request #667 from OpportunityLiu/options
Improve to parse command line options
Diffstat (limited to 'xmake/core')
| -rw-r--r-- | xmake/core/base/bytes.lua | 27 | ||||
| -rw-r--r-- | xmake/core/base/cli.lua | 122 | ||||
| -rw-r--r-- | xmake/core/base/colors.lua | 2 | ||||
| -rw-r--r-- | xmake/core/base/dump.lua | 54 | ||||
| -rw-r--r-- | xmake/core/base/hashset.lua | 30 | ||||
| -rw-r--r-- | xmake/core/base/option.lua | 862 | ||||
| -rw-r--r-- | xmake/core/base/serialize.lua | 6 | ||||
| -rw-r--r-- | xmake/core/base/task.lua | 56 | ||||
| -rw-r--r-- | xmake/core/base/text.lua | 390 | ||||
| -rw-r--r-- | xmake/core/base/todisplay.lua | 114 | ||||
| -rw-r--r-- | xmake/core/base/utils.lua | 32 | ||||
| -rw-r--r-- | xmake/core/main.lua | 40 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/cli.lua | 38 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/hashset.lua | 8 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/option.lua | 4 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/base/text.lua | 38 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/todisplay.lua | 23 |
17 files changed, 1115 insertions, 731 deletions
diff --git a/xmake/core/base/bytes.lua b/xmake/core/base/bytes.lua index 894f701d4..9b32b0de4 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[[ @@ -213,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 @@ -227,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 @@ -250,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 @@ -277,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 @@ -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 "<bytes(" .. self:size() .. "): " .. table.concat(parts, " ") .. (self:size() > 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 @@ -432,8 +433,8 @@ setmetatable(bytes, { __call = function (_, ...) return bytes.new(...) end, - __tostring = function() - return "<bytes>" + __todisplay = function() + return todisplay(bytes.new) end }) diff --git a/xmake/core/base/cli.lua b/xmake/core/base/cli.lua new file mode 100644 index 000000000..ea328c22e --- /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", short and ("-" .. key) or ("--" .. key), argv, argi, { key = key, value = true, short = short or false }) +end + +function cli._make_option(key, value, short, argv, argi) + return cli._make_segment("option", short and ("-" .. key .. " " .. value) or ("--" .. key .. "=" .. value), argv, argi, { key = key, value = value, short = short or false }) +end + +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 diff --git a/xmake/core/base/colors.lua b/xmake/core/base/colors.lua index d74a0c411..c49191cd1 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 -- diff --git a/xmake/core/base/dump.lua b/xmake/core/base/dump.lua index 5b61ba276..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,39 +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(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) - 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) - io.write(dump._translate("${reset}${color.dump.table}"), dump._format("text.dump.table_format", "%s", value), dump._translate("${reset}")) +function dump._print_default_scalar(value) + io.write(dump._translate(todisplay(value))) end -- print scalar value @@ -112,12 +89,8 @@ 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(value) + dump._print_default_scalar(value) end end @@ -278,12 +251,9 @@ 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 - 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..a30bd69f7 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 "${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) + 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 @@ -95,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 diff --git a/xmake/core/base/option.lua b/xmake/core/base/option.lua index dd9df2aa8..c16b400e8 100644 --- a/xmake/core/base/option.lua +++ b/xmake/core/base/option.lua @@ -22,13 +22,10 @@ local option = option or {} -- load modules +local cli = require("base/cli") local table = require("base/table") local colors = require("base/colors") - --- ifelse, a? b : c -function option._ifelse(a, b, c) - if a then return b else return c end -end +local text = require("base/text") -- translate the menu function option._translate(menu) @@ -68,51 +65,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) @@ -146,18 +98,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 @@ -180,377 +124,110 @@ 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 - - -- --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 key - if prefix and #key == 0 then - option.show_menu(context.taskname) - return false, "invalid option: " .. arg - 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 + -- check command + if xmake._COMMAND then - -- 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)) + -- find the current task + for taskname, taskinfo in pairs(main.tasks) do - -- 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 + -- ok? + if taskname == xmake._COMMAND or taskinfo.shortname == xmake._COMMAND then + -- save this task + context.taskname = taskname + 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 + -- invalid task + return false, "invalid task: " .. xmake._COMMAND end 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(xmake._COMMAND_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 + -- save option + 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 + 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 - -- 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 - - -- 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 +236,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 +268,48 @@ 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 - end + 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, "invalid argument: " .. arg.value + end + 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 +318,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 +355,7 @@ function option.taskmenu(task) -- check assert(option._MENU) - + -- the current task task = task or option.taskname() or "main" @@ -749,32 +456,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 @@ -902,7 +584,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() @@ -954,79 +636,58 @@ 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)))) - - -- the padding spaces - local padding = 42 - - -- get width of console - local console_width = os.getwinsize()["width"] + table.insert(tablecontent, {}) + 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(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 .. " " - 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)) + local taskline = string.format(" %s%s", + taskinfo.shortname and (taskinfo.shortname .. ", ") or " ", + taskname) + table.insert(tablecontent, {taskline, taskinfo.description or ""}) end end + + -- 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 if main.options then option.show_options(main.options, "build") end -end +end -- show the options menu function option.show_options(options, taskname) @@ -1034,9 +695,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 @@ -1044,7 +702,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 @@ -1055,156 +713,120 @@ 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 sub-command section - -- - -- @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 .. "): ")) - end - - -- 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 - - -- append the name - if name then - if mode == "v" then - option_info = option_info .. " " .. name - elseif mode == "vs" then - option_info = option_info .. " " .. name .. " ..." - 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()) - end - elseif mode == "v" or mode == "vs" then - option_info = option_info .. " ..." - end - -- append spaces - for i = (#option_info), padding do - option_info = option_info .. " " - end + -- 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}"}}) + elseif opt[3] == nil then - -- append color - option_info = colors.translate("${color.menu.option.name}" .. option_info .. "${clear}") + -- insert empty line + table.insert(tablecontent, {}) + else - -- get width of console - local console_width = os.getwinsize()["width"] + -- init the option info + local option_info - -- 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) - end - - -- append the default value - if default then - local defaultval = tostring(default) - if type(default) == "boolean" then - defaultval = option._ifelse(default, "y", "n") + -- 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 = " " 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) - 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() + -- 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 - -- the description is string? - if type(description) == "string" then + -- get description + local description = table.move(opt, 5, table.maxn(opt), 1, {}) + if #description == 0 then + description[1] = "" + end - -- make spaces - local spaces = "" - for i = 0, padding do - spaces = spaces .. " " + -- transform description + local desp_strs = {} + for _, v in ipairs(description) do + if type(v) == "function" then + v = v() 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)) + 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 - - -- print 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 .. " " + -- 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 - -- print this value - io.print(option._inwidth_append(spaces, " - " .. tostring(value), padding + 1, console_width)) + -- append values + local values, ok = opt.values + if type(values) == "function" then + ok, values = pcall(values) + end + if ok and values then + for _, value in ipairs(table.wrap(values)) do + table.insert(desp_strs, " - " .. tostring(value)) + end end + + -- insert row + table.insert(tablecontent, {option_info, desp_strs}) end 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/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) diff --git a/xmake/core/base/task.lua b/xmake/core/base/task.lua index 03b87982d..a3aadb7b0 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 -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." } + , {} + , {'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/base/text.lua b/xmake/core/base/text.lua new file mode 100644 index 000000000..0583ce7c4 --- /dev/null +++ b/xmake/core/base/text.lua @@ -0,0 +1,390 @@ +--!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" 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 +-- @param opt options for color rendering and word warpping +function text.table(data, opt) + + assert(data) + + -- init options + opt = opt or { ignore_unknown = true } + data.sep = data.sep or " | " + opt.patch_reset = false + + -- 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 = {} + 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) + + -- 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 align):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 d_row = data[i] or {} + 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 + + -- 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 + + 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/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/base/utils.lua b/xmake/core/base/utils.lua index d85d1ecd9..dda5077d3 100644 --- a/xmake/core/base/utils.lua +++ b/xmake/core/base/utils.lua @@ -28,8 +28,9 @@ 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 value +-- dump values function utils.dump(...) if option.get("quiet") then return ... @@ -42,32 +43,23 @@ function utils.dump(...) local info = debug.getinfo(2) local line = info.currentline if not line or line < 0 then line = info.linedefined end - io.write(string.format("dump form %s %s:%s\n", info.name or "<anonymous>", info.source, line)) + io.write(string.format("dump from %s %s:%s\n", info.name or "<anonymous>", info.source, line)) end local values = table.pack(...) 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 @@ -327,5 +319,13 @@ function utils.confirm(opt) return confirm end +function utils.table(data, opt) + utils.printf(text.table(data, opt)) +end + +function utils.vtable(data, opt) + utils.vprintf(text.table(data, opt)) +end + -- return module return utils diff --git a/xmake/core/main.lua b/xmake/core/main.lua index 4aa1c0d45..1078175d6 100644 --- a/xmake/core/main.lua +++ b/xmake/core/main.lua @@ -117,14 +117,30 @@ function main._find_root(projectfile) return projectfile end +function main._basicparse() + + -- check command + 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, #xmake._ARGV, 1, {}) + else + xmake._COMMAND_ARGV = xmake._ARGV + end + + -- parse options + return option.parse(xmake._COMMAND_ARGV, task.common_options(), { allow_unknown = true }) +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") + -- get project directory and project file from the argument option + 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 @@ -146,7 +162,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 @@ -165,16 +181,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/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 + + 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") + |
