summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2020-01-17 22:15:32 +0800
committerGitHub <[email protected]>2020-01-17 22:15:32 +0800
commita859e101ee88ec25fd948a08d2a02e7621241e7f (patch)
treeb6558e583ac20627222139a28f2c9c87c3af3c19
parentffce1ede94e7873e7ab248b05d468e84f6c205d0 (diff)
parentdc25ac7a87256a0470f523017ee790b29e9b6f2d (diff)
Merge pull request #667 from OpportunityLiu/options
Improve to parse command line options
-rwxr-xr-xscripts/get.sh4
-rw-r--r--scripts/register-completions.bash2
-rw-r--r--scripts/register-completions.ps12
-rw-r--r--scripts/register-completions.zsh2
-rw-r--r--tests/cli/test.lua139
-rw-r--r--xmake/actions/build/xmake.lua29
-rw-r--r--xmake/actions/clean/xmake.lua5
-rw-r--r--xmake/actions/config/xmake.lua11
-rw-r--r--xmake/actions/install/xmake.lua15
-rw-r--r--xmake/actions/package/xmake.lua7
-rw-r--r--xmake/actions/run/xmake.lua19
-rw-r--r--xmake/actions/uninstall/xmake.lua23
-rw-r--r--xmake/core/base/bytes.lua27
-rw-r--r--xmake/core/base/cli.lua122
-rw-r--r--xmake/core/base/colors.lua2
-rw-r--r--xmake/core/base/dump.lua54
-rw-r--r--xmake/core/base/hashset.lua30
-rw-r--r--xmake/core/base/option.lua862
-rw-r--r--xmake/core/base/serialize.lua6
-rw-r--r--xmake/core/base/task.lua56
-rw-r--r--xmake/core/base/text.lua390
-rw-r--r--xmake/core/base/todisplay.lua114
-rw-r--r--xmake/core/base/utils.lua32
-rw-r--r--xmake/core/main.lua40
-rw-r--r--xmake/core/sandbox/modules/import/core/base/cli.lua38
-rw-r--r--xmake/core/sandbox/modules/import/core/base/hashset.lua8
-rw-r--r--xmake/core/sandbox/modules/import/core/base/option.lua4
-rw-r--r--xmake/core/sandbox/modules/import/core/base/text.lua38
-rw-r--r--xmake/core/sandbox/modules/todisplay.lua23
-rw-r--r--xmake/modules/private/utils/complete.lua81
-rw-r--r--xmake/plugins/lua/xmake.lua30
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}" }
}
}