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 | |
| parent | ffce1ede94e7873e7ab248b05d468e84f6c205d0 (diff) | |
| parent | dc25ac7a87256a0470f523017ee790b29e9b6f2d (diff) | |
Merge pull request #667 from OpportunityLiu/options
Improve to parse command line options
31 files changed, 1398 insertions, 817 deletions
diff --git a/scripts/get.sh b/scripts/get.sh index c57f3bf4f..a0d49c528 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}" "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 9d5865cd6..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 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.ps1 b/scripts/register-completions.ps1 index bae3281f8..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" "$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}" ) } 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/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/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") + diff --git a/xmake/modules/private/utils/complete.lua b/xmake/modules/private/utils/complete.lua index 35d9c67af..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) + 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 diff --git a/xmake/plugins/lua/xmake.lua b/xmake/plugins/lua/xmake.lua index 1fc96c724..e264ba14d 100644 --- a/xmake/plugins/lua/xmake.lua +++ b/xmake/plugins/lua/xmake.lua @@ -53,18 +53,32 @@ task("lua") -- get script 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 + 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})(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(option.get("arguments") or {})) + import("scripts." .. script, {anonymous = true})(table.unpack(args, 1, args.n)) else -- attempt to find the builtin module @@ -79,13 +93,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(table.unpack(args, 1, args.n))) 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})(table.unpack(args, 1, args.n))) 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 +129,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}" } } } |
