summaryrefslogtreecommitdiff
path: root/src/luarocks/vendor
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
committerMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
commit5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch)
treedeca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/vendor
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/vendor')
-rw-r--r--src/luarocks/vendor/argparse.lua2103
-rw-r--r--src/luarocks/vendor/dkjson.lua749
2 files changed, 2852 insertions, 0 deletions
diff --git a/src/luarocks/vendor/argparse.lua b/src/luarocks/vendor/argparse.lua
new file mode 100644
index 0000000..2c2585d
--- /dev/null
+++ b/src/luarocks/vendor/argparse.lua
@@ -0,0 +1,2103 @@
+-- The MIT License (MIT)
+
+-- Copyright (c) 2013 - 2018 Peter Melnichenko
+-- 2019 Paul Ouellette
+
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+
+-- The above copyright notice and this permission notice shall be included in all
+-- copies or substantial portions of the Software.
+
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local function deep_update(t1, t2)
+ for k, v in pairs(t2) do
+ if type(v) == "table" then
+ v = deep_update({}, v)
+ end
+
+ t1[k] = v
+ end
+
+ return t1
+end
+
+-- A property is a tuple {name, callback}.
+-- properties.args is number of properties that can be set as arguments
+-- when calling an object.
+local function class(prototype, properties, parent)
+ -- Class is the metatable of its instances.
+ local cl = {}
+ cl.__index = cl
+
+ if parent then
+ cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype)
+ else
+ cl.__prototype = prototype
+ end
+
+ if properties then
+ local names = {}
+
+ -- Create setter methods and fill set of property names.
+ for _, property in ipairs(properties) do
+ local name, callback = property[1], property[2]
+
+ cl[name] = function(self, value)
+ if not callback(self, value) then
+ self["_" .. name] = value
+ end
+
+ return self
+ end
+
+ names[name] = true
+ end
+
+ function cl.__call(self, ...)
+ -- When calling an object, if the first argument is a table,
+ -- interpret keys as property names, else delegate arguments
+ -- to corresponding setters in order.
+ if type((...)) == "table" then
+ for name, value in pairs((...)) do
+ if names[name] then
+ self[name](self, value)
+ end
+ end
+ else
+ local nargs = select("#", ...)
+
+ for i, property in ipairs(properties) do
+ if i > nargs or i > properties.args then
+ break
+ end
+
+ local arg = select(i, ...)
+
+ if arg ~= nil then
+ self[property[1]](self, arg)
+ end
+ end
+ end
+
+ return self
+ end
+ end
+
+ -- If indexing class fails, fallback to its parent.
+ local class_metatable = {}
+ class_metatable.__index = parent
+
+ function class_metatable.__call(self, ...)
+ -- Calling a class returns its instance.
+ -- Arguments are delegated to the instance.
+ local object = deep_update({}, self.__prototype)
+ setmetatable(object, self)
+ return object(...)
+ end
+
+ return setmetatable(cl, class_metatable)
+end
+
+local function typecheck(name, types, value)
+ for _, type_ in ipairs(types) do
+ if type(value) == type_ then
+ return true
+ end
+ end
+
+ error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value)))
+end
+
+local function typechecked(name, ...)
+ local types = {...}
+ return {name, function(_, value) typecheck(name, types, value) end}
+end
+
+local multiname = {"name", function(self, value)
+ typecheck("name", {"string"}, value)
+
+ for alias in value:gmatch("%S+") do
+ self._name = self._name or alias
+ table.insert(self._aliases, alias)
+ table.insert(self._public_aliases, alias)
+ -- If alias contains '_', accept '-' also.
+ if alias:find("_", 1, true) then
+ table.insert(self._aliases, (alias:gsub("_", "-")))
+ end
+ end
+
+ -- Do not set _name as with other properties.
+ return true
+end}
+
+local multiname_hidden = {"hidden_name", function(self, value)
+ typecheck("hidden_name", {"string"}, value)
+
+ for alias in value:gmatch("%S+") do
+ table.insert(self._aliases, alias)
+ if alias:find("_", 1, true) then
+ table.insert(self._aliases, (alias:gsub("_", "-")))
+ end
+ end
+
+ return true
+end}
+
+local function parse_boundaries(str)
+ if tonumber(str) then
+ return tonumber(str), tonumber(str)
+ end
+
+ if str == "*" then
+ return 0, math.huge
+ end
+
+ if str == "+" then
+ return 1, math.huge
+ end
+
+ if str == "?" then
+ return 0, 1
+ end
+
+ if str:match "^%d+%-%d+$" then
+ local min, max = str:match "^(%d+)%-(%d+)$"
+ return tonumber(min), tonumber(max)
+ end
+
+ if str:match "^%d+%+$" then
+ local min = str:match "^(%d+)%+$"
+ return tonumber(min), math.huge
+ end
+end
+
+local function boundaries(name)
+ return {name, function(self, value)
+ typecheck(name, {"number", "string"}, value)
+
+ local min, max = parse_boundaries(value)
+
+ if not min then
+ error(("bad property '%s'"):format(name))
+ end
+
+ self["_min" .. name], self["_max" .. name] = min, max
+ end}
+end
+
+local actions = {}
+
+local option_action = {"action", function(_, value)
+ typecheck("action", {"function", "string"}, value)
+
+ if type(value) == "string" and not actions[value] then
+ error(("unknown action '%s'"):format(value))
+ end
+end}
+
+local option_init = {"init", function(self)
+ self._has_init = true
+end}
+
+local option_default = {"default", function(self, value)
+ if type(value) ~= "string" then
+ self._init = value
+ self._has_init = true
+ return true
+ end
+end}
+
+local add_help = {"add_help", function(self, value)
+ typecheck("add_help", {"boolean", "string", "table"}, value)
+
+ if self._help_option_idx then
+ table.remove(self._options, self._help_option_idx)
+ self._help_option_idx = nil
+ end
+
+ if value then
+ local help = self:flag()
+ :description "Show this help message and exit."
+ :action(function()
+ print(self:get_help())
+ os.exit(0)
+ end)
+
+ if value ~= true then
+ help = help(value)
+ end
+
+ if not help._name then
+ help "-h" "--help"
+ end
+
+ self._help_option_idx = #self._options
+ end
+end}
+
+local Parser = class({
+ _arguments = {},
+ _options = {},
+ _commands = {},
+ _mutexes = {},
+ _groups = {},
+ _require_command = true,
+ _handle_options = true
+}, {
+ args = 3,
+ typechecked("name", "string"),
+ typechecked("description", "string"),
+ typechecked("epilog", "string"),
+ typechecked("usage", "string"),
+ typechecked("help", "string"),
+ typechecked("require_command", "boolean"),
+ typechecked("handle_options", "boolean"),
+ typechecked("action", "function"),
+ typechecked("command_target", "string"),
+ typechecked("help_vertical_space", "number"),
+ typechecked("usage_margin", "number"),
+ typechecked("usage_max_width", "number"),
+ typechecked("help_usage_margin", "number"),
+ typechecked("help_description_margin", "number"),
+ typechecked("help_max_width", "number"),
+ add_help
+})
+
+local Command = class({
+ _aliases = {},
+ _public_aliases = {}
+}, {
+ args = 3,
+ multiname,
+ typechecked("description", "string"),
+ typechecked("epilog", "string"),
+ multiname_hidden,
+ typechecked("summary", "string"),
+ typechecked("target", "string"),
+ typechecked("usage", "string"),
+ typechecked("help", "string"),
+ typechecked("require_command", "boolean"),
+ typechecked("handle_options", "boolean"),
+ typechecked("action", "function"),
+ typechecked("command_target", "string"),
+ typechecked("help_vertical_space", "number"),
+ typechecked("usage_margin", "number"),
+ typechecked("usage_max_width", "number"),
+ typechecked("help_usage_margin", "number"),
+ typechecked("help_description_margin", "number"),
+ typechecked("help_max_width", "number"),
+ typechecked("hidden", "boolean"),
+ add_help
+}, Parser)
+
+local Argument = class({
+ _minargs = 1,
+ _maxargs = 1,
+ _mincount = 1,
+ _maxcount = 1,
+ _defmode = "unused",
+ _show_default = true
+}, {
+ args = 5,
+ typechecked("name", "string"),
+ typechecked("description", "string"),
+ option_default,
+ typechecked("convert", "function", "table"),
+ boundaries("args"),
+ typechecked("target", "string"),
+ typechecked("defmode", "string"),
+ typechecked("show_default", "boolean"),
+ typechecked("argname", "string", "table"),
+ typechecked("choices", "table"),
+ typechecked("hidden", "boolean"),
+ option_action,
+ option_init
+})
+
+local Option = class({
+ _aliases = {},
+ _public_aliases = {},
+ _mincount = 0,
+ _overwrite = true
+}, {
+ args = 6,
+ multiname,
+ typechecked("description", "string"),
+ option_default,
+ typechecked("convert", "function", "table"),
+ boundaries("args"),
+ boundaries("count"),
+ multiname_hidden,
+ typechecked("target", "string"),
+ typechecked("defmode", "string"),
+ typechecked("show_default", "boolean"),
+ typechecked("overwrite", "boolean"),
+ typechecked("argname", "string", "table"),
+ typechecked("choices", "table"),
+ typechecked("hidden", "boolean"),
+ option_action,
+ option_init
+}, Argument)
+
+function Parser:_inherit_property(name, default)
+ local element = self
+
+ while true do
+ local value = element["_" .. name]
+
+ if value ~= nil then
+ return value
+ end
+
+ if not element._parent then
+ return default
+ end
+
+ element = element._parent
+ end
+end
+
+function Argument:_get_argument_list()
+ local buf = {}
+ local i = 1
+
+ while i <= math.min(self._minargs, 3) do
+ local argname = self:_get_argname(i)
+
+ if self._default and self._defmode:find "a" then
+ argname = "[" .. argname .. "]"
+ end
+
+ table.insert(buf, argname)
+ i = i+1
+ end
+
+ while i <= math.min(self._maxargs, 3) do
+ table.insert(buf, "[" .. self:_get_argname(i) .. "]")
+ i = i+1
+
+ if self._maxargs == math.huge then
+ break
+ end
+ end
+
+ if i < self._maxargs then
+ table.insert(buf, "...")
+ end
+
+ return buf
+end
+
+function Argument:_get_usage()
+ local usage = table.concat(self:_get_argument_list(), " ")
+
+ if self._default and self._defmode:find "u" then
+ if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then
+ usage = "[" .. usage .. "]"
+ end
+ end
+
+ return usage
+end
+
+function actions.store_true(result, target)
+ result[target] = true
+end
+
+function actions.store_false(result, target)
+ result[target] = false
+end
+
+function actions.store(result, target, argument)
+ result[target] = argument
+end
+
+function actions.count(result, target, _, overwrite)
+ if not overwrite then
+ result[target] = result[target] + 1
+ end
+end
+
+function actions.append(result, target, argument, overwrite)
+ result[target] = result[target] or {}
+ table.insert(result[target], argument)
+
+ if overwrite then
+ table.remove(result[target], 1)
+ end
+end
+
+function actions.concat(result, target, arguments, overwrite)
+ if overwrite then
+ error("'concat' action can't handle too many invocations")
+ end
+
+ result[target] = result[target] or {}
+
+ for _, argument in ipairs(arguments) do
+ table.insert(result[target], argument)
+ end
+end
+
+function Argument:_get_action()
+ local action, init
+
+ if self._maxcount == 1 then
+ if self._maxargs == 0 then
+ action, init = "store_true", nil
+ else
+ action, init = "store", nil
+ end
+ else
+ if self._maxargs == 0 then
+ action, init = "count", 0
+ else
+ action, init = "append", {}
+ end
+ end
+
+ if self._action then
+ action = self._action
+ end
+
+ if self._has_init then
+ init = self._init
+ end
+
+ if type(action) == "string" then
+ action = actions[action]
+ end
+
+ return action, init
+end
+
+-- Returns placeholder for `narg`-th argument.
+function Argument:_get_argname(narg)
+ local argname = self._argname or self:_get_default_argname()
+
+ if type(argname) == "table" then
+ return argname[narg]
+ else
+ return argname
+ end
+end
+
+function Argument:_get_choices_list()
+ return "{" .. table.concat(self._choices, ",") .. "}"
+end
+
+function Argument:_get_default_argname()
+ if self._choices then
+ return self:_get_choices_list()
+ else
+ return "<" .. self._name .. ">"
+ end
+end
+
+function Option:_get_default_argname()
+ if self._choices then
+ return self:_get_choices_list()
+ else
+ return "<" .. self:_get_default_target() .. ">"
+ end
+end
+
+-- Returns labels to be shown in the help message.
+function Argument:_get_label_lines()
+ if self._choices then
+ return {self:_get_choices_list()}
+ else
+ return {self._name}
+ end
+end
+
+function Option:_get_label_lines()
+ local argument_list = self:_get_argument_list()
+
+ if #argument_list == 0 then
+ -- Don't put aliases for simple flags like `-h` on different lines.
+ return {table.concat(self._public_aliases, ", ")}
+ end
+
+ local longest_alias_length = -1
+
+ for _, alias in ipairs(self._public_aliases) do
+ longest_alias_length = math.max(longest_alias_length, #alias)
+ end
+
+ local argument_list_repr = table.concat(argument_list, " ")
+ local lines = {}
+
+ for i, alias in ipairs(self._public_aliases) do
+ local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr
+
+ if i ~= #self._public_aliases then
+ line = line .. ","
+ end
+
+ table.insert(lines, line)
+ end
+
+ return lines
+end
+
+function Command:_get_label_lines()
+ return {table.concat(self._public_aliases, ", ")}
+end
+
+function Argument:_get_description()
+ if self._default and self._show_default then
+ if self._description then
+ return ("%s (default: %s)"):format(self._description, self._default)
+ else
+ return ("default: %s"):format(self._default)
+ end
+ else
+ return self._description or ""
+ end
+end
+
+function Command:_get_description()
+ return self._summary or self._description or ""
+end
+
+function Option:_get_usage()
+ local usage = self:_get_argument_list()
+ table.insert(usage, 1, self._name)
+ usage = table.concat(usage, " ")
+
+ if self._mincount == 0 or self._default then
+ usage = "[" .. usage .. "]"
+ end
+
+ return usage
+end
+
+function Argument:_get_default_target()
+ return self._name
+end
+
+function Option:_get_default_target()
+ local res
+
+ for _, alias in ipairs(self._public_aliases) do
+ if alias:sub(1, 1) == alias:sub(2, 2) then
+ res = alias:sub(3)
+ break
+ end
+ end
+
+ res = res or self._name:sub(2)
+ return (res:gsub("-", "_"))
+end
+
+function Option:_is_vararg()
+ return self._maxargs ~= self._minargs
+end
+
+function Parser:_get_fullname(exclude_root)
+ local parent = self._parent
+ if exclude_root and not parent then
+ return ""
+ end
+ local buf = {self._name}
+
+ while parent do
+ if not exclude_root or parent._parent then
+ table.insert(buf, 1, parent._name)
+ end
+ parent = parent._parent
+ end
+
+ return table.concat(buf, " ")
+end
+
+function Parser:_update_charset(charset)
+ charset = charset or {}
+
+ for _, command in ipairs(self._commands) do
+ command:_update_charset(charset)
+ end
+
+ for _, option in ipairs(self._options) do
+ for _, alias in ipairs(option._aliases) do
+ charset[alias:sub(1, 1)] = true
+ end
+ end
+
+ return charset
+end
+
+function Parser:argument(...)
+ local argument = Argument(...)
+ table.insert(self._arguments, argument)
+ return argument
+end
+
+function Parser:option(...)
+ local option = Option(...)
+ table.insert(self._options, option)
+ return option
+end
+
+function Parser:flag(...)
+ return self:option():args(0)(...)
+end
+
+function Parser:command(...)
+ local command = Command():add_help(true)(...)
+ command._parent = self
+ table.insert(self._commands, command)
+ return command
+end
+
+function Parser:mutex(...)
+ local elements = {...}
+
+ for i, element in ipairs(elements) do
+ local mt = getmetatable(element)
+ assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i))
+ end
+
+ table.insert(self._mutexes, elements)
+ return self
+end
+
+function Parser:group(name, ...)
+ assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name)))
+
+ local group = {name = name, ...}
+
+ for i, element in ipairs(group) do
+ local mt = getmetatable(element)
+ assert(mt == Option or mt == Argument or mt == Command,
+ ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1))
+ end
+
+ table.insert(self._groups, group)
+ return self
+end
+
+local usage_welcome = "Usage: "
+
+function Parser:get_usage()
+ if self._usage then
+ return self._usage
+ end
+
+ local usage_margin = self:_inherit_property("usage_margin", #usage_welcome)
+ local max_usage_width = self:_inherit_property("usage_max_width", 70)
+ local lines = {usage_welcome .. self:_get_fullname()}
+
+ local function add(s)
+ if #lines[#lines]+1+#s <= max_usage_width then
+ lines[#lines] = lines[#lines] .. " " .. s
+ else
+ lines[#lines+1] = (" "):rep(usage_margin) .. s
+ end
+ end
+
+ -- Normally options are before positional arguments in usage messages.
+ -- However, vararg options should be after, because they can't be reliable used
+ -- before a positional argument.
+ -- Mutexes come into play, too, and are shown as soon as possible.
+ -- Overall, output usages in the following order:
+ -- 1. Mutexes that don't have positional arguments or vararg options.
+ -- 2. Options that are not in any mutexes and are not vararg.
+ -- 3. Positional arguments - on their own or as a part of a mutex.
+ -- 4. Remaining mutexes.
+ -- 5. Remaining options.
+
+ local elements_in_mutexes = {}
+ local added_elements = {}
+ local added_mutexes = {}
+ local argument_to_mutexes = {}
+
+ local function add_mutex(mutex, main_argument)
+ if added_mutexes[mutex] then
+ return
+ end
+
+ added_mutexes[mutex] = true
+ local buf = {}
+
+ for _, element in ipairs(mutex) do
+ if not element._hidden and not added_elements[element] then
+ if getmetatable(element) == Option or element == main_argument then
+ table.insert(buf, element:_get_usage())
+ added_elements[element] = true
+ end
+ end
+ end
+
+ if #buf == 1 then
+ add(buf[1])
+ elseif #buf > 1 then
+ add("(" .. table.concat(buf, " | ") .. ")")
+ end
+ end
+
+ local function add_element(element)
+ if not element._hidden and not added_elements[element] then
+ add(element:_get_usage())
+ added_elements[element] = true
+ end
+ end
+
+ for _, mutex in ipairs(self._mutexes) do
+ local is_vararg = false
+ local has_argument = false
+
+ for _, element in ipairs(mutex) do
+ if getmetatable(element) == Option then
+ if element:_is_vararg() then
+ is_vararg = true
+ end
+ else
+ has_argument = true
+ argument_to_mutexes[element] = argument_to_mutexes[element] or {}
+ table.insert(argument_to_mutexes[element], mutex)
+ end
+
+ elements_in_mutexes[element] = true
+ end
+
+ if not is_vararg and not has_argument then
+ add_mutex(mutex)
+ end
+ end
+
+ for _, option in ipairs(self._options) do
+ if not elements_in_mutexes[option] and not option:_is_vararg() then
+ add_element(option)
+ end
+ end
+
+ -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex.
+ for _, argument in ipairs(self._arguments) do
+ -- Pick a mutex as a part of which to show this argument, take the first one that's still available.
+ local mutex
+
+ if elements_in_mutexes[argument] then
+ for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do
+ if not added_mutexes[argument_mutex] then
+ mutex = argument_mutex
+ end
+ end
+ end
+
+ if mutex then
+ add_mutex(mutex, argument)
+ else
+ add_element(argument)
+ end
+ end
+
+ for _, mutex in ipairs(self._mutexes) do
+ add_mutex(mutex)
+ end
+
+ for _, option in ipairs(self._options) do
+ add_element(option)
+ end
+
+ if #self._commands > 0 then
+ if self._require_command then
+ add("<command>")
+ else
+ add("[<command>]")
+ end
+
+ add("...")
+ end
+
+ return table.concat(lines, "\n")
+end
+
+local function split_lines(s)
+ if s == "" then
+ return {}
+ end
+
+ local lines = {}
+
+ if s:sub(-1) ~= "\n" then
+ s = s .. "\n"
+ end
+
+ for line in s:gmatch("([^\n]*)\n") do
+ table.insert(lines, line)
+ end
+
+ return lines
+end
+
+local function autowrap_line(line, max_length)
+ -- Algorithm for splitting lines is simple and greedy.
+ local result_lines = {}
+
+ -- Preserve original indentation of the line, put this at the beginning of each result line.
+ -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts
+ -- of the second and the following lines vertically align with the start of the second word.
+ local indentation = line:match("^ *")
+
+ if line:find("^ *[%*%+%-]") then
+ indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)")
+ end
+
+ -- Parts of the last line being assembled.
+ local line_parts = {}
+
+ -- Length of the current line.
+ local line_length = 0
+
+ -- Index of the next character to consider.
+ local index = 1
+
+ while true do
+ local word_start, word_finish, word = line:find("([^ ]+)", index)
+
+ if not word_start then
+ -- Ignore trailing spaces, if any.
+ break
+ end
+
+ local preceding_spaces = line:sub(index, word_start - 1)
+ index = word_finish + 1
+
+ if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then
+ -- Either this is the very first word or it fits as an addition to the current line, add it.
+ table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation.
+ table.insert(line_parts, word)
+ line_length = line_length + #preceding_spaces + #word
+ else
+ -- Does not fit, finish current line and put the word into a new one.
+ table.insert(result_lines, table.concat(line_parts))
+ line_parts = {indentation, word}
+ line_length = #indentation + #word
+ end
+ end
+
+ if #line_parts > 0 then
+ table.insert(result_lines, table.concat(line_parts))
+ end
+
+ if #result_lines == 0 then
+ -- Preserve empty lines.
+ result_lines[1] = ""
+ end
+
+ return result_lines
+end
+
+-- Automatically wraps lines within given array,
+-- attempting to limit line length to `max_length`.
+-- Existing line splits are preserved.
+local function autowrap(lines, max_length)
+ local result_lines = {}
+
+ for _, line in ipairs(lines) do
+ local autowrapped_lines = autowrap_line(line, max_length)
+
+ for _, autowrapped_line in ipairs(autowrapped_lines) do
+ table.insert(result_lines, autowrapped_line)
+ end
+ end
+
+ return result_lines
+end
+
+function Parser:_get_element_help(element)
+ local label_lines = element:_get_label_lines()
+ local description_lines = split_lines(element:_get_description())
+
+ local result_lines = {}
+
+ -- All label lines should have the same length (except the last one, it has no comma).
+ -- If too long, start description after all the label lines.
+ -- Otherwise, combine label and description lines.
+
+ local usage_margin_len = self:_inherit_property("help_usage_margin", 3)
+ local usage_margin = (" "):rep(usage_margin_len)
+ local description_margin_len = self:_inherit_property("help_description_margin", 25)
+ local description_margin = (" "):rep(description_margin_len)
+
+ local help_max_width = self:_inherit_property("help_max_width")
+
+ if help_max_width then
+ local description_max_width = math.max(help_max_width - description_margin_len, 10)
+ description_lines = autowrap(description_lines, description_max_width)
+ end
+
+ if #label_lines[1] >= (description_margin_len - usage_margin_len) then
+ for _, label_line in ipairs(label_lines) do
+ table.insert(result_lines, usage_margin .. label_line)
+ end
+
+ for _, description_line in ipairs(description_lines) do
+ table.insert(result_lines, description_margin .. description_line)
+ end
+ else
+ for i = 1, math.max(#label_lines, #description_lines) do
+ local label_line = label_lines[i]
+ local description_line = description_lines[i]
+
+ local line = ""
+
+ if label_line then
+ line = usage_margin .. label_line
+ end
+
+ if description_line and description_line ~= "" then
+ line = line .. (" "):rep(description_margin_len - #line) .. description_line
+ end
+
+ table.insert(result_lines, line)
+ end
+ end
+
+ return table.concat(result_lines, "\n")
+end
+
+local function get_group_types(group)
+ local types = {}
+
+ for _, element in ipairs(group) do
+ types[getmetatable(element)] = true
+ end
+
+ return types
+end
+
+function Parser:_add_group_help(blocks, added_elements, label, elements)
+ local buf = {label}
+
+ for _, element in ipairs(elements) do
+ if not element._hidden and not added_elements[element] then
+ added_elements[element] = true
+ table.insert(buf, self:_get_element_help(element))
+ end
+ end
+
+ if #buf > 1 then
+ table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1)))
+ end
+end
+
+function Parser:get_help()
+ if self._help then
+ return self._help
+ end
+
+ local blocks = {self:get_usage()}
+
+ local help_max_width = self:_inherit_property("help_max_width")
+
+ if self._description then
+ local description = self._description
+
+ if help_max_width then
+ description = table.concat(autowrap(split_lines(description), help_max_width), "\n")
+ end
+
+ table.insert(blocks, description)
+ end
+
+ -- 1. Put groups containing arguments first, then other arguments.
+ -- 2. Put remaining groups containing options, then other options.
+ -- 3. Put remaining groups containing commands, then other commands.
+ -- Assume that an element can't be in several groups.
+ local groups_by_type = {
+ [Argument] = {},
+ [Option] = {},
+ [Command] = {}
+ }
+
+ for _, group in ipairs(self._groups) do
+ local group_types = get_group_types(group)
+
+ for _, mt in ipairs({Argument, Option, Command}) do
+ if group_types[mt] then
+ table.insert(groups_by_type[mt], group)
+ break
+ end
+ end
+ end
+
+ local default_groups = {
+ {name = "Arguments", type = Argument, elements = self._arguments},
+ {name = "Options", type = Option, elements = self._options},
+ {name = "Commands", type = Command, elements = self._commands}
+ }
+
+ local added_elements = {}
+
+ for _, default_group in ipairs(default_groups) do
+ local type_groups = groups_by_type[default_group.type]
+
+ for _, group in ipairs(type_groups) do
+ self:_add_group_help(blocks, added_elements, group.name .. ":", group)
+ end
+
+ local default_label = default_group.name .. ":"
+
+ if #type_groups > 0 then
+ default_label = "Other " .. default_label:gsub("^.", string.lower)
+ end
+
+ self:_add_group_help(blocks, added_elements, default_label, default_group.elements)
+ end
+
+ if self._epilog then
+ local epilog = self._epilog
+
+ if help_max_width then
+ epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n")
+ end
+
+ table.insert(blocks, epilog)
+ end
+
+ return table.concat(blocks, "\n\n")
+end
+
+function Parser:add_help_command(value)
+ if value then
+ assert(type(value) == "string" or type(value) == "table",
+ ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value)))
+ end
+
+ local help = self:command()
+ :description "Show help for commands."
+ help:argument "command"
+ :description "The command to show help for."
+ :args "?"
+ :action(function(_, _, cmd)
+ if not cmd then
+ print(self:get_help())
+ os.exit(0)
+ else
+ for _, command in ipairs(self._commands) do
+ for _, alias in ipairs(command._aliases) do
+ if alias == cmd then
+ print(command:get_help())
+ os.exit(0)
+ end
+ end
+ end
+ end
+ help:error(("unknown command '%s'"):format(cmd))
+ end)
+
+ if value then
+ help = help(value)
+ end
+
+ if not help._name then
+ help "help"
+ end
+
+ help._is_help_command = true
+ return self
+end
+
+function Parser:_is_shell_safe()
+ if self._basename then
+ if self._basename:find("[^%w_%-%+%.]") then
+ return false
+ end
+ else
+ for _, alias in ipairs(self._aliases) do
+ if alias:find("[^%w_%-%+%.]") then
+ return false
+ end
+ end
+ end
+ for _, option in ipairs(self._options) do
+ for _, alias in ipairs(option._aliases) do
+ if alias:find("[^%w_%-%+%.]") then
+ return false
+ end
+ end
+ if option._choices then
+ for _, choice in ipairs(option._choices) do
+ if choice:find("[%s'\"]") then
+ return false
+ end
+ end
+ end
+ end
+ for _, argument in ipairs(self._arguments) do
+ if argument._choices then
+ for _, choice in ipairs(argument._choices) do
+ if choice:find("[%s'\"]") then
+ return false
+ end
+ end
+ end
+ end
+ for _, command in ipairs(self._commands) do
+ if not command:_is_shell_safe() then
+ return false
+ end
+ end
+ return true
+end
+
+function Parser:add_complete(value)
+ if value then
+ assert(type(value) == "string" or type(value) == "table",
+ ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value)))
+ end
+
+ local complete = self:option()
+ :description "Output a shell completion script for the specified shell."
+ :args(1)
+ :choices {"bash", "zsh", "fish"}
+ :action(function(_, _, shell)
+ io.write(self["get_" .. shell .. "_complete"](self))
+ os.exit(0)
+ end)
+
+ if value then
+ complete = complete(value)
+ end
+
+ if not complete._name then
+ complete "--completion"
+ end
+
+ return self
+end
+
+function Parser:add_complete_command(value)
+ if value then
+ assert(type(value) == "string" or type(value) == "table",
+ ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value)))
+ end
+
+ local complete = self:command()
+ :description "Output a shell completion script."
+ complete:argument "shell"
+ :description "The shell to output a completion script for."
+ :choices {"bash", "zsh", "fish"}
+ :action(function(_, _, shell)
+ io.write(self["get_" .. shell .. "_complete"](self))
+ os.exit(0)
+ end)
+
+ if value then
+ complete = complete(value)
+ end
+
+ if not complete._name then
+ complete "completion"
+ end
+
+ return self
+end
+
+local function base_name(pathname)
+ return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname
+end
+
+local function get_short_description(element)
+ local short = element:_get_description():match("^(.-)%.%s")
+ return short or element:_get_description():match("^(.-)%.?$")
+end
+
+function Parser:_get_options()
+ local options = {}
+ for _, option in ipairs(self._options) do
+ for _, alias in ipairs(option._aliases) do
+ table.insert(options, alias)
+ end
+ end
+ return table.concat(options, " ")
+end
+
+function Parser:_get_commands()
+ local commands = {}
+ for _, command in ipairs(self._commands) do
+ for _, alias in ipairs(command._aliases) do
+ table.insert(commands, alias)
+ end
+ end
+ return table.concat(commands, " ")
+end
+
+function Parser:_bash_option_args(buf, indent)
+ local opts = {}
+ for _, option in ipairs(self._options) do
+ if option._choices or option._minargs > 0 then
+ local compreply
+ if option._choices then
+ compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))'
+ else
+ compreply = 'COMPREPLY=($(compgen -f -- "$cur"))'
+ end
+ table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")")
+ table.insert(opts, (" "):rep(indent + 8) .. compreply)
+ table.insert(opts, (" "):rep(indent + 8) .. "return 0")
+ table.insert(opts, (" "):rep(indent + 8) .. ";;")
+ end
+ end
+
+ if #opts > 0 then
+ table.insert(buf, (" "):rep(indent) .. 'case "$prev" in')
+ table.insert(buf, table.concat(opts, "\n"))
+ table.insert(buf, (" "):rep(indent) .. "esac\n")
+ end
+end
+
+function Parser:_bash_get_cmd(buf, indent)
+ if #self._commands == 0 then
+ return
+ end
+
+ table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")')
+ table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do')
+ table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in')
+
+ for _, command in ipairs(self._commands) do
+ table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")")
+ if self._parent then
+ table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"')
+ else
+ table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"')
+ end
+ table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"')
+ command:_bash_get_cmd(buf, indent + 12)
+ table.insert(buf, (" "):rep(indent + 12) .. "break")
+ table.insert(buf, (" "):rep(indent + 12) .. ";;")
+ end
+
+ table.insert(buf, (" "):rep(indent + 4) .. "esac")
+ table.insert(buf, (" "):rep(indent) .. "done")
+end
+
+function Parser:_bash_cmd_completions(buf)
+ local cmd_buf = {}
+ if self._parent then
+ self:_bash_option_args(cmd_buf, 12)
+ end
+ if #self._commands > 0 then
+ table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))')
+ elseif self._is_help_command then
+ table.insert(cmd_buf, (" "):rep(12)
+ .. 'COMPREPLY=($(compgen -W "'
+ .. self._parent:_get_commands()
+ .. '" -- "$cur"))')
+ end
+ if #cmd_buf > 0 then
+ table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')")
+ table.insert(buf, table.concat(cmd_buf, "\n"))
+ table.insert(buf, (" "):rep(12) .. ";;")
+ end
+
+ for _, command in ipairs(self._commands) do
+ command:_bash_cmd_completions(buf)
+ end
+end
+
+function Parser:get_bash_complete()
+ self._basename = base_name(self._name)
+ assert(self:_is_shell_safe())
+ local buf = {([[
+_%s() {
+ local IFS=$' \t\n'
+ local args cur prev cmd opts arg
+ args=("${COMP_WORDS[@]}")
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ opts="%s"
+]]):format(self._basename, self:_get_options())}
+
+ self:_bash_option_args(buf, 4)
+ self:_bash_get_cmd(buf, 4)
+ if #self._commands > 0 then
+ table.insert(buf, "")
+ table.insert(buf, (" "):rep(4) .. 'case "$cmd" in')
+ self:_bash_cmd_completions(buf)
+ table.insert(buf, (" "):rep(4) .. "esac\n")
+ end
+
+ table.insert(buf, ([=[
+ if [[ "$cur" = -* ]]; then
+ COMPREPLY=($(compgen -W "$opts" -- "$cur"))
+ fi
+}
+
+complete -F _%s -o bashdefault -o default %s
+]=]):format(self._basename, self._basename))
+
+ return table.concat(buf, "\n")
+end
+
+function Parser:_zsh_arguments(buf, cmd_name, indent)
+ if self._parent then
+ table.insert(buf, (" "):rep(indent) .. "options=(")
+ table.insert(buf, (" "):rep(indent + 2) .. "$options")
+ else
+ table.insert(buf, (" "):rep(indent) .. "local -a options=(")
+ end
+
+ for _, option in ipairs(self._options) do
+ local line = {}
+ if #option._aliases > 1 then
+ if option._maxcount > 1 then
+ table.insert(line, '"*"')
+ end
+ table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"')
+ else
+ table.insert(line, '"')
+ if option._maxcount > 1 then
+ table.insert(line, "*")
+ end
+ table.insert(line, option._name)
+ end
+ if option._description then
+ local description = get_short_description(option):gsub('["%]:`$]', "\\%0")
+ table.insert(line, "[" .. description .. "]")
+ end
+ if option._maxargs == math.huge then
+ table.insert(line, ":*")
+ end
+ if option._choices then
+ table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")")
+ elseif option._maxargs > 0 then
+ table.insert(line, ": :_files")
+ end
+ table.insert(line, '"')
+ table.insert(buf, (" "):rep(indent + 2) .. table.concat(line))
+ end
+
+ table.insert(buf, (" "):rep(indent) .. ")")
+ table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\")
+ table.insert(buf, (" "):rep(indent + 2) .. "$options \\")
+
+ if self._is_help_command then
+ table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\')
+ else
+ for _, argument in ipairs(self._arguments) do
+ local spec
+ if argument._choices then
+ spec = ": :(" .. table.concat(argument._choices, " ") .. ")"
+ else
+ spec = ": :_files"
+ end
+ if argument._maxargs == math.huge then
+ table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\')
+ break
+ end
+ for _ = 1, argument._maxargs do
+ table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\')
+ end
+ end
+
+ if #self._commands > 0 then
+ table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\')
+ table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\')
+ end
+ end
+
+ table.insert(buf, (" "):rep(indent + 2) .. "&& return 0")
+end
+
+function Parser:_zsh_cmds(buf, cmd_name)
+ table.insert(buf, "\n_" .. cmd_name .. "_cmds() {")
+ table.insert(buf, " local -a commands=(")
+
+ for _, command in ipairs(self._commands) do
+ local line = {}
+ if #command._aliases > 1 then
+ table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"')
+ else
+ table.insert(line, '"' .. command._name)
+ end
+ if command._description then
+ table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0"))
+ end
+ table.insert(buf, " " .. table.concat(line) .. '"')
+ end
+
+ table.insert(buf, ' )\n _describe "command" commands\n}')
+end
+
+function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent)
+ if #self._commands == 0 then
+ return
+ end
+
+ self:_zsh_cmds(cmds_buf, cmd_name)
+ table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in")
+
+ for _, command in ipairs(self._commands) do
+ local name = cmd_name .. "_" .. command._name
+ table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")")
+ command:_zsh_arguments(buf, name, indent + 4)
+ command:_zsh_complete_help(buf, cmds_buf, name, indent + 4)
+ table.insert(buf, (" "):rep(indent + 4) .. ";;\n")
+ end
+
+ table.insert(buf, (" "):rep(indent) .. "esac")
+end
+
+function Parser:get_zsh_complete()
+ self._basename = base_name(self._name)
+ assert(self:_is_shell_safe())
+ local buf = {("#compdef %s\n"):format(self._basename)}
+ local cmds_buf = {}
+ table.insert(buf, "_" .. self._basename .. "() {")
+ if #self._commands > 0 then
+ table.insert(buf, " local context state state_descr line")
+ table.insert(buf, " typeset -A opt_args\n")
+ end
+ self:_zsh_arguments(buf, self._basename, 2)
+ self:_zsh_complete_help(buf, cmds_buf, self._basename, 2)
+ table.insert(buf, "\n return 1")
+ table.insert(buf, "}")
+
+ local result = table.concat(buf, "\n")
+ if #cmds_buf > 0 then
+ result = result .. "\n" .. table.concat(cmds_buf, "\n")
+ end
+ return result .. "\n\n_" .. self._basename .. "\n"
+end
+
+local function fish_escape(string)
+ return string:gsub("[\\']", "\\%0")
+end
+
+function Parser:_fish_get_cmd(buf, indent)
+ if #self._commands == 0 then
+ return
+ end
+
+ table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]")
+ table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline")
+ table.insert(buf, (" "):rep(indent + 4) .. "switch $arg")
+
+ for _, command in ipairs(self._commands) do
+ table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " "))
+ table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name)
+ command:_fish_get_cmd(buf, indent + 12)
+ table.insert(buf, (" "):rep(indent + 12) .. "break")
+ end
+
+ table.insert(buf, (" "):rep(indent + 4) .. "end")
+ table.insert(buf, (" "):rep(indent) .. "end")
+end
+
+function Parser:_fish_complete_help(buf, basename)
+ local prefix = "complete -c " .. basename
+ table.insert(buf, "")
+
+ for _, command in ipairs(self._commands) do
+ local aliases = table.concat(command._aliases, " ")
+ local line
+ if self._parent then
+ line = ("%s -n '__fish_%s_using_command %s' -xa '%s'")
+ :format(prefix, basename, self:_get_fullname(true), aliases)
+ else
+ line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases)
+ end
+ if command._description then
+ line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command)))
+ end
+ table.insert(buf, line)
+ end
+
+ if self._is_help_command then
+ local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'")
+ :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands())
+ table.insert(buf, line)
+ end
+
+ for _, option in ipairs(self._options) do
+ local parts = {prefix}
+
+ if self._parent then
+ table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'")
+ end
+
+ for _, alias in ipairs(option._aliases) do
+ if alias:match("^%-.$") then
+ table.insert(parts, "-s " .. alias:sub(2))
+ elseif alias:match("^%-%-.+") then
+ table.insert(parts, "-l " .. alias:sub(3))
+ end
+ end
+
+ if option._choices then
+ table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'")
+ elseif option._minargs > 0 then
+ table.insert(parts, "-r")
+ end
+
+ if option._description then
+ table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'")
+ end
+
+ table.insert(buf, table.concat(parts, " "))
+ end
+
+ for _, command in ipairs(self._commands) do
+ command:_fish_complete_help(buf, basename)
+ end
+end
+
+function Parser:get_fish_complete()
+ self._basename = base_name(self._name)
+ assert(self:_is_shell_safe())
+ local buf = {}
+
+ if #self._commands > 0 then
+ table.insert(buf, ([[
+function __fish_%s_print_command
+ set -l cmdline (commandline -poc)
+ set -l cmd]]):format(self._basename))
+ self:_fish_get_cmd(buf, 4)
+ table.insert(buf, ([[
+ echo "$cmd"
+end
+
+function __fish_%s_using_command
+ test (__fish_%s_print_command) = "$argv"
+ and return 0
+ or return 1
+end
+
+function __fish_%s_seen_command
+ string match -q "$argv*" (__fish_%s_print_command)
+ and return 0
+ or return 1
+end]]):format(self._basename, self._basename, self._basename, self._basename))
+ end
+
+ self:_fish_complete_help(buf, self._basename)
+ return table.concat(buf, "\n") .. "\n"
+end
+
+local function get_tip(context, wrong_name)
+ local context_pool = {}
+ local possible_name
+ local possible_names = {}
+
+ for name in pairs(context) do
+ if type(name) == "string" then
+ for i = 1, #name do
+ possible_name = name:sub(1, i - 1) .. name:sub(i + 1)
+
+ if not context_pool[possible_name] then
+ context_pool[possible_name] = {}
+ end
+
+ table.insert(context_pool[possible_name], name)
+ end
+ end
+ end
+
+ for i = 1, #wrong_name + 1 do
+ possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1)
+
+ if context[possible_name] then
+ possible_names[possible_name] = true
+ elseif context_pool[possible_name] then
+ for _, name in ipairs(context_pool[possible_name]) do
+ possible_names[name] = true
+ end
+ end
+ end
+
+ local first = next(possible_names)
+
+ if first then
+ if next(possible_names, first) then
+ local possible_names_arr = {}
+
+ for name in pairs(possible_names) do
+ table.insert(possible_names_arr, "'" .. name .. "'")
+ end
+
+ table.sort(possible_names_arr)
+ return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?"
+ else
+ return "\nDid you mean '" .. first .. "'?"
+ end
+ else
+ return ""
+ end
+end
+
+local ElementState = class({
+ invocations = 0
+})
+
+function ElementState:__call(state, element)
+ self.state = state
+ self.result = state.result
+ self.element = element
+ self.target = element._target or element:_get_default_target()
+ self.action, self.result[self.target] = element:_get_action()
+ return self
+end
+
+function ElementState:error(fmt, ...)
+ self.state:error(fmt, ...)
+end
+
+function ElementState:convert(argument, index)
+ local converter = self.element._convert
+
+ if converter then
+ local ok, err
+
+ if type(converter) == "function" then
+ ok, err = converter(argument)
+ elseif type(converter[index]) == "function" then
+ ok, err = converter[index](argument)
+ else
+ ok = converter[argument]
+ end
+
+ if ok == nil then
+ self:error(err and "%s" or "malformed argument '%s'", err or argument)
+ end
+
+ argument = ok
+ end
+
+ return argument
+end
+
+function ElementState:default(mode)
+ return self.element._defmode:find(mode) and self.element._default
+end
+
+local function bound(noun, min, max, is_max)
+ local res = ""
+
+ if min ~= max then
+ res = "at " .. (is_max and "most" or "least") .. " "
+ end
+
+ local number = is_max and max or min
+ return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s")
+end
+
+function ElementState:set_name(alias)
+ self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name)
+end
+
+function ElementState:invoke()
+ self.open = true
+ self.overwrite = false
+
+ if self.invocations >= self.element._maxcount then
+ if self.element._overwrite then
+ self.overwrite = true
+ else
+ local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true)
+ self:error("%s must be used %s", self.name, num_times_repr)
+ end
+ else
+ self.invocations = self.invocations + 1
+ end
+
+ self.args = {}
+
+ if self.element._maxargs <= 0 then
+ self:close()
+ end
+
+ return self.open
+end
+
+function ElementState:check_choices(argument)
+ if self.element._choices then
+ for _, choice in ipairs(self.element._choices) do
+ if argument == choice then
+ return
+ end
+ end
+ local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'"
+ local is_option = getmetatable(self.element) == Option
+ self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list)
+ end
+end
+
+function ElementState:pass(argument)
+ self:check_choices(argument)
+ argument = self:convert(argument, #self.args + 1)
+ table.insert(self.args, argument)
+
+ if #self.args >= self.element._maxargs then
+ self:close()
+ end
+
+ return self.open
+end
+
+function ElementState:complete_invocation()
+ while #self.args < self.element._minargs do
+ self:pass(self.element._default)
+ end
+end
+
+function ElementState:close()
+ if self.open then
+ self.open = false
+
+ if #self.args < self.element._minargs then
+ if self:default("a") then
+ self:complete_invocation()
+ else
+ if #self.args == 0 then
+ if getmetatable(self.element) == Argument then
+ self:error("missing %s", self.name)
+ elseif self.element._maxargs == 1 then
+ self:error("%s requires an argument", self.name)
+ end
+ end
+
+ self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs))
+ end
+ end
+
+ local args
+
+ if self.element._maxargs == 0 then
+ args = self.args[1]
+ elseif self.element._maxargs == 1 then
+ if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then
+ args = self.args
+ else
+ args = self.args[1]
+ end
+ else
+ args = self.args
+ end
+
+ self.action(self.result, self.target, args, self.overwrite)
+ end
+end
+
+local ParseState = class({
+ result = {},
+ options = {},
+ arguments = {},
+ argument_i = 1,
+ element_to_mutexes = {},
+ mutex_to_element_state = {},
+ command_actions = {}
+})
+
+function ParseState:__call(parser, error_handler)
+ self.parser = parser
+ self.error_handler = error_handler
+ self.charset = parser:_update_charset()
+ self:switch(parser)
+ return self
+end
+
+function ParseState:error(fmt, ...)
+ self.error_handler(self.parser, fmt:format(...))
+end
+
+function ParseState:switch(parser)
+ self.parser = parser
+
+ if parser._action then
+ table.insert(self.command_actions, {action = parser._action, name = parser._name})
+ end
+
+ for _, option in ipairs(parser._options) do
+ option = ElementState(self, option)
+ table.insert(self.options, option)
+
+ for _, alias in ipairs(option.element._aliases) do
+ self.options[alias] = option
+ end
+ end
+
+ for _, mutex in ipairs(parser._mutexes) do
+ for _, element in ipairs(mutex) do
+ if not self.element_to_mutexes[element] then
+ self.element_to_mutexes[element] = {}
+ end
+
+ table.insert(self.element_to_mutexes[element], mutex)
+ end
+ end
+
+ for _, argument in ipairs(parser._arguments) do
+ argument = ElementState(self, argument)
+ table.insert(self.arguments, argument)
+ argument:set_name()
+ argument:invoke()
+ end
+
+ self.handle_options = parser._handle_options
+ self.argument = self.arguments[self.argument_i]
+ self.commands = parser._commands
+
+ for _, command in ipairs(self.commands) do
+ for _, alias in ipairs(command._aliases) do
+ self.commands[alias] = command
+ end
+ end
+end
+
+function ParseState:get_option(name)
+ local option = self.options[name]
+
+ if not option then
+ self:error("unknown option '%s'%s", name, get_tip(self.options, name))
+ else
+ return option
+ end
+end
+
+function ParseState:get_command(name)
+ local command = self.commands[name]
+
+ if not command then
+ if #self.commands > 0 then
+ self:error("unknown command '%s'%s", name, get_tip(self.commands, name))
+ else
+ self:error("too many arguments")
+ end
+ else
+ return command
+ end
+end
+
+function ParseState:check_mutexes(element_state)
+ if self.element_to_mutexes[element_state.element] then
+ for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do
+ local used_element_state = self.mutex_to_element_state[mutex]
+
+ if used_element_state and used_element_state ~= element_state then
+ self:error("%s can not be used together with %s", element_state.name, used_element_state.name)
+ else
+ self.mutex_to_element_state[mutex] = element_state
+ end
+ end
+ end
+end
+
+function ParseState:invoke(option, name)
+ self:close()
+ option:set_name(name)
+ self:check_mutexes(option, name)
+
+ if option:invoke() then
+ self.option = option
+ end
+end
+
+function ParseState:pass(arg)
+ if self.option then
+ if not self.option:pass(arg) then
+ self.option = nil
+ end
+ elseif self.argument then
+ self:check_mutexes(self.argument)
+
+ if not self.argument:pass(arg) then
+ self.argument_i = self.argument_i + 1
+ self.argument = self.arguments[self.argument_i]
+ end
+ else
+ local command = self:get_command(arg)
+ self.result[command._target or command._name] = true
+
+ if self.parser._command_target then
+ self.result[self.parser._command_target] = command._name
+ end
+
+ self:switch(command)
+ end
+end
+
+function ParseState:close()
+ if self.option then
+ self.option:close()
+ self.option = nil
+ end
+end
+
+function ParseState:finalize()
+ self:close()
+
+ for i = self.argument_i, #self.arguments do
+ local argument = self.arguments[i]
+ if #argument.args == 0 and argument:default("u") then
+ argument:complete_invocation()
+ else
+ argument:close()
+ end
+ end
+
+ if self.parser._require_command and #self.commands > 0 then
+ self:error("a command is required")
+ end
+
+ for _, option in ipairs(self.options) do
+ option.name = option.name or ("option '%s'"):format(option.element._name)
+
+ if option.invocations == 0 then
+ if option:default("u") then
+ option:invoke()
+ option:complete_invocation()
+ option:close()
+ end
+ end
+
+ local mincount = option.element._mincount
+
+ if option.invocations < mincount then
+ if option:default("a") then
+ while option.invocations < mincount do
+ option:invoke()
+ option:close()
+ end
+ elseif option.invocations == 0 then
+ self:error("missing %s", option.name)
+ else
+ self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount))
+ end
+ end
+ end
+
+ for i = #self.command_actions, 1, -1 do
+ self.command_actions[i].action(self.result, self.command_actions[i].name)
+ end
+end
+
+function ParseState:parse(args)
+ for _, arg in ipairs(args) do
+ local plain = true
+
+ if self.handle_options then
+ local first = arg:sub(1, 1)
+
+ if self.charset[first] then
+ if #arg > 1 then
+ plain = false
+
+ if arg:sub(2, 2) == first then
+ if #arg == 2 then
+ if self.options[arg] then
+ local option = self:get_option(arg)
+ self:invoke(option, arg)
+ else
+ self:close()
+ end
+
+ self.handle_options = false
+ else
+ local equals = arg:find "="
+ if equals then
+ local name = arg:sub(1, equals - 1)
+ local option = self:get_option(name)
+
+ if option.element._maxargs <= 0 then
+ self:error("option '%s' does not take arguments", name)
+ end
+
+ self:invoke(option, name)
+ self:pass(arg:sub(equals + 1))
+ else
+ local option = self:get_option(arg)
+ self:invoke(option, arg)
+ end
+ end
+ else
+ for i = 2, #arg do
+ local name = first .. arg:sub(i, i)
+ local option = self:get_option(name)
+ self:invoke(option, name)
+
+ if i ~= #arg and option.element._maxargs > 0 then
+ self:pass(arg:sub(i + 1))
+ break
+ end
+ end
+ end
+ end
+ end
+ end
+
+ if plain then
+ self:pass(arg)
+ end
+ end
+
+ self:finalize()
+ return self.result
+end
+
+function Parser:error(msg)
+ io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg))
+ os.exit(1)
+end
+
+-- Compatibility with strict.lua and other checkers:
+local default_cmdline = rawget(_G, "arg") or {}
+
+function Parser:_parse(args, error_handler)
+ return ParseState(self, error_handler):parse(args or default_cmdline)
+end
+
+function Parser:parse(args)
+ return self:_parse(args, self.error)
+end
+
+local function xpcall_error_handler(err)
+ if not debug then
+ return tostring(err)
+ end
+ return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2)
+end
+
+function Parser:pparse(args)
+ local parse_error
+
+ local ok, result = xpcall(function()
+ return self:_parse(args, function(_, err)
+ parse_error = err
+ error(err, 0)
+ end)
+ end, xpcall_error_handler)
+
+ if ok then
+ return true, result
+ elseif not parse_error then
+ error(result, 0)
+ else
+ return false, parse_error
+ end
+end
+
+local argparse = {}
+
+argparse.version = "0.7.0"
+
+setmetatable(argparse, {__call = function(_, ...)
+ return Parser(default_cmdline[0]):add_help(true)(...)
+end})
+
+return argparse
diff --git a/src/luarocks/vendor/dkjson.lua b/src/luarocks/vendor/dkjson.lua
new file mode 100644
index 0000000..7a86724
--- /dev/null
+++ b/src/luarocks/vendor/dkjson.lua
@@ -0,0 +1,749 @@
+-- Module options:
+local always_use_lpeg = false
+local register_global_module_table = false
+local global_module_name = 'json'
+
+--[==[
+
+David Kolf's JSON module for Lua 5.1 - 5.4
+
+Version 2.7
+
+
+For the documentation see the corresponding readme.txt or visit
+<http://dkolf.de/src/dkjson-lua.fsl/>.
+
+You can contact the author by sending an e-mail to 'david' at the
+domain 'dkolf.de'.
+
+
+Copyright (C) 2010-2024 David Heiko Kolf
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+--]==]
+
+-- global dependencies:
+local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
+ pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
+local error, require, pcall, select = error, require, pcall, select
+local floor, huge = math.floor, math.huge
+local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
+ string.rep, string.gsub, string.sub, string.byte, string.char,
+ string.find, string.len, string.format
+local strmatch = string.match
+local concat = table.concat
+
+local json = { version = "dkjson 2.7" }
+
+local jsonlpeg = {}
+
+if register_global_module_table then
+ if always_use_lpeg then
+ _G[global_module_name] = jsonlpeg
+ else
+ _G[global_module_name] = json
+ end
+end
+
+local _ENV = nil -- blocking globals in Lua 5.2 and later
+
+pcall (function()
+ -- Enable access to blocked metatables.
+ -- Don't worry, this module doesn't change anything in them.
+ local debmeta = require "debug".getmetatable
+ if debmeta then getmetatable = debmeta end
+end)
+
+json.null = setmetatable ({}, {
+ __tojson = function () return "null" end
+})
+
+local function isarray (tbl)
+ local max, n, arraylen = 0, 0, 0
+ for k,v in pairs (tbl) do
+ if k == 'n' and type(v) == 'number' then
+ arraylen = v
+ if v > max then
+ max = v
+ end
+ else
+ if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
+ return false
+ end
+ if k > max then
+ max = k
+ end
+ n = n + 1
+ end
+ end
+ if max > 10 and max > arraylen and max > n * 2 then
+ return false -- don't create an array with too many holes
+ end
+ return true, max
+end
+
+local escapecodes = {
+ ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
+ ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
+}
+
+local function escapeutf8 (uchar)
+ local value = escapecodes[uchar]
+ if value then
+ return value
+ end
+ local a, b, c, d = strbyte (uchar, 1, 4)
+ a, b, c, d = a or 0, b or 0, c or 0, d or 0
+ if a <= 0x7f then
+ value = a
+ elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
+ value = (a - 0xc0) * 0x40 + b - 0x80
+ elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
+ value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
+ elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
+ value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
+ else
+ return ""
+ end
+ if value <= 0xffff then
+ return strformat ("\\u%.4x", value)
+ elseif value <= 0x10ffff then
+ -- encode as UTF-16 surrogate pair
+ value = value - 0x10000
+ local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
+ return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
+ else
+ return ""
+ end
+end
+
+local function fsub (str, pattern, repl)
+ -- gsub always builds a new string in a buffer, even when no match
+ -- exists. First using find should be more efficient when most strings
+ -- don't contain the pattern.
+ if strfind (str, pattern) then
+ return gsub (str, pattern, repl)
+ else
+ return str
+ end
+end
+
+local function quotestring (value)
+ -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
+ value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
+ if strfind (value, "[\194\216\220\225\226\239]") then
+ value = fsub (value, "\194[\128-\159\173]", escapeutf8)
+ value = fsub (value, "\216[\128-\132]", escapeutf8)
+ value = fsub (value, "\220\143", escapeutf8)
+ value = fsub (value, "\225\158[\180\181]", escapeutf8)
+ value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
+ value = fsub (value, "\226\129[\160-\175]", escapeutf8)
+ value = fsub (value, "\239\187\191", escapeutf8)
+ value = fsub (value, "\239\191[\176-\191]", escapeutf8)
+ end
+ return "\"" .. value .. "\""
+end
+json.quotestring = quotestring
+
+local function replace(str, o, n)
+ local i, j = strfind (str, o, 1, true)
+ if i then
+ return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
+ else
+ return str
+ end
+end
+
+-- locale independent num2str and str2num functions
+local decpoint, numfilter
+
+local function updatedecpoint ()
+ decpoint = strmatch(tostring(0.5), "([^05+])")
+ -- build a filter that can be used to remove group separators
+ numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
+end
+
+updatedecpoint()
+
+local function num2str (num)
+ return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
+end
+
+local function str2num (str)
+ local num = tonumber(replace(str, ".", decpoint))
+ if not num then
+ updatedecpoint()
+ num = tonumber(replace(str, ".", decpoint))
+ end
+ return num
+end
+
+local function addnewline2 (level, buffer, buflen)
+ buffer[buflen+1] = "\n"
+ buffer[buflen+2] = strrep (" ", level)
+ buflen = buflen + 2
+ return buflen
+end
+
+function json.addnewline (state)
+ if state.indent then
+ state.bufferlen = addnewline2 (state.level or 0,
+ state.buffer, state.bufferlen or #(state.buffer))
+ end
+end
+
+local encode2 -- forward declaration
+
+local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ local kt = type (key)
+ if kt ~= 'string' and kt ~= 'number' then
+ return nil, "type '" .. kt .. "' is not supported as a key by JSON."
+ end
+ if prev then
+ buflen = buflen + 1
+ buffer[buflen] = ","
+ end
+ if indent then
+ buflen = addnewline2 (level, buffer, buflen)
+ end
+ buffer[buflen+1] = quotestring (key)
+ buffer[buflen+2] = ":"
+ return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
+end
+
+local function appendcustom(res, buffer, state)
+ local buflen = state.bufferlen
+ if type (res) == 'string' then
+ buflen = buflen + 1
+ buffer[buflen] = res
+ end
+ return buflen
+end
+
+local function exception(reason, value, state, buffer, buflen, defaultmessage)
+ defaultmessage = defaultmessage or reason
+ local handler = state.exception
+ if not handler then
+ return nil, defaultmessage
+ else
+ state.bufferlen = buflen
+ local ret, msg = handler (reason, value, state, defaultmessage)
+ if not ret then return nil, msg or defaultmessage end
+ return appendcustom(ret, buffer, state)
+ end
+end
+
+function json.encodeexception(reason, value, state, defaultmessage)
+ return quotestring("<" .. defaultmessage .. ">")
+end
+
+encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
+ local valtype = type (value)
+ local valmeta = getmetatable (value)
+ valmeta = type (valmeta) == 'table' and valmeta -- only tables
+ local valtojson = valmeta and valmeta.__tojson
+ if valtojson then
+ if tables[value] then
+ return exception('reference cycle', value, state, buffer, buflen)
+ end
+ tables[value] = true
+ state.bufferlen = buflen
+ local ret, msg = valtojson (value, state)
+ if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
+ tables[value] = nil
+ buflen = appendcustom(ret, buffer, state)
+ elseif value == nil then
+ buflen = buflen + 1
+ buffer[buflen] = "null"
+ elseif valtype == 'number' then
+ local s
+ if value ~= value or value >= huge or -value >= huge then
+ -- This is the behaviour of the original JSON implementation.
+ s = "null"
+ else
+ s = num2str (value)
+ end
+ buflen = buflen + 1
+ buffer[buflen] = s
+ elseif valtype == 'boolean' then
+ buflen = buflen + 1
+ buffer[buflen] = value and "true" or "false"
+ elseif valtype == 'string' then
+ buflen = buflen + 1
+ buffer[buflen] = quotestring (value)
+ elseif valtype == 'table' then
+ if tables[value] then
+ return exception('reference cycle', value, state, buffer, buflen)
+ end
+ tables[value] = true
+ level = level + 1
+ local isa, n = isarray (value)
+ if n == 0 and valmeta and valmeta.__jsontype == 'object' then
+ isa = false
+ end
+ local msg
+ if isa then -- JSON array
+ buflen = buflen + 1
+ buffer[buflen] = "["
+ for i = 1, n do
+ buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ if i < n then
+ buflen = buflen + 1
+ buffer[buflen] = ","
+ end
+ end
+ buflen = buflen + 1
+ buffer[buflen] = "]"
+ else -- JSON object
+ local prev = false
+ buflen = buflen + 1
+ buffer[buflen] = "{"
+ local order = valmeta and valmeta.__jsonorder or globalorder
+ if order then
+ local used = {}
+ n = #order
+ for i = 1, n do
+ local k = order[i]
+ local v = value[k]
+ if v ~= nil then
+ used[k] = true
+ buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ prev = true -- add a seperator before the next element
+ end
+ end
+ for k,v in pairs (value) do
+ if not used[k] then
+ buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ prev = true -- add a seperator before the next element
+ end
+ end
+ else -- unordered
+ for k,v in pairs (value) do
+ buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ prev = true -- add a seperator before the next element
+ end
+ end
+ if indent then
+ buflen = addnewline2 (level - 1, buffer, buflen)
+ end
+ buflen = buflen + 1
+ buffer[buflen] = "}"
+ end
+ tables[value] = nil
+ else
+ return exception ('unsupported type', value, state, buffer, buflen,
+ "type '" .. valtype .. "' is not supported by JSON.")
+ end
+ return buflen
+end
+
+function json.encode (value, state)
+ state = state or {}
+ local oldbuffer = state.buffer
+ local buffer = oldbuffer or {}
+ state.buffer = buffer
+ updatedecpoint()
+ local ret, msg = encode2 (value, state.indent, state.level or 0,
+ buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
+ if not ret then
+ error (msg, 2)
+ elseif oldbuffer == buffer then
+ state.bufferlen = ret
+ return true
+ else
+ state.bufferlen = nil
+ state.buffer = nil
+ return concat (buffer)
+ end
+end
+
+local function loc (str, where)
+ local line, pos, linepos = 1, 1, 0
+ while true do
+ pos = strfind (str, "\n", pos, true)
+ if pos and pos < where then
+ line = line + 1
+ linepos = pos
+ pos = pos + 1
+ else
+ break
+ end
+ end
+ return "line " .. line .. ", column " .. (where - linepos)
+end
+
+local function unterminated (str, what, where)
+ return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
+end
+
+local function scanwhite (str, pos)
+ while true do
+ pos = strfind (str, "%S", pos)
+ if not pos then return nil end
+ local sub2 = strsub (str, pos, pos + 1)
+ if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
+ -- UTF-8 Byte Order Mark
+ pos = pos + 3
+ elseif sub2 == "//" then
+ pos = strfind (str, "[\n\r]", pos + 2)
+ if not pos then return nil end
+ elseif sub2 == "/*" then
+ pos = strfind (str, "*/", pos + 2)
+ if not pos then return nil end
+ pos = pos + 2
+ else
+ return pos
+ end
+ end
+end
+
+local escapechars = {
+ ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
+ ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
+}
+
+local function unichar (value)
+ if value < 0 then
+ return nil
+ elseif value <= 0x007f then
+ return strchar (value)
+ elseif value <= 0x07ff then
+ return strchar (0xc0 + floor(value/0x40),
+ 0x80 + (floor(value) % 0x40))
+ elseif value <= 0xffff then
+ return strchar (0xe0 + floor(value/0x1000),
+ 0x80 + (floor(value/0x40) % 0x40),
+ 0x80 + (floor(value) % 0x40))
+ elseif value <= 0x10ffff then
+ return strchar (0xf0 + floor(value/0x40000),
+ 0x80 + (floor(value/0x1000) % 0x40),
+ 0x80 + (floor(value/0x40) % 0x40),
+ 0x80 + (floor(value) % 0x40))
+ else
+ return nil
+ end
+end
+
+local function scanstring (str, pos)
+ local lastpos = pos + 1
+ local buffer, n = {}, 0
+ while true do
+ local nextpos = strfind (str, "[\"\\]", lastpos)
+ if not nextpos then
+ return unterminated (str, "string", pos)
+ end
+ if nextpos > lastpos then
+ n = n + 1
+ buffer[n] = strsub (str, lastpos, nextpos - 1)
+ end
+ if strsub (str, nextpos, nextpos) == "\"" then
+ lastpos = nextpos + 1
+ break
+ else
+ local escchar = strsub (str, nextpos + 1, nextpos + 1)
+ local value
+ if escchar == "u" then
+ value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
+ if value then
+ local value2
+ if 0xD800 <= value and value <= 0xDBff then
+ -- we have the high surrogate of UTF-16. Check if there is a
+ -- low surrogate escaped nearby to combine them.
+ if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
+ value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
+ if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
+ value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
+ else
+ value2 = nil -- in case it was out of range for a low surrogate
+ end
+ end
+ end
+ value = value and unichar (value)
+ if value then
+ if value2 then
+ lastpos = nextpos + 12
+ else
+ lastpos = nextpos + 6
+ end
+ end
+ end
+ end
+ if not value then
+ value = escapechars[escchar] or escchar
+ lastpos = nextpos + 2
+ end
+ n = n + 1
+ buffer[n] = value
+ end
+ end
+ if n == 1 then
+ return buffer[1], lastpos
+ elseif n > 1 then
+ return concat (buffer), lastpos
+ else
+ return "", lastpos
+ end
+end
+
+local scanvalue -- forward declaration
+
+local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
+ local len = strlen (str)
+ local tbl, n = {}, 0
+ local pos = startpos + 1
+ if what == 'object' then
+ setmetatable (tbl, objectmeta)
+ else
+ setmetatable (tbl, arraymeta)
+ end
+ while true do
+ pos = scanwhite (str, pos)
+ if not pos then return unterminated (str, what, startpos) end
+ local char = strsub (str, pos, pos)
+ if char == closechar then
+ return tbl, pos + 1
+ end
+ local val1, err
+ val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
+ if err then return nil, pos, err end
+ pos = scanwhite (str, pos)
+ if not pos then return unterminated (str, what, startpos) end
+ char = strsub (str, pos, pos)
+ if char == ":" then
+ if val1 == nil then
+ return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
+ end
+ pos = scanwhite (str, pos + 1)
+ if not pos then return unterminated (str, what, startpos) end
+ local val2
+ val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
+ if err then return nil, pos, err end
+ tbl[val1] = val2
+ pos = scanwhite (str, pos)
+ if not pos then return unterminated (str, what, startpos) end
+ char = strsub (str, pos, pos)
+ else
+ n = n + 1
+ tbl[n] = val1
+ end
+ if char == "," then
+ pos = pos + 1
+ end
+ end
+end
+
+scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
+ pos = pos or 1
+ pos = scanwhite (str, pos)
+ if not pos then
+ return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
+ end
+ local char = strsub (str, pos, pos)
+ if char == "{" then
+ return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
+ elseif char == "[" then
+ return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
+ elseif char == "\"" then
+ return scanstring (str, pos)
+ else
+ local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
+ if pstart then
+ local number = str2num (strsub (str, pstart, pend))
+ if number then
+ return number, pend + 1
+ end
+ end
+ pstart, pend = strfind (str, "^%a%w*", pos)
+ if pstart then
+ local name = strsub (str, pstart, pend)
+ if name == "true" then
+ return true, pend + 1
+ elseif name == "false" then
+ return false, pend + 1
+ elseif name == "null" then
+ return nullval, pend + 1
+ end
+ end
+ return nil, pos, "no valid JSON value at " .. loc (str, pos)
+ end
+end
+
+local function optionalmetatables(...)
+ if select("#", ...) > 0 then
+ return ...
+ else
+ return {__jsontype = 'object'}, {__jsontype = 'array'}
+ end
+end
+
+function json.decode (str, pos, nullval, ...)
+ local objectmeta, arraymeta = optionalmetatables(...)
+ return scanvalue (str, pos, nullval, objectmeta, arraymeta)
+end
+
+function json.use_lpeg ()
+ local g = require ("lpeg")
+
+ if type(g.version) == 'function' and g.version() == "0.11" then
+ error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
+ end
+
+ local pegmatch = g.match
+ local P, S, R = g.P, g.S, g.R
+
+ local function ErrorCall (str, pos, msg, state)
+ if not state.msg then
+ state.msg = msg .. " at " .. loc (str, pos)
+ state.pos = pos
+ end
+ return false
+ end
+
+ local function Err (msg)
+ return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
+ end
+
+ local function ErrorUnterminatedCall (str, pos, what, state)
+ return ErrorCall (str, pos - 1, "unterminated " .. what, state)
+ end
+
+ local SingleLineComment = P"//" * (1 - S"\n\r")^0
+ local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
+ local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
+
+ local function ErrUnterminated (what)
+ return g.Cmt (g.Cc (what) * g.Carg (2), ErrorUnterminatedCall)
+ end
+
+ local PlainChar = 1 - S"\"\\\n\r"
+ local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
+ local HexDigit = R("09", "af", "AF")
+ local function UTF16Surrogate (match, pos, high, low)
+ high, low = tonumber (high, 16), tonumber (low, 16)
+ if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
+ return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
+ else
+ return false
+ end
+ end
+ local function UTF16BMP (hex)
+ return unichar (tonumber (hex, 16))
+ end
+ local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
+ local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
+ local Char = UnicodeEscape + EscapeSequence + PlainChar
+ local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string")
+ local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
+ local Fractal = P"." * R"09"^0
+ local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
+ local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
+ local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
+ local SimpleValue = Number + String + Constant
+ local ArrayContent, ObjectContent
+
+ -- The functions parsearray and parseobject parse only a single value/pair
+ -- at a time and store them directly to avoid hitting the LPeg limits.
+ local function parsearray (str, pos, nullval, state)
+ local obj, cont
+ local start = pos
+ local npos
+ local t, nt = {}, 0
+ repeat
+ obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
+ if cont == 'end' then
+ return ErrorUnterminatedCall (str, start, "array", state)
+ end
+ pos = npos
+ if cont == 'cont' or cont == 'last' then
+ nt = nt + 1
+ t[nt] = obj
+ end
+ until cont ~= 'cont'
+ return pos, setmetatable (t, state.arraymeta)
+ end
+
+ local function parseobject (str, pos, nullval, state)
+ local obj, key, cont
+ local start = pos
+ local npos
+ local t = {}
+ repeat
+ key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
+ if cont == 'end' then
+ return ErrorUnterminatedCall (str, start, "object", state)
+ end
+ pos = npos
+ if cont == 'cont' or cont == 'last' then
+ t[key] = obj
+ end
+ until cont ~= 'cont'
+ return pos, setmetatable (t, state.objectmeta)
+ end
+
+ local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray)
+ local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject)
+ local Value = Space * (Array + Object + SimpleValue)
+ local ExpectedValue = Value + Space * Err "value expected"
+ local ExpectedKey = String + Err "key expected"
+ local End = P(-1) * g.Cc'end'
+ local ErrInvalid = Err "invalid JSON"
+ ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp()
+ local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue)
+ ObjectContent = (g.Cc(nil) * g.Cc(nil) * P"}" * g.Cc'empty' + End + (Pair * Space * (P"," * g.Cc'cont' + P"}" * g.Cc'last' + End + ErrInvalid) + ErrInvalid)) * g.Cp()
+ local DecodeValue = ExpectedValue * g.Cp ()
+
+ jsonlpeg.version = json.version
+ jsonlpeg.encode = json.encode
+ jsonlpeg.null = json.null
+ jsonlpeg.quotestring = json.quotestring
+ jsonlpeg.addnewline = json.addnewline
+ jsonlpeg.encodeexception = json.encodeexception
+ jsonlpeg.using_lpeg = true
+
+ function jsonlpeg.decode (str, pos, nullval, ...)
+ local state = {}
+ state.objectmeta, state.arraymeta = optionalmetatables(...)
+ local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
+ if state.msg then
+ return nil, state.pos, state.msg
+ else
+ return obj, retpos
+ end
+ end
+
+ -- cache result of this function:
+ json.use_lpeg = function () return jsonlpeg end
+ jsonlpeg.use_lpeg = json.use_lpeg
+
+ return jsonlpeg
+end
+
+if always_use_lpeg then
+ return json.use_lpeg()
+end
+
+return json
+