diff options
Diffstat (limited to 'vendor')
| -rw-r--r-- | vendor/argparse.d.tl | 111 | ||||
| -rw-r--r-- | vendor/argparse.lua | 2103 | ||||
| -rw-r--r-- | vendor/compat53/file_mt.lua | 71 | ||||
| -rw-r--r-- | vendor/compat53/init.lua | 325 | ||||
| -rw-r--r-- | vendor/compat53/module.lua | 894 | ||||
| -rw-r--r-- | vendor/dkjson.d.tl | 33 | ||||
| -rw-r--r-- | vendor/dkjson.lua | 749 |
7 files changed, 4286 insertions, 0 deletions
diff --git a/vendor/argparse.d.tl b/vendor/argparse.d.tl new file mode 100644 index 00000000..812786c4 --- /dev/null +++ b/vendor/argparse.d.tl | |||
| @@ -0,0 +1,111 @@ | |||
| 1 | |||
| 2 | local record argparse | ||
| 3 | type Args = {string : {string} | string | boolean} | ||
| 4 | |||
| 5 | record Parser | ||
| 6 | name: function(self: Parser, name: string): Parser | ||
| 7 | description: function(self: Parser, description: string): Parser | ||
| 8 | epilog: function(self: Parser, epilog: string): Parser | ||
| 9 | |||
| 10 | flag: function(self: Parser, flag: string): Option | ||
| 11 | flag: function(self: Parser, shortalias: string, longalias: string): Option | ||
| 12 | |||
| 13 | parse: function(self: Parser, argv: {string}): Args | ||
| 14 | pparse: function(self: Parser, argv: {string}): boolean, Args | string | ||
| 15 | |||
| 16 | error: function(self: Parser, error: string) | ||
| 17 | |||
| 18 | argument: function(self: Parser, name: string, description: string): Argument | ||
| 19 | |||
| 20 | get_usage: function(self: Parser): string | ||
| 21 | get_help: function(self: Parser): string | ||
| 22 | |||
| 23 | option: function(self: Parser, name: string, description?: string, default?: string, convert?: function | {function}, args?: {string}, count?: integer | string): Option | ||
| 24 | option: function(self: Parser, name: string, description?: string, default?: string, convert?: {string:string}, args?: {string}, count?: integer | string): Option | ||
| 25 | |||
| 26 | require_command: function(self: Parser, require_command: boolean): Parser | ||
| 27 | command_target: function(self: Parser, command_target: string): Parser | ||
| 28 | |||
| 29 | command: function(self: Parser, name: string, description: string, epilog: string): Command | ||
| 30 | |||
| 31 | add_help: function(self: Parser, boolean) | ||
| 32 | |||
| 33 | help_max_width: function(self: Parser, number): Parser | ||
| 34 | add_help_command: function(self: Parser, ?string | {string: any}): Parser | ||
| 35 | add_complete_command: function(self: Parser, ?string | {string: any}): Parser | ||
| 36 | |||
| 37 | group: function(self: Parser, ...:any): Parser | ||
| 38 | |||
| 39 | -- TODO: should be Argument | Option | ||
| 40 | mutex: function(self: Parser, ...: any) | ||
| 41 | |||
| 42 | record Opts | ||
| 43 | name: string | ||
| 44 | description: string | ||
| 45 | epilog: string | ||
| 46 | end | ||
| 47 | metamethod __call: function(Parser, Opts): Parser | ||
| 48 | metamethod __call: function(Parser, string, string, string): Parser | ||
| 49 | end | ||
| 50 | |||
| 51 | type ActionCallback = function(args: Args, index: string, val: string | boolean | {string}, overwrite: boolean) | ||
| 52 | |||
| 53 | record Argument | ||
| 54 | choices: function(self: Argument, choices: {string}): Argument | ||
| 55 | |||
| 56 | convert: function(self: Argument, convert: function | {function}): Argument | ||
| 57 | convert: function(self: Argument, convert: {string:string}): Argument | ||
| 58 | |||
| 59 | args: function(self: Argument, args: string | integer): Argument | ||
| 60 | |||
| 61 | action: function(self: Argument, cb: ActionCallback) | ||
| 62 | end | ||
| 63 | |||
| 64 | record Option | ||
| 65 | name: function(self: Option, name: string): Option | ||
| 66 | description: function(self: Option, description: string): Option | ||
| 67 | |||
| 68 | argname: function(self: Option, argname: string | {string}): Option | ||
| 69 | |||
| 70 | count: function(self: Option, count: integer | string): Option | ||
| 71 | |||
| 72 | choices: function(self: Option, {string}): Option | ||
| 73 | |||
| 74 | default: function(self: Option, string): Option | ||
| 75 | |||
| 76 | defmode: function(self: Option, string): Option | ||
| 77 | |||
| 78 | target: function(self: Option, target: string): Option | ||
| 79 | |||
| 80 | args: function(self: Option, args: string|integer): Option | ||
| 81 | |||
| 82 | action: function(self: Option, cb: ActionCallback) | ||
| 83 | |||
| 84 | hidden_name: function(self: Option, string) | ||
| 85 | |||
| 86 | hidden: function(self: Option, boolean) | ||
| 87 | |||
| 88 | convert: function(self: Option, function) | ||
| 89 | end | ||
| 90 | |||
| 91 | record Command | ||
| 92 | summary: function(self: Command, summary: string): Command | ||
| 93 | description: function(self: Command, description: string): Command | ||
| 94 | |||
| 95 | argument: function(self: Command, name: string, description?: string): Argument | ||
| 96 | |||
| 97 | option: function(self: Command, name: string, description: string): Option | ||
| 98 | |||
| 99 | flag: function(self: Command, string, ?string): Option | ||
| 100 | |||
| 101 | handle_options: function(self: Command, boolean): Command | ||
| 102 | |||
| 103 | mutex: function(self: Command, ...: any) --! copied over from Parser | ||
| 104 | |||
| 105 | group: function(self: Command, ...:any): Command --! copied over from Parser | ||
| 106 | end | ||
| 107 | |||
| 108 | metamethod __call: function(self: argparse, name: string, description: string, epilog: string): Parser | ||
| 109 | end | ||
| 110 | |||
| 111 | return argparse | ||
diff --git a/vendor/argparse.lua b/vendor/argparse.lua new file mode 100644 index 00000000..2c2585dd --- /dev/null +++ b/vendor/argparse.lua | |||
| @@ -0,0 +1,2103 @@ | |||
| 1 | -- The MIT License (MIT) | ||
| 2 | |||
| 3 | -- Copyright (c) 2013 - 2018 Peter Melnichenko | ||
| 4 | -- 2019 Paul Ouellette | ||
| 5 | |||
| 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 7 | -- this software and associated documentation files (the "Software"), to deal in | ||
| 8 | -- the Software without restriction, including without limitation the rights to | ||
| 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
| 10 | -- the Software, and to permit persons to whom the Software is furnished to do so, | ||
| 11 | -- subject to the following conditions: | ||
| 12 | |||
| 13 | -- The above copyright notice and this permission notice shall be included in all | ||
| 14 | -- copies or substantial portions of the Software. | ||
| 15 | |||
| 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
| 18 | -- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
| 19 | -- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
| 20 | -- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| 21 | -- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 22 | |||
| 23 | local function deep_update(t1, t2) | ||
| 24 | for k, v in pairs(t2) do | ||
| 25 | if type(v) == "table" then | ||
| 26 | v = deep_update({}, v) | ||
| 27 | end | ||
| 28 | |||
| 29 | t1[k] = v | ||
| 30 | end | ||
| 31 | |||
| 32 | return t1 | ||
| 33 | end | ||
| 34 | |||
| 35 | -- A property is a tuple {name, callback}. | ||
| 36 | -- properties.args is number of properties that can be set as arguments | ||
| 37 | -- when calling an object. | ||
| 38 | local function class(prototype, properties, parent) | ||
| 39 | -- Class is the metatable of its instances. | ||
| 40 | local cl = {} | ||
| 41 | cl.__index = cl | ||
| 42 | |||
| 43 | if parent then | ||
| 44 | cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) | ||
| 45 | else | ||
| 46 | cl.__prototype = prototype | ||
| 47 | end | ||
| 48 | |||
| 49 | if properties then | ||
| 50 | local names = {} | ||
| 51 | |||
| 52 | -- Create setter methods and fill set of property names. | ||
| 53 | for _, property in ipairs(properties) do | ||
| 54 | local name, callback = property[1], property[2] | ||
| 55 | |||
| 56 | cl[name] = function(self, value) | ||
| 57 | if not callback(self, value) then | ||
| 58 | self["_" .. name] = value | ||
| 59 | end | ||
| 60 | |||
| 61 | return self | ||
| 62 | end | ||
| 63 | |||
| 64 | names[name] = true | ||
| 65 | end | ||
| 66 | |||
| 67 | function cl.__call(self, ...) | ||
| 68 | -- When calling an object, if the first argument is a table, | ||
| 69 | -- interpret keys as property names, else delegate arguments | ||
| 70 | -- to corresponding setters in order. | ||
| 71 | if type((...)) == "table" then | ||
| 72 | for name, value in pairs((...)) do | ||
| 73 | if names[name] then | ||
| 74 | self[name](self, value) | ||
| 75 | end | ||
| 76 | end | ||
| 77 | else | ||
| 78 | local nargs = select("#", ...) | ||
| 79 | |||
| 80 | for i, property in ipairs(properties) do | ||
| 81 | if i > nargs or i > properties.args then | ||
| 82 | break | ||
| 83 | end | ||
| 84 | |||
| 85 | local arg = select(i, ...) | ||
| 86 | |||
| 87 | if arg ~= nil then | ||
| 88 | self[property[1]](self, arg) | ||
| 89 | end | ||
| 90 | end | ||
| 91 | end | ||
| 92 | |||
| 93 | return self | ||
| 94 | end | ||
| 95 | end | ||
| 96 | |||
| 97 | -- If indexing class fails, fallback to its parent. | ||
| 98 | local class_metatable = {} | ||
| 99 | class_metatable.__index = parent | ||
| 100 | |||
| 101 | function class_metatable.__call(self, ...) | ||
| 102 | -- Calling a class returns its instance. | ||
| 103 | -- Arguments are delegated to the instance. | ||
| 104 | local object = deep_update({}, self.__prototype) | ||
| 105 | setmetatable(object, self) | ||
| 106 | return object(...) | ||
| 107 | end | ||
| 108 | |||
| 109 | return setmetatable(cl, class_metatable) | ||
| 110 | end | ||
| 111 | |||
| 112 | local function typecheck(name, types, value) | ||
| 113 | for _, type_ in ipairs(types) do | ||
| 114 | if type(value) == type_ then | ||
| 115 | return true | ||
| 116 | end | ||
| 117 | end | ||
| 118 | |||
| 119 | error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) | ||
| 120 | end | ||
| 121 | |||
| 122 | local function typechecked(name, ...) | ||
| 123 | local types = {...} | ||
| 124 | return {name, function(_, value) typecheck(name, types, value) end} | ||
| 125 | end | ||
| 126 | |||
| 127 | local multiname = {"name", function(self, value) | ||
| 128 | typecheck("name", {"string"}, value) | ||
| 129 | |||
| 130 | for alias in value:gmatch("%S+") do | ||
| 131 | self._name = self._name or alias | ||
| 132 | table.insert(self._aliases, alias) | ||
| 133 | table.insert(self._public_aliases, alias) | ||
| 134 | -- If alias contains '_', accept '-' also. | ||
| 135 | if alias:find("_", 1, true) then | ||
| 136 | table.insert(self._aliases, (alias:gsub("_", "-"))) | ||
| 137 | end | ||
| 138 | end | ||
| 139 | |||
| 140 | -- Do not set _name as with other properties. | ||
| 141 | return true | ||
| 142 | end} | ||
| 143 | |||
| 144 | local multiname_hidden = {"hidden_name", function(self, value) | ||
| 145 | typecheck("hidden_name", {"string"}, value) | ||
| 146 | |||
| 147 | for alias in value:gmatch("%S+") do | ||
| 148 | table.insert(self._aliases, alias) | ||
| 149 | if alias:find("_", 1, true) then | ||
| 150 | table.insert(self._aliases, (alias:gsub("_", "-"))) | ||
| 151 | end | ||
| 152 | end | ||
| 153 | |||
| 154 | return true | ||
| 155 | end} | ||
| 156 | |||
| 157 | local function parse_boundaries(str) | ||
| 158 | if tonumber(str) then | ||
| 159 | return tonumber(str), tonumber(str) | ||
| 160 | end | ||
| 161 | |||
| 162 | if str == "*" then | ||
| 163 | return 0, math.huge | ||
| 164 | end | ||
| 165 | |||
| 166 | if str == "+" then | ||
| 167 | return 1, math.huge | ||
| 168 | end | ||
| 169 | |||
| 170 | if str == "?" then | ||
| 171 | return 0, 1 | ||
| 172 | end | ||
| 173 | |||
| 174 | if str:match "^%d+%-%d+$" then | ||
| 175 | local min, max = str:match "^(%d+)%-(%d+)$" | ||
| 176 | return tonumber(min), tonumber(max) | ||
| 177 | end | ||
| 178 | |||
| 179 | if str:match "^%d+%+$" then | ||
| 180 | local min = str:match "^(%d+)%+$" | ||
| 181 | return tonumber(min), math.huge | ||
| 182 | end | ||
| 183 | end | ||
| 184 | |||
| 185 | local function boundaries(name) | ||
| 186 | return {name, function(self, value) | ||
| 187 | typecheck(name, {"number", "string"}, value) | ||
| 188 | |||
| 189 | local min, max = parse_boundaries(value) | ||
| 190 | |||
| 191 | if not min then | ||
| 192 | error(("bad property '%s'"):format(name)) | ||
| 193 | end | ||
| 194 | |||
| 195 | self["_min" .. name], self["_max" .. name] = min, max | ||
| 196 | end} | ||
| 197 | end | ||
| 198 | |||
| 199 | local actions = {} | ||
| 200 | |||
| 201 | local option_action = {"action", function(_, value) | ||
| 202 | typecheck("action", {"function", "string"}, value) | ||
| 203 | |||
| 204 | if type(value) == "string" and not actions[value] then | ||
| 205 | error(("unknown action '%s'"):format(value)) | ||
| 206 | end | ||
| 207 | end} | ||
| 208 | |||
| 209 | local option_init = {"init", function(self) | ||
| 210 | self._has_init = true | ||
| 211 | end} | ||
| 212 | |||
| 213 | local option_default = {"default", function(self, value) | ||
| 214 | if type(value) ~= "string" then | ||
| 215 | self._init = value | ||
| 216 | self._has_init = true | ||
| 217 | return true | ||
| 218 | end | ||
| 219 | end} | ||
| 220 | |||
| 221 | local add_help = {"add_help", function(self, value) | ||
| 222 | typecheck("add_help", {"boolean", "string", "table"}, value) | ||
| 223 | |||
| 224 | if self._help_option_idx then | ||
| 225 | table.remove(self._options, self._help_option_idx) | ||
| 226 | self._help_option_idx = nil | ||
| 227 | end | ||
| 228 | |||
| 229 | if value then | ||
| 230 | local help = self:flag() | ||
| 231 | :description "Show this help message and exit." | ||
| 232 | :action(function() | ||
| 233 | print(self:get_help()) | ||
| 234 | os.exit(0) | ||
| 235 | end) | ||
| 236 | |||
| 237 | if value ~= true then | ||
| 238 | help = help(value) | ||
| 239 | end | ||
| 240 | |||
| 241 | if not help._name then | ||
| 242 | help "-h" "--help" | ||
| 243 | end | ||
| 244 | |||
| 245 | self._help_option_idx = #self._options | ||
| 246 | end | ||
| 247 | end} | ||
| 248 | |||
| 249 | local Parser = class({ | ||
| 250 | _arguments = {}, | ||
| 251 | _options = {}, | ||
| 252 | _commands = {}, | ||
| 253 | _mutexes = {}, | ||
| 254 | _groups = {}, | ||
| 255 | _require_command = true, | ||
| 256 | _handle_options = true | ||
| 257 | }, { | ||
| 258 | args = 3, | ||
| 259 | typechecked("name", "string"), | ||
| 260 | typechecked("description", "string"), | ||
| 261 | typechecked("epilog", "string"), | ||
| 262 | typechecked("usage", "string"), | ||
| 263 | typechecked("help", "string"), | ||
| 264 | typechecked("require_command", "boolean"), | ||
| 265 | typechecked("handle_options", "boolean"), | ||
| 266 | typechecked("action", "function"), | ||
| 267 | typechecked("command_target", "string"), | ||
| 268 | typechecked("help_vertical_space", "number"), | ||
| 269 | typechecked("usage_margin", "number"), | ||
| 270 | typechecked("usage_max_width", "number"), | ||
| 271 | typechecked("help_usage_margin", "number"), | ||
| 272 | typechecked("help_description_margin", "number"), | ||
| 273 | typechecked("help_max_width", "number"), | ||
| 274 | add_help | ||
| 275 | }) | ||
| 276 | |||
| 277 | local Command = class({ | ||
| 278 | _aliases = {}, | ||
| 279 | _public_aliases = {} | ||
| 280 | }, { | ||
| 281 | args = 3, | ||
| 282 | multiname, | ||
| 283 | typechecked("description", "string"), | ||
| 284 | typechecked("epilog", "string"), | ||
| 285 | multiname_hidden, | ||
| 286 | typechecked("summary", "string"), | ||
| 287 | typechecked("target", "string"), | ||
| 288 | typechecked("usage", "string"), | ||
| 289 | typechecked("help", "string"), | ||
| 290 | typechecked("require_command", "boolean"), | ||
| 291 | typechecked("handle_options", "boolean"), | ||
| 292 | typechecked("action", "function"), | ||
| 293 | typechecked("command_target", "string"), | ||
| 294 | typechecked("help_vertical_space", "number"), | ||
| 295 | typechecked("usage_margin", "number"), | ||
| 296 | typechecked("usage_max_width", "number"), | ||
| 297 | typechecked("help_usage_margin", "number"), | ||
| 298 | typechecked("help_description_margin", "number"), | ||
| 299 | typechecked("help_max_width", "number"), | ||
| 300 | typechecked("hidden", "boolean"), | ||
| 301 | add_help | ||
| 302 | }, Parser) | ||
| 303 | |||
| 304 | local Argument = class({ | ||
| 305 | _minargs = 1, | ||
| 306 | _maxargs = 1, | ||
| 307 | _mincount = 1, | ||
| 308 | _maxcount = 1, | ||
| 309 | _defmode = "unused", | ||
| 310 | _show_default = true | ||
| 311 | }, { | ||
| 312 | args = 5, | ||
| 313 | typechecked("name", "string"), | ||
| 314 | typechecked("description", "string"), | ||
| 315 | option_default, | ||
| 316 | typechecked("convert", "function", "table"), | ||
| 317 | boundaries("args"), | ||
| 318 | typechecked("target", "string"), | ||
| 319 | typechecked("defmode", "string"), | ||
| 320 | typechecked("show_default", "boolean"), | ||
| 321 | typechecked("argname", "string", "table"), | ||
| 322 | typechecked("choices", "table"), | ||
| 323 | typechecked("hidden", "boolean"), | ||
| 324 | option_action, | ||
| 325 | option_init | ||
| 326 | }) | ||
| 327 | |||
| 328 | local Option = class({ | ||
| 329 | _aliases = {}, | ||
| 330 | _public_aliases = {}, | ||
| 331 | _mincount = 0, | ||
| 332 | _overwrite = true | ||
| 333 | }, { | ||
| 334 | args = 6, | ||
| 335 | multiname, | ||
| 336 | typechecked("description", "string"), | ||
| 337 | option_default, | ||
| 338 | typechecked("convert", "function", "table"), | ||
| 339 | boundaries("args"), | ||
| 340 | boundaries("count"), | ||
| 341 | multiname_hidden, | ||
| 342 | typechecked("target", "string"), | ||
| 343 | typechecked("defmode", "string"), | ||
| 344 | typechecked("show_default", "boolean"), | ||
| 345 | typechecked("overwrite", "boolean"), | ||
| 346 | typechecked("argname", "string", "table"), | ||
| 347 | typechecked("choices", "table"), | ||
| 348 | typechecked("hidden", "boolean"), | ||
| 349 | option_action, | ||
| 350 | option_init | ||
| 351 | }, Argument) | ||
| 352 | |||
| 353 | function Parser:_inherit_property(name, default) | ||
| 354 | local element = self | ||
| 355 | |||
| 356 | while true do | ||
| 357 | local value = element["_" .. name] | ||
| 358 | |||
| 359 | if value ~= nil then | ||
| 360 | return value | ||
| 361 | end | ||
| 362 | |||
| 363 | if not element._parent then | ||
| 364 | return default | ||
| 365 | end | ||
| 366 | |||
| 367 | element = element._parent | ||
| 368 | end | ||
| 369 | end | ||
| 370 | |||
| 371 | function Argument:_get_argument_list() | ||
| 372 | local buf = {} | ||
| 373 | local i = 1 | ||
| 374 | |||
| 375 | while i <= math.min(self._minargs, 3) do | ||
| 376 | local argname = self:_get_argname(i) | ||
| 377 | |||
| 378 | if self._default and self._defmode:find "a" then | ||
| 379 | argname = "[" .. argname .. "]" | ||
| 380 | end | ||
| 381 | |||
| 382 | table.insert(buf, argname) | ||
| 383 | i = i+1 | ||
| 384 | end | ||
| 385 | |||
| 386 | while i <= math.min(self._maxargs, 3) do | ||
| 387 | table.insert(buf, "[" .. self:_get_argname(i) .. "]") | ||
| 388 | i = i+1 | ||
| 389 | |||
| 390 | if self._maxargs == math.huge then | ||
| 391 | break | ||
| 392 | end | ||
| 393 | end | ||
| 394 | |||
| 395 | if i < self._maxargs then | ||
| 396 | table.insert(buf, "...") | ||
| 397 | end | ||
| 398 | |||
| 399 | return buf | ||
| 400 | end | ||
| 401 | |||
| 402 | function Argument:_get_usage() | ||
| 403 | local usage = table.concat(self:_get_argument_list(), " ") | ||
| 404 | |||
| 405 | if self._default and self._defmode:find "u" then | ||
| 406 | if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then | ||
| 407 | usage = "[" .. usage .. "]" | ||
| 408 | end | ||
| 409 | end | ||
| 410 | |||
| 411 | return usage | ||
| 412 | end | ||
| 413 | |||
| 414 | function actions.store_true(result, target) | ||
| 415 | result[target] = true | ||
| 416 | end | ||
| 417 | |||
| 418 | function actions.store_false(result, target) | ||
| 419 | result[target] = false | ||
| 420 | end | ||
| 421 | |||
| 422 | function actions.store(result, target, argument) | ||
| 423 | result[target] = argument | ||
| 424 | end | ||
| 425 | |||
| 426 | function actions.count(result, target, _, overwrite) | ||
| 427 | if not overwrite then | ||
| 428 | result[target] = result[target] + 1 | ||
| 429 | end | ||
| 430 | end | ||
| 431 | |||
| 432 | function actions.append(result, target, argument, overwrite) | ||
| 433 | result[target] = result[target] or {} | ||
| 434 | table.insert(result[target], argument) | ||
| 435 | |||
| 436 | if overwrite then | ||
| 437 | table.remove(result[target], 1) | ||
| 438 | end | ||
| 439 | end | ||
| 440 | |||
| 441 | function actions.concat(result, target, arguments, overwrite) | ||
| 442 | if overwrite then | ||
| 443 | error("'concat' action can't handle too many invocations") | ||
| 444 | end | ||
| 445 | |||
| 446 | result[target] = result[target] or {} | ||
| 447 | |||
| 448 | for _, argument in ipairs(arguments) do | ||
| 449 | table.insert(result[target], argument) | ||
| 450 | end | ||
| 451 | end | ||
| 452 | |||
| 453 | function Argument:_get_action() | ||
| 454 | local action, init | ||
| 455 | |||
| 456 | if self._maxcount == 1 then | ||
| 457 | if self._maxargs == 0 then | ||
| 458 | action, init = "store_true", nil | ||
| 459 | else | ||
| 460 | action, init = "store", nil | ||
| 461 | end | ||
| 462 | else | ||
| 463 | if self._maxargs == 0 then | ||
| 464 | action, init = "count", 0 | ||
| 465 | else | ||
| 466 | action, init = "append", {} | ||
| 467 | end | ||
| 468 | end | ||
| 469 | |||
| 470 | if self._action then | ||
| 471 | action = self._action | ||
| 472 | end | ||
| 473 | |||
| 474 | if self._has_init then | ||
| 475 | init = self._init | ||
| 476 | end | ||
| 477 | |||
| 478 | if type(action) == "string" then | ||
| 479 | action = actions[action] | ||
| 480 | end | ||
| 481 | |||
| 482 | return action, init | ||
| 483 | end | ||
| 484 | |||
| 485 | -- Returns placeholder for `narg`-th argument. | ||
| 486 | function Argument:_get_argname(narg) | ||
| 487 | local argname = self._argname or self:_get_default_argname() | ||
| 488 | |||
| 489 | if type(argname) == "table" then | ||
| 490 | return argname[narg] | ||
| 491 | else | ||
| 492 | return argname | ||
| 493 | end | ||
| 494 | end | ||
| 495 | |||
| 496 | function Argument:_get_choices_list() | ||
| 497 | return "{" .. table.concat(self._choices, ",") .. "}" | ||
| 498 | end | ||
| 499 | |||
| 500 | function Argument:_get_default_argname() | ||
| 501 | if self._choices then | ||
| 502 | return self:_get_choices_list() | ||
| 503 | else | ||
| 504 | return "<" .. self._name .. ">" | ||
| 505 | end | ||
| 506 | end | ||
| 507 | |||
| 508 | function Option:_get_default_argname() | ||
| 509 | if self._choices then | ||
| 510 | return self:_get_choices_list() | ||
| 511 | else | ||
| 512 | return "<" .. self:_get_default_target() .. ">" | ||
| 513 | end | ||
| 514 | end | ||
| 515 | |||
| 516 | -- Returns labels to be shown in the help message. | ||
| 517 | function Argument:_get_label_lines() | ||
| 518 | if self._choices then | ||
| 519 | return {self:_get_choices_list()} | ||
| 520 | else | ||
| 521 | return {self._name} | ||
| 522 | end | ||
| 523 | end | ||
| 524 | |||
| 525 | function Option:_get_label_lines() | ||
| 526 | local argument_list = self:_get_argument_list() | ||
| 527 | |||
| 528 | if #argument_list == 0 then | ||
| 529 | -- Don't put aliases for simple flags like `-h` on different lines. | ||
| 530 | return {table.concat(self._public_aliases, ", ")} | ||
| 531 | end | ||
| 532 | |||
| 533 | local longest_alias_length = -1 | ||
| 534 | |||
| 535 | for _, alias in ipairs(self._public_aliases) do | ||
| 536 | longest_alias_length = math.max(longest_alias_length, #alias) | ||
| 537 | end | ||
| 538 | |||
| 539 | local argument_list_repr = table.concat(argument_list, " ") | ||
| 540 | local lines = {} | ||
| 541 | |||
| 542 | for i, alias in ipairs(self._public_aliases) do | ||
| 543 | local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr | ||
| 544 | |||
| 545 | if i ~= #self._public_aliases then | ||
| 546 | line = line .. "," | ||
| 547 | end | ||
| 548 | |||
| 549 | table.insert(lines, line) | ||
| 550 | end | ||
| 551 | |||
| 552 | return lines | ||
| 553 | end | ||
| 554 | |||
| 555 | function Command:_get_label_lines() | ||
| 556 | return {table.concat(self._public_aliases, ", ")} | ||
| 557 | end | ||
| 558 | |||
| 559 | function Argument:_get_description() | ||
| 560 | if self._default and self._show_default then | ||
| 561 | if self._description then | ||
| 562 | return ("%s (default: %s)"):format(self._description, self._default) | ||
| 563 | else | ||
| 564 | return ("default: %s"):format(self._default) | ||
| 565 | end | ||
| 566 | else | ||
| 567 | return self._description or "" | ||
| 568 | end | ||
| 569 | end | ||
| 570 | |||
| 571 | function Command:_get_description() | ||
| 572 | return self._summary or self._description or "" | ||
| 573 | end | ||
| 574 | |||
| 575 | function Option:_get_usage() | ||
| 576 | local usage = self:_get_argument_list() | ||
| 577 | table.insert(usage, 1, self._name) | ||
| 578 | usage = table.concat(usage, " ") | ||
| 579 | |||
| 580 | if self._mincount == 0 or self._default then | ||
| 581 | usage = "[" .. usage .. "]" | ||
| 582 | end | ||
| 583 | |||
| 584 | return usage | ||
| 585 | end | ||
| 586 | |||
| 587 | function Argument:_get_default_target() | ||
| 588 | return self._name | ||
| 589 | end | ||
| 590 | |||
| 591 | function Option:_get_default_target() | ||
| 592 | local res | ||
| 593 | |||
| 594 | for _, alias in ipairs(self._public_aliases) do | ||
| 595 | if alias:sub(1, 1) == alias:sub(2, 2) then | ||
| 596 | res = alias:sub(3) | ||
| 597 | break | ||
| 598 | end | ||
| 599 | end | ||
| 600 | |||
| 601 | res = res or self._name:sub(2) | ||
| 602 | return (res:gsub("-", "_")) | ||
| 603 | end | ||
| 604 | |||
| 605 | function Option:_is_vararg() | ||
| 606 | return self._maxargs ~= self._minargs | ||
| 607 | end | ||
| 608 | |||
| 609 | function Parser:_get_fullname(exclude_root) | ||
| 610 | local parent = self._parent | ||
| 611 | if exclude_root and not parent then | ||
| 612 | return "" | ||
| 613 | end | ||
| 614 | local buf = {self._name} | ||
| 615 | |||
| 616 | while parent do | ||
| 617 | if not exclude_root or parent._parent then | ||
| 618 | table.insert(buf, 1, parent._name) | ||
| 619 | end | ||
| 620 | parent = parent._parent | ||
| 621 | end | ||
| 622 | |||
| 623 | return table.concat(buf, " ") | ||
| 624 | end | ||
| 625 | |||
| 626 | function Parser:_update_charset(charset) | ||
| 627 | charset = charset or {} | ||
| 628 | |||
| 629 | for _, command in ipairs(self._commands) do | ||
| 630 | command:_update_charset(charset) | ||
| 631 | end | ||
| 632 | |||
| 633 | for _, option in ipairs(self._options) do | ||
| 634 | for _, alias in ipairs(option._aliases) do | ||
| 635 | charset[alias:sub(1, 1)] = true | ||
| 636 | end | ||
| 637 | end | ||
| 638 | |||
| 639 | return charset | ||
| 640 | end | ||
| 641 | |||
| 642 | function Parser:argument(...) | ||
| 643 | local argument = Argument(...) | ||
| 644 | table.insert(self._arguments, argument) | ||
| 645 | return argument | ||
| 646 | end | ||
| 647 | |||
| 648 | function Parser:option(...) | ||
| 649 | local option = Option(...) | ||
| 650 | table.insert(self._options, option) | ||
| 651 | return option | ||
| 652 | end | ||
| 653 | |||
| 654 | function Parser:flag(...) | ||
| 655 | return self:option():args(0)(...) | ||
| 656 | end | ||
| 657 | |||
| 658 | function Parser:command(...) | ||
| 659 | local command = Command():add_help(true)(...) | ||
| 660 | command._parent = self | ||
| 661 | table.insert(self._commands, command) | ||
| 662 | return command | ||
| 663 | end | ||
| 664 | |||
| 665 | function Parser:mutex(...) | ||
| 666 | local elements = {...} | ||
| 667 | |||
| 668 | for i, element in ipairs(elements) do | ||
| 669 | local mt = getmetatable(element) | ||
| 670 | assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) | ||
| 671 | end | ||
| 672 | |||
| 673 | table.insert(self._mutexes, elements) | ||
| 674 | return self | ||
| 675 | end | ||
| 676 | |||
| 677 | function Parser:group(name, ...) | ||
| 678 | assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) | ||
| 679 | |||
| 680 | local group = {name = name, ...} | ||
| 681 | |||
| 682 | for i, element in ipairs(group) do | ||
| 683 | local mt = getmetatable(element) | ||
| 684 | assert(mt == Option or mt == Argument or mt == Command, | ||
| 685 | ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) | ||
| 686 | end | ||
| 687 | |||
| 688 | table.insert(self._groups, group) | ||
| 689 | return self | ||
| 690 | end | ||
| 691 | |||
| 692 | local usage_welcome = "Usage: " | ||
| 693 | |||
| 694 | function Parser:get_usage() | ||
| 695 | if self._usage then | ||
| 696 | return self._usage | ||
| 697 | end | ||
| 698 | |||
| 699 | local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) | ||
| 700 | local max_usage_width = self:_inherit_property("usage_max_width", 70) | ||
| 701 | local lines = {usage_welcome .. self:_get_fullname()} | ||
| 702 | |||
| 703 | local function add(s) | ||
| 704 | if #lines[#lines]+1+#s <= max_usage_width then | ||
| 705 | lines[#lines] = lines[#lines] .. " " .. s | ||
| 706 | else | ||
| 707 | lines[#lines+1] = (" "):rep(usage_margin) .. s | ||
| 708 | end | ||
| 709 | end | ||
| 710 | |||
| 711 | -- Normally options are before positional arguments in usage messages. | ||
| 712 | -- However, vararg options should be after, because they can't be reliable used | ||
| 713 | -- before a positional argument. | ||
| 714 | -- Mutexes come into play, too, and are shown as soon as possible. | ||
| 715 | -- Overall, output usages in the following order: | ||
| 716 | -- 1. Mutexes that don't have positional arguments or vararg options. | ||
| 717 | -- 2. Options that are not in any mutexes and are not vararg. | ||
| 718 | -- 3. Positional arguments - on their own or as a part of a mutex. | ||
| 719 | -- 4. Remaining mutexes. | ||
| 720 | -- 5. Remaining options. | ||
| 721 | |||
| 722 | local elements_in_mutexes = {} | ||
| 723 | local added_elements = {} | ||
| 724 | local added_mutexes = {} | ||
| 725 | local argument_to_mutexes = {} | ||
| 726 | |||
| 727 | local function add_mutex(mutex, main_argument) | ||
| 728 | if added_mutexes[mutex] then | ||
| 729 | return | ||
| 730 | end | ||
| 731 | |||
| 732 | added_mutexes[mutex] = true | ||
| 733 | local buf = {} | ||
| 734 | |||
| 735 | for _, element in ipairs(mutex) do | ||
| 736 | if not element._hidden and not added_elements[element] then | ||
| 737 | if getmetatable(element) == Option or element == main_argument then | ||
| 738 | table.insert(buf, element:_get_usage()) | ||
| 739 | added_elements[element] = true | ||
| 740 | end | ||
| 741 | end | ||
| 742 | end | ||
| 743 | |||
| 744 | if #buf == 1 then | ||
| 745 | add(buf[1]) | ||
| 746 | elseif #buf > 1 then | ||
| 747 | add("(" .. table.concat(buf, " | ") .. ")") | ||
| 748 | end | ||
| 749 | end | ||
| 750 | |||
| 751 | local function add_element(element) | ||
| 752 | if not element._hidden and not added_elements[element] then | ||
| 753 | add(element:_get_usage()) | ||
| 754 | added_elements[element] = true | ||
| 755 | end | ||
| 756 | end | ||
| 757 | |||
| 758 | for _, mutex in ipairs(self._mutexes) do | ||
| 759 | local is_vararg = false | ||
| 760 | local has_argument = false | ||
| 761 | |||
| 762 | for _, element in ipairs(mutex) do | ||
| 763 | if getmetatable(element) == Option then | ||
| 764 | if element:_is_vararg() then | ||
| 765 | is_vararg = true | ||
| 766 | end | ||
| 767 | else | ||
| 768 | has_argument = true | ||
| 769 | argument_to_mutexes[element] = argument_to_mutexes[element] or {} | ||
| 770 | table.insert(argument_to_mutexes[element], mutex) | ||
| 771 | end | ||
| 772 | |||
| 773 | elements_in_mutexes[element] = true | ||
| 774 | end | ||
| 775 | |||
| 776 | if not is_vararg and not has_argument then | ||
| 777 | add_mutex(mutex) | ||
| 778 | end | ||
| 779 | end | ||
| 780 | |||
| 781 | for _, option in ipairs(self._options) do | ||
| 782 | if not elements_in_mutexes[option] and not option:_is_vararg() then | ||
| 783 | add_element(option) | ||
| 784 | end | ||
| 785 | end | ||
| 786 | |||
| 787 | -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. | ||
| 788 | for _, argument in ipairs(self._arguments) do | ||
| 789 | -- Pick a mutex as a part of which to show this argument, take the first one that's still available. | ||
| 790 | local mutex | ||
| 791 | |||
| 792 | if elements_in_mutexes[argument] then | ||
| 793 | for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do | ||
| 794 | if not added_mutexes[argument_mutex] then | ||
| 795 | mutex = argument_mutex | ||
| 796 | end | ||
| 797 | end | ||
| 798 | end | ||
| 799 | |||
| 800 | if mutex then | ||
| 801 | add_mutex(mutex, argument) | ||
| 802 | else | ||
| 803 | add_element(argument) | ||
| 804 | end | ||
| 805 | end | ||
| 806 | |||
| 807 | for _, mutex in ipairs(self._mutexes) do | ||
| 808 | add_mutex(mutex) | ||
| 809 | end | ||
| 810 | |||
| 811 | for _, option in ipairs(self._options) do | ||
| 812 | add_element(option) | ||
| 813 | end | ||
| 814 | |||
| 815 | if #self._commands > 0 then | ||
| 816 | if self._require_command then | ||
| 817 | add("<command>") | ||
| 818 | else | ||
| 819 | add("[<command>]") | ||
| 820 | end | ||
| 821 | |||
| 822 | add("...") | ||
| 823 | end | ||
| 824 | |||
| 825 | return table.concat(lines, "\n") | ||
| 826 | end | ||
| 827 | |||
| 828 | local function split_lines(s) | ||
| 829 | if s == "" then | ||
| 830 | return {} | ||
| 831 | end | ||
| 832 | |||
| 833 | local lines = {} | ||
| 834 | |||
| 835 | if s:sub(-1) ~= "\n" then | ||
| 836 | s = s .. "\n" | ||
| 837 | end | ||
| 838 | |||
| 839 | for line in s:gmatch("([^\n]*)\n") do | ||
| 840 | table.insert(lines, line) | ||
| 841 | end | ||
| 842 | |||
| 843 | return lines | ||
| 844 | end | ||
| 845 | |||
| 846 | local function autowrap_line(line, max_length) | ||
| 847 | -- Algorithm for splitting lines is simple and greedy. | ||
| 848 | local result_lines = {} | ||
| 849 | |||
| 850 | -- Preserve original indentation of the line, put this at the beginning of each result line. | ||
| 851 | -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts | ||
| 852 | -- of the second and the following lines vertically align with the start of the second word. | ||
| 853 | local indentation = line:match("^ *") | ||
| 854 | |||
| 855 | if line:find("^ *[%*%+%-]") then | ||
| 856 | indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") | ||
| 857 | end | ||
| 858 | |||
| 859 | -- Parts of the last line being assembled. | ||
| 860 | local line_parts = {} | ||
| 861 | |||
| 862 | -- Length of the current line. | ||
| 863 | local line_length = 0 | ||
| 864 | |||
| 865 | -- Index of the next character to consider. | ||
| 866 | local index = 1 | ||
| 867 | |||
| 868 | while true do | ||
| 869 | local word_start, word_finish, word = line:find("([^ ]+)", index) | ||
| 870 | |||
| 871 | if not word_start then | ||
| 872 | -- Ignore trailing spaces, if any. | ||
| 873 | break | ||
| 874 | end | ||
| 875 | |||
| 876 | local preceding_spaces = line:sub(index, word_start - 1) | ||
| 877 | index = word_finish + 1 | ||
| 878 | |||
| 879 | if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then | ||
| 880 | -- Either this is the very first word or it fits as an addition to the current line, add it. | ||
| 881 | table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. | ||
| 882 | table.insert(line_parts, word) | ||
| 883 | line_length = line_length + #preceding_spaces + #word | ||
| 884 | else | ||
| 885 | -- Does not fit, finish current line and put the word into a new one. | ||
| 886 | table.insert(result_lines, table.concat(line_parts)) | ||
| 887 | line_parts = {indentation, word} | ||
| 888 | line_length = #indentation + #word | ||
| 889 | end | ||
| 890 | end | ||
| 891 | |||
| 892 | if #line_parts > 0 then | ||
| 893 | table.insert(result_lines, table.concat(line_parts)) | ||
| 894 | end | ||
| 895 | |||
| 896 | if #result_lines == 0 then | ||
| 897 | -- Preserve empty lines. | ||
| 898 | result_lines[1] = "" | ||
| 899 | end | ||
| 900 | |||
| 901 | return result_lines | ||
| 902 | end | ||
| 903 | |||
| 904 | -- Automatically wraps lines within given array, | ||
| 905 | -- attempting to limit line length to `max_length`. | ||
| 906 | -- Existing line splits are preserved. | ||
| 907 | local function autowrap(lines, max_length) | ||
| 908 | local result_lines = {} | ||
| 909 | |||
| 910 | for _, line in ipairs(lines) do | ||
| 911 | local autowrapped_lines = autowrap_line(line, max_length) | ||
| 912 | |||
| 913 | for _, autowrapped_line in ipairs(autowrapped_lines) do | ||
| 914 | table.insert(result_lines, autowrapped_line) | ||
| 915 | end | ||
| 916 | end | ||
| 917 | |||
| 918 | return result_lines | ||
| 919 | end | ||
| 920 | |||
| 921 | function Parser:_get_element_help(element) | ||
| 922 | local label_lines = element:_get_label_lines() | ||
| 923 | local description_lines = split_lines(element:_get_description()) | ||
| 924 | |||
| 925 | local result_lines = {} | ||
| 926 | |||
| 927 | -- All label lines should have the same length (except the last one, it has no comma). | ||
| 928 | -- If too long, start description after all the label lines. | ||
| 929 | -- Otherwise, combine label and description lines. | ||
| 930 | |||
| 931 | local usage_margin_len = self:_inherit_property("help_usage_margin", 3) | ||
| 932 | local usage_margin = (" "):rep(usage_margin_len) | ||
| 933 | local description_margin_len = self:_inherit_property("help_description_margin", 25) | ||
| 934 | local description_margin = (" "):rep(description_margin_len) | ||
| 935 | |||
| 936 | local help_max_width = self:_inherit_property("help_max_width") | ||
| 937 | |||
| 938 | if help_max_width then | ||
| 939 | local description_max_width = math.max(help_max_width - description_margin_len, 10) | ||
| 940 | description_lines = autowrap(description_lines, description_max_width) | ||
| 941 | end | ||
| 942 | |||
| 943 | if #label_lines[1] >= (description_margin_len - usage_margin_len) then | ||
| 944 | for _, label_line in ipairs(label_lines) do | ||
| 945 | table.insert(result_lines, usage_margin .. label_line) | ||
| 946 | end | ||
| 947 | |||
| 948 | for _, description_line in ipairs(description_lines) do | ||
| 949 | table.insert(result_lines, description_margin .. description_line) | ||
| 950 | end | ||
| 951 | else | ||
| 952 | for i = 1, math.max(#label_lines, #description_lines) do | ||
| 953 | local label_line = label_lines[i] | ||
| 954 | local description_line = description_lines[i] | ||
| 955 | |||
| 956 | local line = "" | ||
| 957 | |||
| 958 | if label_line then | ||
| 959 | line = usage_margin .. label_line | ||
| 960 | end | ||
| 961 | |||
| 962 | if description_line and description_line ~= "" then | ||
| 963 | line = line .. (" "):rep(description_margin_len - #line) .. description_line | ||
| 964 | end | ||
| 965 | |||
| 966 | table.insert(result_lines, line) | ||
| 967 | end | ||
| 968 | end | ||
| 969 | |||
| 970 | return table.concat(result_lines, "\n") | ||
| 971 | end | ||
| 972 | |||
| 973 | local function get_group_types(group) | ||
| 974 | local types = {} | ||
| 975 | |||
| 976 | for _, element in ipairs(group) do | ||
| 977 | types[getmetatable(element)] = true | ||
| 978 | end | ||
| 979 | |||
| 980 | return types | ||
| 981 | end | ||
| 982 | |||
| 983 | function Parser:_add_group_help(blocks, added_elements, label, elements) | ||
| 984 | local buf = {label} | ||
| 985 | |||
| 986 | for _, element in ipairs(elements) do | ||
| 987 | if not element._hidden and not added_elements[element] then | ||
| 988 | added_elements[element] = true | ||
| 989 | table.insert(buf, self:_get_element_help(element)) | ||
| 990 | end | ||
| 991 | end | ||
| 992 | |||
| 993 | if #buf > 1 then | ||
| 994 | table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) | ||
| 995 | end | ||
| 996 | end | ||
| 997 | |||
| 998 | function Parser:get_help() | ||
| 999 | if self._help then | ||
| 1000 | return self._help | ||
| 1001 | end | ||
| 1002 | |||
| 1003 | local blocks = {self:get_usage()} | ||
| 1004 | |||
| 1005 | local help_max_width = self:_inherit_property("help_max_width") | ||
| 1006 | |||
| 1007 | if self._description then | ||
| 1008 | local description = self._description | ||
| 1009 | |||
| 1010 | if help_max_width then | ||
| 1011 | description = table.concat(autowrap(split_lines(description), help_max_width), "\n") | ||
| 1012 | end | ||
| 1013 | |||
| 1014 | table.insert(blocks, description) | ||
| 1015 | end | ||
| 1016 | |||
| 1017 | -- 1. Put groups containing arguments first, then other arguments. | ||
| 1018 | -- 2. Put remaining groups containing options, then other options. | ||
| 1019 | -- 3. Put remaining groups containing commands, then other commands. | ||
| 1020 | -- Assume that an element can't be in several groups. | ||
| 1021 | local groups_by_type = { | ||
| 1022 | [Argument] = {}, | ||
| 1023 | [Option] = {}, | ||
| 1024 | [Command] = {} | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | for _, group in ipairs(self._groups) do | ||
| 1028 | local group_types = get_group_types(group) | ||
| 1029 | |||
| 1030 | for _, mt in ipairs({Argument, Option, Command}) do | ||
| 1031 | if group_types[mt] then | ||
| 1032 | table.insert(groups_by_type[mt], group) | ||
| 1033 | break | ||
| 1034 | end | ||
| 1035 | end | ||
| 1036 | end | ||
| 1037 | |||
| 1038 | local default_groups = { | ||
| 1039 | {name = "Arguments", type = Argument, elements = self._arguments}, | ||
| 1040 | {name = "Options", type = Option, elements = self._options}, | ||
| 1041 | {name = "Commands", type = Command, elements = self._commands} | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | local added_elements = {} | ||
| 1045 | |||
| 1046 | for _, default_group in ipairs(default_groups) do | ||
| 1047 | local type_groups = groups_by_type[default_group.type] | ||
| 1048 | |||
| 1049 | for _, group in ipairs(type_groups) do | ||
| 1050 | self:_add_group_help(blocks, added_elements, group.name .. ":", group) | ||
| 1051 | end | ||
| 1052 | |||
| 1053 | local default_label = default_group.name .. ":" | ||
| 1054 | |||
| 1055 | if #type_groups > 0 then | ||
| 1056 | default_label = "Other " .. default_label:gsub("^.", string.lower) | ||
| 1057 | end | ||
| 1058 | |||
| 1059 | self:_add_group_help(blocks, added_elements, default_label, default_group.elements) | ||
| 1060 | end | ||
| 1061 | |||
| 1062 | if self._epilog then | ||
| 1063 | local epilog = self._epilog | ||
| 1064 | |||
| 1065 | if help_max_width then | ||
| 1066 | epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") | ||
| 1067 | end | ||
| 1068 | |||
| 1069 | table.insert(blocks, epilog) | ||
| 1070 | end | ||
| 1071 | |||
| 1072 | return table.concat(blocks, "\n\n") | ||
| 1073 | end | ||
| 1074 | |||
| 1075 | function Parser:add_help_command(value) | ||
| 1076 | if value then | ||
| 1077 | assert(type(value) == "string" or type(value) == "table", | ||
| 1078 | ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value))) | ||
| 1079 | end | ||
| 1080 | |||
| 1081 | local help = self:command() | ||
| 1082 | :description "Show help for commands." | ||
| 1083 | help:argument "command" | ||
| 1084 | :description "The command to show help for." | ||
| 1085 | :args "?" | ||
| 1086 | :action(function(_, _, cmd) | ||
| 1087 | if not cmd then | ||
| 1088 | print(self:get_help()) | ||
| 1089 | os.exit(0) | ||
| 1090 | else | ||
| 1091 | for _, command in ipairs(self._commands) do | ||
| 1092 | for _, alias in ipairs(command._aliases) do | ||
| 1093 | if alias == cmd then | ||
| 1094 | print(command:get_help()) | ||
| 1095 | os.exit(0) | ||
| 1096 | end | ||
| 1097 | end | ||
| 1098 | end | ||
| 1099 | end | ||
| 1100 | help:error(("unknown command '%s'"):format(cmd)) | ||
| 1101 | end) | ||
| 1102 | |||
| 1103 | if value then | ||
| 1104 | help = help(value) | ||
| 1105 | end | ||
| 1106 | |||
| 1107 | if not help._name then | ||
| 1108 | help "help" | ||
| 1109 | end | ||
| 1110 | |||
| 1111 | help._is_help_command = true | ||
| 1112 | return self | ||
| 1113 | end | ||
| 1114 | |||
| 1115 | function Parser:_is_shell_safe() | ||
| 1116 | if self._basename then | ||
| 1117 | if self._basename:find("[^%w_%-%+%.]") then | ||
| 1118 | return false | ||
| 1119 | end | ||
| 1120 | else | ||
| 1121 | for _, alias in ipairs(self._aliases) do | ||
| 1122 | if alias:find("[^%w_%-%+%.]") then | ||
| 1123 | return false | ||
| 1124 | end | ||
| 1125 | end | ||
| 1126 | end | ||
| 1127 | for _, option in ipairs(self._options) do | ||
| 1128 | for _, alias in ipairs(option._aliases) do | ||
| 1129 | if alias:find("[^%w_%-%+%.]") then | ||
| 1130 | return false | ||
| 1131 | end | ||
| 1132 | end | ||
| 1133 | if option._choices then | ||
| 1134 | for _, choice in ipairs(option._choices) do | ||
| 1135 | if choice:find("[%s'\"]") then | ||
| 1136 | return false | ||
| 1137 | end | ||
| 1138 | end | ||
| 1139 | end | ||
| 1140 | end | ||
| 1141 | for _, argument in ipairs(self._arguments) do | ||
| 1142 | if argument._choices then | ||
| 1143 | for _, choice in ipairs(argument._choices) do | ||
| 1144 | if choice:find("[%s'\"]") then | ||
| 1145 | return false | ||
| 1146 | end | ||
| 1147 | end | ||
| 1148 | end | ||
| 1149 | end | ||
| 1150 | for _, command in ipairs(self._commands) do | ||
| 1151 | if not command:_is_shell_safe() then | ||
| 1152 | return false | ||
| 1153 | end | ||
| 1154 | end | ||
| 1155 | return true | ||
| 1156 | end | ||
| 1157 | |||
| 1158 | function Parser:add_complete(value) | ||
| 1159 | if value then | ||
| 1160 | assert(type(value) == "string" or type(value) == "table", | ||
| 1161 | ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value))) | ||
| 1162 | end | ||
| 1163 | |||
| 1164 | local complete = self:option() | ||
| 1165 | :description "Output a shell completion script for the specified shell." | ||
| 1166 | :args(1) | ||
| 1167 | :choices {"bash", "zsh", "fish"} | ||
| 1168 | :action(function(_, _, shell) | ||
| 1169 | io.write(self["get_" .. shell .. "_complete"](self)) | ||
| 1170 | os.exit(0) | ||
| 1171 | end) | ||
| 1172 | |||
| 1173 | if value then | ||
| 1174 | complete = complete(value) | ||
| 1175 | end | ||
| 1176 | |||
| 1177 | if not complete._name then | ||
| 1178 | complete "--completion" | ||
| 1179 | end | ||
| 1180 | |||
| 1181 | return self | ||
| 1182 | end | ||
| 1183 | |||
| 1184 | function Parser:add_complete_command(value) | ||
| 1185 | if value then | ||
| 1186 | assert(type(value) == "string" or type(value) == "table", | ||
| 1187 | ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value))) | ||
| 1188 | end | ||
| 1189 | |||
| 1190 | local complete = self:command() | ||
| 1191 | :description "Output a shell completion script." | ||
| 1192 | complete:argument "shell" | ||
| 1193 | :description "The shell to output a completion script for." | ||
| 1194 | :choices {"bash", "zsh", "fish"} | ||
| 1195 | :action(function(_, _, shell) | ||
| 1196 | io.write(self["get_" .. shell .. "_complete"](self)) | ||
| 1197 | os.exit(0) | ||
| 1198 | end) | ||
| 1199 | |||
| 1200 | if value then | ||
| 1201 | complete = complete(value) | ||
| 1202 | end | ||
| 1203 | |||
| 1204 | if not complete._name then | ||
| 1205 | complete "completion" | ||
| 1206 | end | ||
| 1207 | |||
| 1208 | return self | ||
| 1209 | end | ||
| 1210 | |||
| 1211 | local function base_name(pathname) | ||
| 1212 | return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname | ||
| 1213 | end | ||
| 1214 | |||
| 1215 | local function get_short_description(element) | ||
| 1216 | local short = element:_get_description():match("^(.-)%.%s") | ||
| 1217 | return short or element:_get_description():match("^(.-)%.?$") | ||
| 1218 | end | ||
| 1219 | |||
| 1220 | function Parser:_get_options() | ||
| 1221 | local options = {} | ||
| 1222 | for _, option in ipairs(self._options) do | ||
| 1223 | for _, alias in ipairs(option._aliases) do | ||
| 1224 | table.insert(options, alias) | ||
| 1225 | end | ||
| 1226 | end | ||
| 1227 | return table.concat(options, " ") | ||
| 1228 | end | ||
| 1229 | |||
| 1230 | function Parser:_get_commands() | ||
| 1231 | local commands = {} | ||
| 1232 | for _, command in ipairs(self._commands) do | ||
| 1233 | for _, alias in ipairs(command._aliases) do | ||
| 1234 | table.insert(commands, alias) | ||
| 1235 | end | ||
| 1236 | end | ||
| 1237 | return table.concat(commands, " ") | ||
| 1238 | end | ||
| 1239 | |||
| 1240 | function Parser:_bash_option_args(buf, indent) | ||
| 1241 | local opts = {} | ||
| 1242 | for _, option in ipairs(self._options) do | ||
| 1243 | if option._choices or option._minargs > 0 then | ||
| 1244 | local compreply | ||
| 1245 | if option._choices then | ||
| 1246 | compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))' | ||
| 1247 | else | ||
| 1248 | compreply = 'COMPREPLY=($(compgen -f -- "$cur"))' | ||
| 1249 | end | ||
| 1250 | table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")") | ||
| 1251 | table.insert(opts, (" "):rep(indent + 8) .. compreply) | ||
| 1252 | table.insert(opts, (" "):rep(indent + 8) .. "return 0") | ||
| 1253 | table.insert(opts, (" "):rep(indent + 8) .. ";;") | ||
| 1254 | end | ||
| 1255 | end | ||
| 1256 | |||
| 1257 | if #opts > 0 then | ||
| 1258 | table.insert(buf, (" "):rep(indent) .. 'case "$prev" in') | ||
| 1259 | table.insert(buf, table.concat(opts, "\n")) | ||
| 1260 | table.insert(buf, (" "):rep(indent) .. "esac\n") | ||
| 1261 | end | ||
| 1262 | end | ||
| 1263 | |||
| 1264 | function Parser:_bash_get_cmd(buf, indent) | ||
| 1265 | if #self._commands == 0 then | ||
| 1266 | return | ||
| 1267 | end | ||
| 1268 | |||
| 1269 | table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")') | ||
| 1270 | table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do') | ||
| 1271 | table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in') | ||
| 1272 | |||
| 1273 | for _, command in ipairs(self._commands) do | ||
| 1274 | table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")") | ||
| 1275 | if self._parent then | ||
| 1276 | table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"') | ||
| 1277 | else | ||
| 1278 | table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"') | ||
| 1279 | end | ||
| 1280 | table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"') | ||
| 1281 | command:_bash_get_cmd(buf, indent + 12) | ||
| 1282 | table.insert(buf, (" "):rep(indent + 12) .. "break") | ||
| 1283 | table.insert(buf, (" "):rep(indent + 12) .. ";;") | ||
| 1284 | end | ||
| 1285 | |||
| 1286 | table.insert(buf, (" "):rep(indent + 4) .. "esac") | ||
| 1287 | table.insert(buf, (" "):rep(indent) .. "done") | ||
| 1288 | end | ||
| 1289 | |||
| 1290 | function Parser:_bash_cmd_completions(buf) | ||
| 1291 | local cmd_buf = {} | ||
| 1292 | if self._parent then | ||
| 1293 | self:_bash_option_args(cmd_buf, 12) | ||
| 1294 | end | ||
| 1295 | if #self._commands > 0 then | ||
| 1296 | table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') | ||
| 1297 | elseif self._is_help_command then | ||
| 1298 | table.insert(cmd_buf, (" "):rep(12) | ||
| 1299 | .. 'COMPREPLY=($(compgen -W "' | ||
| 1300 | .. self._parent:_get_commands() | ||
| 1301 | .. '" -- "$cur"))') | ||
| 1302 | end | ||
| 1303 | if #cmd_buf > 0 then | ||
| 1304 | table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')") | ||
| 1305 | table.insert(buf, table.concat(cmd_buf, "\n")) | ||
| 1306 | table.insert(buf, (" "):rep(12) .. ";;") | ||
| 1307 | end | ||
| 1308 | |||
| 1309 | for _, command in ipairs(self._commands) do | ||
| 1310 | command:_bash_cmd_completions(buf) | ||
| 1311 | end | ||
| 1312 | end | ||
| 1313 | |||
| 1314 | function Parser:get_bash_complete() | ||
| 1315 | self._basename = base_name(self._name) | ||
| 1316 | assert(self:_is_shell_safe()) | ||
| 1317 | local buf = {([[ | ||
| 1318 | _%s() { | ||
| 1319 | local IFS=$' \t\n' | ||
| 1320 | local args cur prev cmd opts arg | ||
| 1321 | args=("${COMP_WORDS[@]}") | ||
| 1322 | cur="${COMP_WORDS[COMP_CWORD]}" | ||
| 1323 | prev="${COMP_WORDS[COMP_CWORD-1]}" | ||
| 1324 | opts="%s" | ||
| 1325 | ]]):format(self._basename, self:_get_options())} | ||
| 1326 | |||
| 1327 | self:_bash_option_args(buf, 4) | ||
| 1328 | self:_bash_get_cmd(buf, 4) | ||
| 1329 | if #self._commands > 0 then | ||
| 1330 | table.insert(buf, "") | ||
| 1331 | table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') | ||
| 1332 | self:_bash_cmd_completions(buf) | ||
| 1333 | table.insert(buf, (" "):rep(4) .. "esac\n") | ||
| 1334 | end | ||
| 1335 | |||
| 1336 | table.insert(buf, ([=[ | ||
| 1337 | if [[ "$cur" = -* ]]; then | ||
| 1338 | COMPREPLY=($(compgen -W "$opts" -- "$cur")) | ||
| 1339 | fi | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | complete -F _%s -o bashdefault -o default %s | ||
| 1343 | ]=]):format(self._basename, self._basename)) | ||
| 1344 | |||
| 1345 | return table.concat(buf, "\n") | ||
| 1346 | end | ||
| 1347 | |||
| 1348 | function Parser:_zsh_arguments(buf, cmd_name, indent) | ||
| 1349 | if self._parent then | ||
| 1350 | table.insert(buf, (" "):rep(indent) .. "options=(") | ||
| 1351 | table.insert(buf, (" "):rep(indent + 2) .. "$options") | ||
| 1352 | else | ||
| 1353 | table.insert(buf, (" "):rep(indent) .. "local -a options=(") | ||
| 1354 | end | ||
| 1355 | |||
| 1356 | for _, option in ipairs(self._options) do | ||
| 1357 | local line = {} | ||
| 1358 | if #option._aliases > 1 then | ||
| 1359 | if option._maxcount > 1 then | ||
| 1360 | table.insert(line, '"*"') | ||
| 1361 | end | ||
| 1362 | table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"') | ||
| 1363 | else | ||
| 1364 | table.insert(line, '"') | ||
| 1365 | if option._maxcount > 1 then | ||
| 1366 | table.insert(line, "*") | ||
| 1367 | end | ||
| 1368 | table.insert(line, option._name) | ||
| 1369 | end | ||
| 1370 | if option._description then | ||
| 1371 | local description = get_short_description(option):gsub('["%]:`$]', "\\%0") | ||
| 1372 | table.insert(line, "[" .. description .. "]") | ||
| 1373 | end | ||
| 1374 | if option._maxargs == math.huge then | ||
| 1375 | table.insert(line, ":*") | ||
| 1376 | end | ||
| 1377 | if option._choices then | ||
| 1378 | table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")") | ||
| 1379 | elseif option._maxargs > 0 then | ||
| 1380 | table.insert(line, ": :_files") | ||
| 1381 | end | ||
| 1382 | table.insert(line, '"') | ||
| 1383 | table.insert(buf, (" "):rep(indent + 2) .. table.concat(line)) | ||
| 1384 | end | ||
| 1385 | |||
| 1386 | table.insert(buf, (" "):rep(indent) .. ")") | ||
| 1387 | table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\") | ||
| 1388 | table.insert(buf, (" "):rep(indent + 2) .. "$options \\") | ||
| 1389 | |||
| 1390 | if self._is_help_command then | ||
| 1391 | table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\') | ||
| 1392 | else | ||
| 1393 | for _, argument in ipairs(self._arguments) do | ||
| 1394 | local spec | ||
| 1395 | if argument._choices then | ||
| 1396 | spec = ": :(" .. table.concat(argument._choices, " ") .. ")" | ||
| 1397 | else | ||
| 1398 | spec = ": :_files" | ||
| 1399 | end | ||
| 1400 | if argument._maxargs == math.huge then | ||
| 1401 | table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\') | ||
| 1402 | break | ||
| 1403 | end | ||
| 1404 | for _ = 1, argument._maxargs do | ||
| 1405 | table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\') | ||
| 1406 | end | ||
| 1407 | end | ||
| 1408 | |||
| 1409 | if #self._commands > 0 then | ||
| 1410 | table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\') | ||
| 1411 | table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\') | ||
| 1412 | end | ||
| 1413 | end | ||
| 1414 | |||
| 1415 | table.insert(buf, (" "):rep(indent + 2) .. "&& return 0") | ||
| 1416 | end | ||
| 1417 | |||
| 1418 | function Parser:_zsh_cmds(buf, cmd_name) | ||
| 1419 | table.insert(buf, "\n_" .. cmd_name .. "_cmds() {") | ||
| 1420 | table.insert(buf, " local -a commands=(") | ||
| 1421 | |||
| 1422 | for _, command in ipairs(self._commands) do | ||
| 1423 | local line = {} | ||
| 1424 | if #command._aliases > 1 then | ||
| 1425 | table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"') | ||
| 1426 | else | ||
| 1427 | table.insert(line, '"' .. command._name) | ||
| 1428 | end | ||
| 1429 | if command._description then | ||
| 1430 | table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0")) | ||
| 1431 | end | ||
| 1432 | table.insert(buf, " " .. table.concat(line) .. '"') | ||
| 1433 | end | ||
| 1434 | |||
| 1435 | table.insert(buf, ' )\n _describe "command" commands\n}') | ||
| 1436 | end | ||
| 1437 | |||
| 1438 | function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent) | ||
| 1439 | if #self._commands == 0 then | ||
| 1440 | return | ||
| 1441 | end | ||
| 1442 | |||
| 1443 | self:_zsh_cmds(cmds_buf, cmd_name) | ||
| 1444 | table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in") | ||
| 1445 | |||
| 1446 | for _, command in ipairs(self._commands) do | ||
| 1447 | local name = cmd_name .. "_" .. command._name | ||
| 1448 | table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")") | ||
| 1449 | command:_zsh_arguments(buf, name, indent + 4) | ||
| 1450 | command:_zsh_complete_help(buf, cmds_buf, name, indent + 4) | ||
| 1451 | table.insert(buf, (" "):rep(indent + 4) .. ";;\n") | ||
| 1452 | end | ||
| 1453 | |||
| 1454 | table.insert(buf, (" "):rep(indent) .. "esac") | ||
| 1455 | end | ||
| 1456 | |||
| 1457 | function Parser:get_zsh_complete() | ||
| 1458 | self._basename = base_name(self._name) | ||
| 1459 | assert(self:_is_shell_safe()) | ||
| 1460 | local buf = {("#compdef %s\n"):format(self._basename)} | ||
| 1461 | local cmds_buf = {} | ||
| 1462 | table.insert(buf, "_" .. self._basename .. "() {") | ||
| 1463 | if #self._commands > 0 then | ||
| 1464 | table.insert(buf, " local context state state_descr line") | ||
| 1465 | table.insert(buf, " typeset -A opt_args\n") | ||
| 1466 | end | ||
| 1467 | self:_zsh_arguments(buf, self._basename, 2) | ||
| 1468 | self:_zsh_complete_help(buf, cmds_buf, self._basename, 2) | ||
| 1469 | table.insert(buf, "\n return 1") | ||
| 1470 | table.insert(buf, "}") | ||
| 1471 | |||
| 1472 | local result = table.concat(buf, "\n") | ||
| 1473 | if #cmds_buf > 0 then | ||
| 1474 | result = result .. "\n" .. table.concat(cmds_buf, "\n") | ||
| 1475 | end | ||
| 1476 | return result .. "\n\n_" .. self._basename .. "\n" | ||
| 1477 | end | ||
| 1478 | |||
| 1479 | local function fish_escape(string) | ||
| 1480 | return string:gsub("[\\']", "\\%0") | ||
| 1481 | end | ||
| 1482 | |||
| 1483 | function Parser:_fish_get_cmd(buf, indent) | ||
| 1484 | if #self._commands == 0 then | ||
| 1485 | return | ||
| 1486 | end | ||
| 1487 | |||
| 1488 | table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]") | ||
| 1489 | table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline") | ||
| 1490 | table.insert(buf, (" "):rep(indent + 4) .. "switch $arg") | ||
| 1491 | |||
| 1492 | for _, command in ipairs(self._commands) do | ||
| 1493 | table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " ")) | ||
| 1494 | table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name) | ||
| 1495 | command:_fish_get_cmd(buf, indent + 12) | ||
| 1496 | table.insert(buf, (" "):rep(indent + 12) .. "break") | ||
| 1497 | end | ||
| 1498 | |||
| 1499 | table.insert(buf, (" "):rep(indent + 4) .. "end") | ||
| 1500 | table.insert(buf, (" "):rep(indent) .. "end") | ||
| 1501 | end | ||
| 1502 | |||
| 1503 | function Parser:_fish_complete_help(buf, basename) | ||
| 1504 | local prefix = "complete -c " .. basename | ||
| 1505 | table.insert(buf, "") | ||
| 1506 | |||
| 1507 | for _, command in ipairs(self._commands) do | ||
| 1508 | local aliases = table.concat(command._aliases, " ") | ||
| 1509 | local line | ||
| 1510 | if self._parent then | ||
| 1511 | line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") | ||
| 1512 | :format(prefix, basename, self:_get_fullname(true), aliases) | ||
| 1513 | else | ||
| 1514 | line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases) | ||
| 1515 | end | ||
| 1516 | if command._description then | ||
| 1517 | line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) | ||
| 1518 | end | ||
| 1519 | table.insert(buf, line) | ||
| 1520 | end | ||
| 1521 | |||
| 1522 | if self._is_help_command then | ||
| 1523 | local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") | ||
| 1524 | :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands()) | ||
| 1525 | table.insert(buf, line) | ||
| 1526 | end | ||
| 1527 | |||
| 1528 | for _, option in ipairs(self._options) do | ||
| 1529 | local parts = {prefix} | ||
| 1530 | |||
| 1531 | if self._parent then | ||
| 1532 | table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'") | ||
| 1533 | end | ||
| 1534 | |||
| 1535 | for _, alias in ipairs(option._aliases) do | ||
| 1536 | if alias:match("^%-.$") then | ||
| 1537 | table.insert(parts, "-s " .. alias:sub(2)) | ||
| 1538 | elseif alias:match("^%-%-.+") then | ||
| 1539 | table.insert(parts, "-l " .. alias:sub(3)) | ||
| 1540 | end | ||
| 1541 | end | ||
| 1542 | |||
| 1543 | if option._choices then | ||
| 1544 | table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'") | ||
| 1545 | elseif option._minargs > 0 then | ||
| 1546 | table.insert(parts, "-r") | ||
| 1547 | end | ||
| 1548 | |||
| 1549 | if option._description then | ||
| 1550 | table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'") | ||
| 1551 | end | ||
| 1552 | |||
| 1553 | table.insert(buf, table.concat(parts, " ")) | ||
| 1554 | end | ||
| 1555 | |||
| 1556 | for _, command in ipairs(self._commands) do | ||
| 1557 | command:_fish_complete_help(buf, basename) | ||
| 1558 | end | ||
| 1559 | end | ||
| 1560 | |||
| 1561 | function Parser:get_fish_complete() | ||
| 1562 | self._basename = base_name(self._name) | ||
| 1563 | assert(self:_is_shell_safe()) | ||
| 1564 | local buf = {} | ||
| 1565 | |||
| 1566 | if #self._commands > 0 then | ||
| 1567 | table.insert(buf, ([[ | ||
| 1568 | function __fish_%s_print_command | ||
| 1569 | set -l cmdline (commandline -poc) | ||
| 1570 | set -l cmd]]):format(self._basename)) | ||
| 1571 | self:_fish_get_cmd(buf, 4) | ||
| 1572 | table.insert(buf, ([[ | ||
| 1573 | echo "$cmd" | ||
| 1574 | end | ||
| 1575 | |||
| 1576 | function __fish_%s_using_command | ||
| 1577 | test (__fish_%s_print_command) = "$argv" | ||
| 1578 | and return 0 | ||
| 1579 | or return 1 | ||
| 1580 | end | ||
| 1581 | |||
| 1582 | function __fish_%s_seen_command | ||
| 1583 | string match -q "$argv*" (__fish_%s_print_command) | ||
| 1584 | and return 0 | ||
| 1585 | or return 1 | ||
| 1586 | end]]):format(self._basename, self._basename, self._basename, self._basename)) | ||
| 1587 | end | ||
| 1588 | |||
| 1589 | self:_fish_complete_help(buf, self._basename) | ||
| 1590 | return table.concat(buf, "\n") .. "\n" | ||
| 1591 | end | ||
| 1592 | |||
| 1593 | local function get_tip(context, wrong_name) | ||
| 1594 | local context_pool = {} | ||
| 1595 | local possible_name | ||
| 1596 | local possible_names = {} | ||
| 1597 | |||
| 1598 | for name in pairs(context) do | ||
| 1599 | if type(name) == "string" then | ||
| 1600 | for i = 1, #name do | ||
| 1601 | possible_name = name:sub(1, i - 1) .. name:sub(i + 1) | ||
| 1602 | |||
| 1603 | if not context_pool[possible_name] then | ||
| 1604 | context_pool[possible_name] = {} | ||
| 1605 | end | ||
| 1606 | |||
| 1607 | table.insert(context_pool[possible_name], name) | ||
| 1608 | end | ||
| 1609 | end | ||
| 1610 | end | ||
| 1611 | |||
| 1612 | for i = 1, #wrong_name + 1 do | ||
| 1613 | possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) | ||
| 1614 | |||
| 1615 | if context[possible_name] then | ||
| 1616 | possible_names[possible_name] = true | ||
| 1617 | elseif context_pool[possible_name] then | ||
| 1618 | for _, name in ipairs(context_pool[possible_name]) do | ||
| 1619 | possible_names[name] = true | ||
| 1620 | end | ||
| 1621 | end | ||
| 1622 | end | ||
| 1623 | |||
| 1624 | local first = next(possible_names) | ||
| 1625 | |||
| 1626 | if first then | ||
| 1627 | if next(possible_names, first) then | ||
| 1628 | local possible_names_arr = {} | ||
| 1629 | |||
| 1630 | for name in pairs(possible_names) do | ||
| 1631 | table.insert(possible_names_arr, "'" .. name .. "'") | ||
| 1632 | end | ||
| 1633 | |||
| 1634 | table.sort(possible_names_arr) | ||
| 1635 | return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" | ||
| 1636 | else | ||
| 1637 | return "\nDid you mean '" .. first .. "'?" | ||
| 1638 | end | ||
| 1639 | else | ||
| 1640 | return "" | ||
| 1641 | end | ||
| 1642 | end | ||
| 1643 | |||
| 1644 | local ElementState = class({ | ||
| 1645 | invocations = 0 | ||
| 1646 | }) | ||
| 1647 | |||
| 1648 | function ElementState:__call(state, element) | ||
| 1649 | self.state = state | ||
| 1650 | self.result = state.result | ||
| 1651 | self.element = element | ||
| 1652 | self.target = element._target or element:_get_default_target() | ||
| 1653 | self.action, self.result[self.target] = element:_get_action() | ||
| 1654 | return self | ||
| 1655 | end | ||
| 1656 | |||
| 1657 | function ElementState:error(fmt, ...) | ||
| 1658 | self.state:error(fmt, ...) | ||
| 1659 | end | ||
| 1660 | |||
| 1661 | function ElementState:convert(argument, index) | ||
| 1662 | local converter = self.element._convert | ||
| 1663 | |||
| 1664 | if converter then | ||
| 1665 | local ok, err | ||
| 1666 | |||
| 1667 | if type(converter) == "function" then | ||
| 1668 | ok, err = converter(argument) | ||
| 1669 | elseif type(converter[index]) == "function" then | ||
| 1670 | ok, err = converter[index](argument) | ||
| 1671 | else | ||
| 1672 | ok = converter[argument] | ||
| 1673 | end | ||
| 1674 | |||
| 1675 | if ok == nil then | ||
| 1676 | self:error(err and "%s" or "malformed argument '%s'", err or argument) | ||
| 1677 | end | ||
| 1678 | |||
| 1679 | argument = ok | ||
| 1680 | end | ||
| 1681 | |||
| 1682 | return argument | ||
| 1683 | end | ||
| 1684 | |||
| 1685 | function ElementState:default(mode) | ||
| 1686 | return self.element._defmode:find(mode) and self.element._default | ||
| 1687 | end | ||
| 1688 | |||
| 1689 | local function bound(noun, min, max, is_max) | ||
| 1690 | local res = "" | ||
| 1691 | |||
| 1692 | if min ~= max then | ||
| 1693 | res = "at " .. (is_max and "most" or "least") .. " " | ||
| 1694 | end | ||
| 1695 | |||
| 1696 | local number = is_max and max or min | ||
| 1697 | return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") | ||
| 1698 | end | ||
| 1699 | |||
| 1700 | function ElementState:set_name(alias) | ||
| 1701 | self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) | ||
| 1702 | end | ||
| 1703 | |||
| 1704 | function ElementState:invoke() | ||
| 1705 | self.open = true | ||
| 1706 | self.overwrite = false | ||
| 1707 | |||
| 1708 | if self.invocations >= self.element._maxcount then | ||
| 1709 | if self.element._overwrite then | ||
| 1710 | self.overwrite = true | ||
| 1711 | else | ||
| 1712 | local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) | ||
| 1713 | self:error("%s must be used %s", self.name, num_times_repr) | ||
| 1714 | end | ||
| 1715 | else | ||
| 1716 | self.invocations = self.invocations + 1 | ||
| 1717 | end | ||
| 1718 | |||
| 1719 | self.args = {} | ||
| 1720 | |||
| 1721 | if self.element._maxargs <= 0 then | ||
| 1722 | self:close() | ||
| 1723 | end | ||
| 1724 | |||
| 1725 | return self.open | ||
| 1726 | end | ||
| 1727 | |||
| 1728 | function ElementState:check_choices(argument) | ||
| 1729 | if self.element._choices then | ||
| 1730 | for _, choice in ipairs(self.element._choices) do | ||
| 1731 | if argument == choice then | ||
| 1732 | return | ||
| 1733 | end | ||
| 1734 | end | ||
| 1735 | local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'" | ||
| 1736 | local is_option = getmetatable(self.element) == Option | ||
| 1737 | self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list) | ||
| 1738 | end | ||
| 1739 | end | ||
| 1740 | |||
| 1741 | function ElementState:pass(argument) | ||
| 1742 | self:check_choices(argument) | ||
| 1743 | argument = self:convert(argument, #self.args + 1) | ||
| 1744 | table.insert(self.args, argument) | ||
| 1745 | |||
| 1746 | if #self.args >= self.element._maxargs then | ||
| 1747 | self:close() | ||
| 1748 | end | ||
| 1749 | |||
| 1750 | return self.open | ||
| 1751 | end | ||
| 1752 | |||
| 1753 | function ElementState:complete_invocation() | ||
| 1754 | while #self.args < self.element._minargs do | ||
| 1755 | self:pass(self.element._default) | ||
| 1756 | end | ||
| 1757 | end | ||
| 1758 | |||
| 1759 | function ElementState:close() | ||
| 1760 | if self.open then | ||
| 1761 | self.open = false | ||
| 1762 | |||
| 1763 | if #self.args < self.element._minargs then | ||
| 1764 | if self:default("a") then | ||
| 1765 | self:complete_invocation() | ||
| 1766 | else | ||
| 1767 | if #self.args == 0 then | ||
| 1768 | if getmetatable(self.element) == Argument then | ||
| 1769 | self:error("missing %s", self.name) | ||
| 1770 | elseif self.element._maxargs == 1 then | ||
| 1771 | self:error("%s requires an argument", self.name) | ||
| 1772 | end | ||
| 1773 | end | ||
| 1774 | |||
| 1775 | self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) | ||
| 1776 | end | ||
| 1777 | end | ||
| 1778 | |||
| 1779 | local args | ||
| 1780 | |||
| 1781 | if self.element._maxargs == 0 then | ||
| 1782 | args = self.args[1] | ||
| 1783 | elseif self.element._maxargs == 1 then | ||
| 1784 | if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then | ||
| 1785 | args = self.args | ||
| 1786 | else | ||
| 1787 | args = self.args[1] | ||
| 1788 | end | ||
| 1789 | else | ||
| 1790 | args = self.args | ||
| 1791 | end | ||
| 1792 | |||
| 1793 | self.action(self.result, self.target, args, self.overwrite) | ||
| 1794 | end | ||
| 1795 | end | ||
| 1796 | |||
| 1797 | local ParseState = class({ | ||
| 1798 | result = {}, | ||
| 1799 | options = {}, | ||
| 1800 | arguments = {}, | ||
| 1801 | argument_i = 1, | ||
| 1802 | element_to_mutexes = {}, | ||
| 1803 | mutex_to_element_state = {}, | ||
| 1804 | command_actions = {} | ||
| 1805 | }) | ||
| 1806 | |||
| 1807 | function ParseState:__call(parser, error_handler) | ||
| 1808 | self.parser = parser | ||
| 1809 | self.error_handler = error_handler | ||
| 1810 | self.charset = parser:_update_charset() | ||
| 1811 | self:switch(parser) | ||
| 1812 | return self | ||
| 1813 | end | ||
| 1814 | |||
| 1815 | function ParseState:error(fmt, ...) | ||
| 1816 | self.error_handler(self.parser, fmt:format(...)) | ||
| 1817 | end | ||
| 1818 | |||
| 1819 | function ParseState:switch(parser) | ||
| 1820 | self.parser = parser | ||
| 1821 | |||
| 1822 | if parser._action then | ||
| 1823 | table.insert(self.command_actions, {action = parser._action, name = parser._name}) | ||
| 1824 | end | ||
| 1825 | |||
| 1826 | for _, option in ipairs(parser._options) do | ||
| 1827 | option = ElementState(self, option) | ||
| 1828 | table.insert(self.options, option) | ||
| 1829 | |||
| 1830 | for _, alias in ipairs(option.element._aliases) do | ||
| 1831 | self.options[alias] = option | ||
| 1832 | end | ||
| 1833 | end | ||
| 1834 | |||
| 1835 | for _, mutex in ipairs(parser._mutexes) do | ||
| 1836 | for _, element in ipairs(mutex) do | ||
| 1837 | if not self.element_to_mutexes[element] then | ||
| 1838 | self.element_to_mutexes[element] = {} | ||
| 1839 | end | ||
| 1840 | |||
| 1841 | table.insert(self.element_to_mutexes[element], mutex) | ||
| 1842 | end | ||
| 1843 | end | ||
| 1844 | |||
| 1845 | for _, argument in ipairs(parser._arguments) do | ||
| 1846 | argument = ElementState(self, argument) | ||
| 1847 | table.insert(self.arguments, argument) | ||
| 1848 | argument:set_name() | ||
| 1849 | argument:invoke() | ||
| 1850 | end | ||
| 1851 | |||
| 1852 | self.handle_options = parser._handle_options | ||
| 1853 | self.argument = self.arguments[self.argument_i] | ||
| 1854 | self.commands = parser._commands | ||
| 1855 | |||
| 1856 | for _, command in ipairs(self.commands) do | ||
| 1857 | for _, alias in ipairs(command._aliases) do | ||
| 1858 | self.commands[alias] = command | ||
| 1859 | end | ||
| 1860 | end | ||
| 1861 | end | ||
| 1862 | |||
| 1863 | function ParseState:get_option(name) | ||
| 1864 | local option = self.options[name] | ||
| 1865 | |||
| 1866 | if not option then | ||
| 1867 | self:error("unknown option '%s'%s", name, get_tip(self.options, name)) | ||
| 1868 | else | ||
| 1869 | return option | ||
| 1870 | end | ||
| 1871 | end | ||
| 1872 | |||
| 1873 | function ParseState:get_command(name) | ||
| 1874 | local command = self.commands[name] | ||
| 1875 | |||
| 1876 | if not command then | ||
| 1877 | if #self.commands > 0 then | ||
| 1878 | self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) | ||
| 1879 | else | ||
| 1880 | self:error("too many arguments") | ||
| 1881 | end | ||
| 1882 | else | ||
| 1883 | return command | ||
| 1884 | end | ||
| 1885 | end | ||
| 1886 | |||
| 1887 | function ParseState:check_mutexes(element_state) | ||
| 1888 | if self.element_to_mutexes[element_state.element] then | ||
| 1889 | for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do | ||
| 1890 | local used_element_state = self.mutex_to_element_state[mutex] | ||
| 1891 | |||
| 1892 | if used_element_state and used_element_state ~= element_state then | ||
| 1893 | self:error("%s can not be used together with %s", element_state.name, used_element_state.name) | ||
| 1894 | else | ||
| 1895 | self.mutex_to_element_state[mutex] = element_state | ||
| 1896 | end | ||
| 1897 | end | ||
| 1898 | end | ||
| 1899 | end | ||
| 1900 | |||
| 1901 | function ParseState:invoke(option, name) | ||
| 1902 | self:close() | ||
| 1903 | option:set_name(name) | ||
| 1904 | self:check_mutexes(option, name) | ||
| 1905 | |||
| 1906 | if option:invoke() then | ||
| 1907 | self.option = option | ||
| 1908 | end | ||
| 1909 | end | ||
| 1910 | |||
| 1911 | function ParseState:pass(arg) | ||
| 1912 | if self.option then | ||
| 1913 | if not self.option:pass(arg) then | ||
| 1914 | self.option = nil | ||
| 1915 | end | ||
| 1916 | elseif self.argument then | ||
| 1917 | self:check_mutexes(self.argument) | ||
| 1918 | |||
| 1919 | if not self.argument:pass(arg) then | ||
| 1920 | self.argument_i = self.argument_i + 1 | ||
| 1921 | self.argument = self.arguments[self.argument_i] | ||
| 1922 | end | ||
| 1923 | else | ||
| 1924 | local command = self:get_command(arg) | ||
| 1925 | self.result[command._target or command._name] = true | ||
| 1926 | |||
| 1927 | if self.parser._command_target then | ||
| 1928 | self.result[self.parser._command_target] = command._name | ||
| 1929 | end | ||
| 1930 | |||
| 1931 | self:switch(command) | ||
| 1932 | end | ||
| 1933 | end | ||
| 1934 | |||
| 1935 | function ParseState:close() | ||
| 1936 | if self.option then | ||
| 1937 | self.option:close() | ||
| 1938 | self.option = nil | ||
| 1939 | end | ||
| 1940 | end | ||
| 1941 | |||
| 1942 | function ParseState:finalize() | ||
| 1943 | self:close() | ||
| 1944 | |||
| 1945 | for i = self.argument_i, #self.arguments do | ||
| 1946 | local argument = self.arguments[i] | ||
| 1947 | if #argument.args == 0 and argument:default("u") then | ||
| 1948 | argument:complete_invocation() | ||
| 1949 | else | ||
| 1950 | argument:close() | ||
| 1951 | end | ||
| 1952 | end | ||
| 1953 | |||
| 1954 | if self.parser._require_command and #self.commands > 0 then | ||
| 1955 | self:error("a command is required") | ||
| 1956 | end | ||
| 1957 | |||
| 1958 | for _, option in ipairs(self.options) do | ||
| 1959 | option.name = option.name or ("option '%s'"):format(option.element._name) | ||
| 1960 | |||
| 1961 | if option.invocations == 0 then | ||
| 1962 | if option:default("u") then | ||
| 1963 | option:invoke() | ||
| 1964 | option:complete_invocation() | ||
| 1965 | option:close() | ||
| 1966 | end | ||
| 1967 | end | ||
| 1968 | |||
| 1969 | local mincount = option.element._mincount | ||
| 1970 | |||
| 1971 | if option.invocations < mincount then | ||
| 1972 | if option:default("a") then | ||
| 1973 | while option.invocations < mincount do | ||
| 1974 | option:invoke() | ||
| 1975 | option:close() | ||
| 1976 | end | ||
| 1977 | elseif option.invocations == 0 then | ||
| 1978 | self:error("missing %s", option.name) | ||
| 1979 | else | ||
| 1980 | self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) | ||
| 1981 | end | ||
| 1982 | end | ||
| 1983 | end | ||
| 1984 | |||
| 1985 | for i = #self.command_actions, 1, -1 do | ||
| 1986 | self.command_actions[i].action(self.result, self.command_actions[i].name) | ||
| 1987 | end | ||
| 1988 | end | ||
| 1989 | |||
| 1990 | function ParseState:parse(args) | ||
| 1991 | for _, arg in ipairs(args) do | ||
| 1992 | local plain = true | ||
| 1993 | |||
| 1994 | if self.handle_options then | ||
| 1995 | local first = arg:sub(1, 1) | ||
| 1996 | |||
| 1997 | if self.charset[first] then | ||
| 1998 | if #arg > 1 then | ||
| 1999 | plain = false | ||
| 2000 | |||
| 2001 | if arg:sub(2, 2) == first then | ||
| 2002 | if #arg == 2 then | ||
| 2003 | if self.options[arg] then | ||
| 2004 | local option = self:get_option(arg) | ||
| 2005 | self:invoke(option, arg) | ||
| 2006 | else | ||
| 2007 | self:close() | ||
| 2008 | end | ||
| 2009 | |||
| 2010 | self.handle_options = false | ||
| 2011 | else | ||
| 2012 | local equals = arg:find "=" | ||
| 2013 | if equals then | ||
| 2014 | local name = arg:sub(1, equals - 1) | ||
| 2015 | local option = self:get_option(name) | ||
| 2016 | |||
| 2017 | if option.element._maxargs <= 0 then | ||
| 2018 | self:error("option '%s' does not take arguments", name) | ||
| 2019 | end | ||
| 2020 | |||
| 2021 | self:invoke(option, name) | ||
| 2022 | self:pass(arg:sub(equals + 1)) | ||
| 2023 | else | ||
| 2024 | local option = self:get_option(arg) | ||
| 2025 | self:invoke(option, arg) | ||
| 2026 | end | ||
| 2027 | end | ||
| 2028 | else | ||
| 2029 | for i = 2, #arg do | ||
| 2030 | local name = first .. arg:sub(i, i) | ||
| 2031 | local option = self:get_option(name) | ||
| 2032 | self:invoke(option, name) | ||
| 2033 | |||
| 2034 | if i ~= #arg and option.element._maxargs > 0 then | ||
| 2035 | self:pass(arg:sub(i + 1)) | ||
| 2036 | break | ||
| 2037 | end | ||
| 2038 | end | ||
| 2039 | end | ||
| 2040 | end | ||
| 2041 | end | ||
| 2042 | end | ||
| 2043 | |||
| 2044 | if plain then | ||
| 2045 | self:pass(arg) | ||
| 2046 | end | ||
| 2047 | end | ||
| 2048 | |||
| 2049 | self:finalize() | ||
| 2050 | return self.result | ||
| 2051 | end | ||
| 2052 | |||
| 2053 | function Parser:error(msg) | ||
| 2054 | io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) | ||
| 2055 | os.exit(1) | ||
| 2056 | end | ||
| 2057 | |||
| 2058 | -- Compatibility with strict.lua and other checkers: | ||
| 2059 | local default_cmdline = rawget(_G, "arg") or {} | ||
| 2060 | |||
| 2061 | function Parser:_parse(args, error_handler) | ||
| 2062 | return ParseState(self, error_handler):parse(args or default_cmdline) | ||
| 2063 | end | ||
| 2064 | |||
| 2065 | function Parser:parse(args) | ||
| 2066 | return self:_parse(args, self.error) | ||
| 2067 | end | ||
| 2068 | |||
| 2069 | local function xpcall_error_handler(err) | ||
| 2070 | if not debug then | ||
| 2071 | return tostring(err) | ||
| 2072 | end | ||
| 2073 | return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) | ||
| 2074 | end | ||
| 2075 | |||
| 2076 | function Parser:pparse(args) | ||
| 2077 | local parse_error | ||
| 2078 | |||
| 2079 | local ok, result = xpcall(function() | ||
| 2080 | return self:_parse(args, function(_, err) | ||
| 2081 | parse_error = err | ||
| 2082 | error(err, 0) | ||
| 2083 | end) | ||
| 2084 | end, xpcall_error_handler) | ||
| 2085 | |||
| 2086 | if ok then | ||
| 2087 | return true, result | ||
| 2088 | elseif not parse_error then | ||
| 2089 | error(result, 0) | ||
| 2090 | else | ||
| 2091 | return false, parse_error | ||
| 2092 | end | ||
| 2093 | end | ||
| 2094 | |||
| 2095 | local argparse = {} | ||
| 2096 | |||
| 2097 | argparse.version = "0.7.0" | ||
| 2098 | |||
| 2099 | setmetatable(argparse, {__call = function(_, ...) | ||
| 2100 | return Parser(default_cmdline[0]):add_help(true)(...) | ||
| 2101 | end}) | ||
| 2102 | |||
| 2103 | return argparse | ||
diff --git a/vendor/compat53/file_mt.lua b/vendor/compat53/file_mt.lua new file mode 100644 index 00000000..6433619d --- /dev/null +++ b/vendor/compat53/file_mt.lua | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | local lua_version = _VERSION:sub(-3) | ||
| 2 | |||
| 3 | local M = {} | ||
| 4 | |||
| 5 | local unpack = lua_version == "5.1" and unpack or table.unpack | ||
| 6 | |||
| 7 | local function addasterisk(fmt) | ||
| 8 | if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then | ||
| 9 | return "*"..fmt | ||
| 10 | else | ||
| 11 | return fmt | ||
| 12 | end | ||
| 13 | end | ||
| 14 | |||
| 15 | function M.update_file_meta(file_meta, is_luajit52) | ||
| 16 | |||
| 17 | -- make '*' optional for file:read and file:lines | ||
| 18 | |||
| 19 | local file_lines = file_meta.__index.lines | ||
| 20 | file_meta.__index.lines = function(self, ...) | ||
| 21 | local n = select('#', ...) | ||
| 22 | for i = 1, n do | ||
| 23 | local a = select(i, ...) | ||
| 24 | local b = addasterisk(a) | ||
| 25 | -- as an optimization we only allocate a table for the | ||
| 26 | -- modified format arguments when we have a '*' somewhere | ||
| 27 | if a ~= b then | ||
| 28 | local args = { ... } | ||
| 29 | args[i] = b | ||
| 30 | for j = i+1, n do | ||
| 31 | args[j] = addasterisk(args[j]) | ||
| 32 | end | ||
| 33 | return file_lines(self, unpack(args, 1, n)) | ||
| 34 | end | ||
| 35 | end | ||
| 36 | return file_lines(self, ...) | ||
| 37 | end | ||
| 38 | |||
| 39 | local file_read = file_meta.__index.read | ||
| 40 | file_meta.__index.read = function(self, ...) | ||
| 41 | local n = select('#', ...) | ||
| 42 | for i = 1, n do | ||
| 43 | local a = select(i, ...) | ||
| 44 | local b = addasterisk(a) | ||
| 45 | -- as an optimization we only allocate a table for the | ||
| 46 | -- modified format arguments when we have a '*' somewhere | ||
| 47 | if a ~= b then | ||
| 48 | local args = { ... } | ||
| 49 | args[i] = b | ||
| 50 | for j = i+1, n do | ||
| 51 | args[j] = addasterisk(args[j]) | ||
| 52 | end | ||
| 53 | return file_read(self, unpack(args, 1, n)) | ||
| 54 | end | ||
| 55 | end | ||
| 56 | return file_read(self, ...) | ||
| 57 | end | ||
| 58 | |||
| 59 | if not is_luajit52 then | ||
| 60 | local file_write = file_meta.__index.write | ||
| 61 | file_meta.__index.write = function(self, ...) | ||
| 62 | local ret, err = file_write(self, ...) | ||
| 63 | if ret then | ||
| 64 | return self | ||
| 65 | end | ||
| 66 | return ret, err | ||
| 67 | end | ||
| 68 | end | ||
| 69 | end | ||
| 70 | |||
| 71 | return M | ||
diff --git a/vendor/compat53/init.lua b/vendor/compat53/init.lua new file mode 100644 index 00000000..b5075713 --- /dev/null +++ b/vendor/compat53/init.lua | |||
| @@ -0,0 +1,325 @@ | |||
| 1 | local lua_version = _VERSION:sub(-3) | ||
| 2 | |||
| 3 | |||
| 4 | if lua_version < "5.3" then | ||
| 5 | |||
| 6 | local _G, pairs, require, select, type = | ||
| 7 | _G, pairs, require, select, type | ||
| 8 | local debug, io = debug, io | ||
| 9 | local unpack = lua_version == "5.1" and unpack or table.unpack | ||
| 10 | |||
| 11 | local M = require("compat53.module") | ||
| 12 | |||
| 13 | -- select the most powerful getmetatable function available | ||
| 14 | local gmt = type(debug) == "table" and debug.getmetatable or | ||
| 15 | getmetatable or function() return false end | ||
| 16 | -- metatable for file objects from Lua's standard io library | ||
| 17 | local file_meta = gmt(io.stdout) | ||
| 18 | |||
| 19 | |||
| 20 | -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag) | ||
| 21 | local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ" | ||
| 22 | local is_luajit52 = is_luajit and | ||
| 23 | #setmetatable({}, { __len = function() return 1 end }) == 1 | ||
| 24 | |||
| 25 | |||
| 26 | if type(file_meta) == "table" and type(file_meta.__index) == "table" then | ||
| 27 | local file_mt = require("compat53.file_mt") | ||
| 28 | file_mt.update_file_meta(file_meta, is_luajit52) | ||
| 29 | end -- got a valid metatable for file objects | ||
| 30 | |||
| 31 | |||
| 32 | -- changes for Lua 5.1 only | ||
| 33 | if lua_version == "5.1" then | ||
| 34 | |||
| 35 | -- cache globals | ||
| 36 | local error, pcall, rawset, setmetatable, tostring, xpcall = | ||
| 37 | error, pcall, rawset, setmetatable, tostring, xpcall | ||
| 38 | local coroutine, package, string = coroutine, package, string | ||
| 39 | local coroutine_resume = coroutine.resume | ||
| 40 | local coroutine_running = coroutine.running | ||
| 41 | local coroutine_status = coroutine.status | ||
| 42 | local coroutine_yield = coroutine.yield | ||
| 43 | local io_type = io.type | ||
| 44 | |||
| 45 | |||
| 46 | -- make package.searchers available as an alias for package.loaders | ||
| 47 | local p_index = { searchers = package.loaders } | ||
| 48 | setmetatable(package, { | ||
| 49 | __index = p_index, | ||
| 50 | __newindex = function(p, k, v) | ||
| 51 | if k == "searchers" then | ||
| 52 | rawset(p, "loaders", v) | ||
| 53 | p_index.searchers = v | ||
| 54 | else | ||
| 55 | rawset(p, k, v) | ||
| 56 | end | ||
| 57 | end | ||
| 58 | }) | ||
| 59 | |||
| 60 | |||
| 61 | if type(file_meta) == "table" and type(file_meta.__index) == "table" then | ||
| 62 | if not is_luajit then | ||
| 63 | local function helper(_, var_1, ...) | ||
| 64 | if var_1 == nil then | ||
| 65 | if (...) ~= nil then | ||
| 66 | error((...), 2) | ||
| 67 | end | ||
| 68 | end | ||
| 69 | return var_1, ... | ||
| 70 | end | ||
| 71 | |||
| 72 | local function lines_iterator(st) | ||
| 73 | return helper(st, st.f:read(unpack(st, 1, st.n))) | ||
| 74 | end | ||
| 75 | |||
| 76 | local file_write = file_meta.__index.write | ||
| 77 | file_meta.__index.write = function(self, ...) | ||
| 78 | local res, msg, errno = file_write(self, ...) | ||
| 79 | if res then | ||
| 80 | return self | ||
| 81 | else | ||
| 82 | return nil, msg, errno | ||
| 83 | end | ||
| 84 | end | ||
| 85 | |||
| 86 | file_meta.__index.lines = function(self, ...) | ||
| 87 | if io_type(self) == "closed file" then | ||
| 88 | error("attempt to use a closed file", 2) | ||
| 89 | end | ||
| 90 | local st = { f=self, n=select('#', ...), ... } | ||
| 91 | for i = 1, st.n do | ||
| 92 | local t = type(st[i]) | ||
| 93 | if t == "string" then | ||
| 94 | local fmt = st[i]:match("^*?([aln])") | ||
| 95 | if not fmt then | ||
| 96 | error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2) | ||
| 97 | end | ||
| 98 | st[i] = "*"..fmt | ||
| 99 | elseif t ~= "number" then | ||
| 100 | error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2) | ||
| 101 | end | ||
| 102 | end | ||
| 103 | return lines_iterator, st | ||
| 104 | end | ||
| 105 | end -- not luajit | ||
| 106 | end -- file_meta valid | ||
| 107 | |||
| 108 | |||
| 109 | -- the (x)pcall implementations start a new coroutine internally | ||
| 110 | -- to allow yielding even in Lua 5.1. to allow for accurate | ||
| 111 | -- stack traces we keep track of the nested coroutine activations | ||
| 112 | -- in the weak tables below: | ||
| 113 | local weak_meta = { __mode = "kv" } | ||
| 114 | -- maps the internal pcall coroutines to the user coroutine that | ||
| 115 | -- *should* be running if pcall didn't use coroutines internally | ||
| 116 | local pcall_mainOf = setmetatable({}, weak_meta) | ||
| 117 | -- table that maps each running coroutine started by pcall to | ||
| 118 | -- the coroutine that resumed it (user coroutine *or* pcall | ||
| 119 | -- coroutine!) | ||
| 120 | local pcall_previous = setmetatable({}, weak_meta) | ||
| 121 | -- reverse of `pcall_mainOf`. maps a user coroutine to the | ||
| 122 | -- currently active pcall coroutine started within it | ||
| 123 | local pcall_callOf = setmetatable({}, weak_meta) | ||
| 124 | -- similar to `pcall_mainOf` but is used only while executing | ||
| 125 | -- the error handler of xpcall (thus no nesting is necessary!) | ||
| 126 | local xpcall_running = setmetatable({}, weak_meta) | ||
| 127 | |||
| 128 | -- handle debug functions | ||
| 129 | if type(debug) == "table" then | ||
| 130 | local debug_getinfo = debug.getinfo | ||
| 131 | local debug_traceback = debug.traceback | ||
| 132 | |||
| 133 | if not is_luajit then | ||
| 134 | local function calculate_trace_level(co, level) | ||
| 135 | if level ~= nil then | ||
| 136 | for out = 1, 1/0 do | ||
| 137 | local info = (co==nil) and debug_getinfo(out, "") or debug_getinfo(co, out, "") | ||
| 138 | if info == nil then | ||
| 139 | local max = out-1 | ||
| 140 | if level <= max then | ||
| 141 | return level | ||
| 142 | end | ||
| 143 | return nil, level-max | ||
| 144 | end | ||
| 145 | end | ||
| 146 | end | ||
| 147 | return 1 | ||
| 148 | end | ||
| 149 | |||
| 150 | local stack_pattern = "\nstack traceback:" | ||
| 151 | local stack_replace = "" | ||
| 152 | function debug.traceback(co, msg, level) | ||
| 153 | local lvl | ||
| 154 | local nilmsg | ||
| 155 | if type(co) ~= "thread" then | ||
| 156 | co, msg, level = coroutine_running(), co, msg | ||
| 157 | end | ||
| 158 | if msg == nil then | ||
| 159 | msg = "" | ||
| 160 | nilmsg = true | ||
| 161 | elseif type(msg) ~= "string" then | ||
| 162 | return msg | ||
| 163 | end | ||
| 164 | if co == nil then | ||
| 165 | msg = debug_traceback(msg, level or 1) | ||
| 166 | else | ||
| 167 | local xpco = xpcall_running[co] | ||
| 168 | if xpco ~= nil then | ||
| 169 | lvl, level = calculate_trace_level(xpco, level) | ||
| 170 | if lvl then | ||
| 171 | msg = debug_traceback(xpco, msg, lvl) | ||
| 172 | else | ||
| 173 | msg = msg..stack_pattern | ||
| 174 | end | ||
| 175 | lvl, level = calculate_trace_level(co, level) | ||
| 176 | if lvl then | ||
| 177 | local trace = debug_traceback(co, "", lvl) | ||
| 178 | msg = msg..trace:gsub(stack_pattern, stack_replace) | ||
| 179 | end | ||
| 180 | else | ||
| 181 | co = pcall_callOf[co] or co | ||
| 182 | lvl, level = calculate_trace_level(co, level) | ||
| 183 | if lvl then | ||
| 184 | msg = debug_traceback(co, msg, lvl) | ||
| 185 | else | ||
| 186 | msg = msg..stack_pattern | ||
| 187 | end | ||
| 188 | end | ||
| 189 | co = pcall_previous[co] | ||
| 190 | while co ~= nil do | ||
| 191 | lvl, level = calculate_trace_level(co, level) | ||
| 192 | if lvl then | ||
| 193 | local trace = debug_traceback(co, "", lvl) | ||
| 194 | msg = msg..trace:gsub(stack_pattern, stack_replace) | ||
| 195 | end | ||
| 196 | co = pcall_previous[co] | ||
| 197 | end | ||
| 198 | end | ||
| 199 | if nilmsg then | ||
| 200 | msg = msg:gsub("^\n", "") | ||
| 201 | end | ||
| 202 | msg = msg:gsub("\n\t%(tail call%): %?", "\000") | ||
| 203 | msg = msg:gsub("\n\t%.%.%.\n", "\001\n") | ||
| 204 | msg = msg:gsub("\n\t%.%.%.$", "\001") | ||
| 205 | msg = msg:gsub("(%z+)\001(%z+)", function(some, other) | ||
| 206 | return "\n\t(..."..#some+#other.."+ tail call(s)...)" | ||
| 207 | end) | ||
| 208 | msg = msg:gsub("\001(%z+)", function(zeros) | ||
| 209 | return "\n\t(..."..#zeros.."+ tail call(s)...)" | ||
| 210 | end) | ||
| 211 | msg = msg:gsub("(%z+)\001", function(zeros) | ||
| 212 | return "\n\t(..."..#zeros.."+ tail call(s)...)" | ||
| 213 | end) | ||
| 214 | msg = msg:gsub("%z+", function(zeros) | ||
| 215 | return "\n\t(..."..#zeros.." tail call(s)...)" | ||
| 216 | end) | ||
| 217 | msg = msg:gsub("\001", function() | ||
| 218 | return "\n\t..." | ||
| 219 | end) | ||
| 220 | return msg | ||
| 221 | end | ||
| 222 | end -- is not luajit | ||
| 223 | end -- debug table available | ||
| 224 | |||
| 225 | |||
| 226 | if not is_luajit52 then | ||
| 227 | local coroutine_running52 = M.coroutine.running | ||
| 228 | function M.coroutine.running() | ||
| 229 | local co, ismain = coroutine_running52() | ||
| 230 | if ismain then | ||
| 231 | return co, true | ||
| 232 | else | ||
| 233 | return pcall_mainOf[co] or co, false | ||
| 234 | end | ||
| 235 | end | ||
| 236 | end | ||
| 237 | |||
| 238 | if not is_luajit then | ||
| 239 | local function pcall_results(current, call, success, ...) | ||
| 240 | if coroutine_status(call) == "suspended" then | ||
| 241 | return pcall_results(current, call, coroutine_resume(call, coroutine_yield(...))) | ||
| 242 | end | ||
| 243 | if pcall_previous then | ||
| 244 | pcall_previous[call] = nil | ||
| 245 | local main = pcall_mainOf[call] | ||
| 246 | if main == current then current = nil end | ||
| 247 | pcall_callOf[main] = current | ||
| 248 | end | ||
| 249 | pcall_mainOf[call] = nil | ||
| 250 | return success, ... | ||
| 251 | end | ||
| 252 | |||
| 253 | local function pcall_exec(current, call, ...) | ||
| 254 | local main = pcall_mainOf[current] or current | ||
| 255 | pcall_mainOf[call] = main | ||
| 256 | if pcall_previous then | ||
| 257 | pcall_previous[call] = current | ||
| 258 | pcall_callOf[main] = call | ||
| 259 | end | ||
| 260 | return pcall_results(current, call, coroutine_resume(call, ...)) | ||
| 261 | end | ||
| 262 | |||
| 263 | local coroutine_create52 = M.coroutine.create | ||
| 264 | |||
| 265 | local function pcall_coroutine(func) | ||
| 266 | if type(func) ~= "function" then | ||
| 267 | local callable = func | ||
| 268 | func = function (...) return callable(...) end | ||
| 269 | end | ||
| 270 | return coroutine_create52(func) | ||
| 271 | end | ||
| 272 | |||
| 273 | function M.pcall(func, ...) | ||
| 274 | local current = coroutine_running() | ||
| 275 | if not current then return pcall(func, ...) end | ||
| 276 | return pcall_exec(current, pcall_coroutine(func), ...) | ||
| 277 | end | ||
| 278 | |||
| 279 | local function xpcall_catch(current, call, msgh, success, ...) | ||
| 280 | if not success then | ||
| 281 | xpcall_running[current] = call | ||
| 282 | local ok, result = pcall(msgh, ...) | ||
| 283 | xpcall_running[current] = nil | ||
| 284 | if not ok then | ||
| 285 | return false, "error in error handling ("..tostring(result)..")" | ||
| 286 | end | ||
| 287 | return false, result | ||
| 288 | end | ||
| 289 | return true, ... | ||
| 290 | end | ||
| 291 | |||
| 292 | function M.xpcall(f, msgh, ...) | ||
| 293 | local current = coroutine_running() | ||
| 294 | if not current then | ||
| 295 | local args, n = { ... }, select('#', ...) | ||
| 296 | return xpcall(function() return f(unpack(args, 1, n)) end, msgh) | ||
| 297 | end | ||
| 298 | local call = pcall_coroutine(f) | ||
| 299 | return xpcall_catch(current, call, msgh, pcall_exec(current, call, ...)) | ||
| 300 | end | ||
| 301 | end -- not luajit | ||
| 302 | |||
| 303 | end -- lua 5.1 | ||
| 304 | |||
| 305 | |||
| 306 | -- handle exporting to global scope | ||
| 307 | local function extend_table(from, to) | ||
| 308 | if from ~= to then | ||
| 309 | for k,v in pairs(from) do | ||
| 310 | if type(v) == "table" and | ||
| 311 | type(to[k]) == "table" and | ||
| 312 | v ~= to[k] then | ||
| 313 | extend_table(v, to[k]) | ||
| 314 | else | ||
| 315 | to[k] = v | ||
| 316 | end | ||
| 317 | end | ||
| 318 | end | ||
| 319 | end | ||
| 320 | |||
| 321 | extend_table(M, _G) | ||
| 322 | |||
| 323 | end -- lua < 5.3 | ||
| 324 | |||
| 325 | -- vi: set expandtab softtabstop=3 shiftwidth=3 : | ||
diff --git a/vendor/compat53/module.lua b/vendor/compat53/module.lua new file mode 100644 index 00000000..b580e536 --- /dev/null +++ b/vendor/compat53/module.lua | |||
| @@ -0,0 +1,894 @@ | |||
| 1 | local _G, _VERSION = _G, _VERSION | ||
| 2 | local lua_version = _VERSION:sub(-3) | ||
| 3 | |||
| 4 | |||
| 5 | local M = _G | ||
| 6 | |||
| 7 | if lua_version < "5.3" then | ||
| 8 | |||
| 9 | -- cache globals in upvalues | ||
| 10 | local error, ipairs, pairs, pcall, require, select, setmetatable, type = | ||
| 11 | error, ipairs, pairs, pcall, require, select, setmetatable, type | ||
| 12 | local debug, io, math, package, string, table = | ||
| 13 | debug, io, math, package, string, table | ||
| 14 | local io_lines = io.lines | ||
| 15 | local io_read = io.read | ||
| 16 | local io_open = io.open | ||
| 17 | local io_popen = io.popen | ||
| 18 | local io_tmpfile = io.tmpfile | ||
| 19 | local unpack = lua_version == "5.1" and unpack or table.unpack | ||
| 20 | local debug_setmetatable = type(debug) == "table" and debug.setmetatable | ||
| 21 | |||
| 22 | -- create module table | ||
| 23 | M = {} | ||
| 24 | local M_meta = { | ||
| 25 | __index = _G, | ||
| 26 | -- __newindex is set at the end | ||
| 27 | } | ||
| 28 | setmetatable(M, M_meta) | ||
| 29 | |||
| 30 | -- create subtables | ||
| 31 | M.io = setmetatable({}, { __index = io }) | ||
| 32 | M.math = setmetatable({}, { __index = math }) | ||
| 33 | M.string = setmetatable({}, { __index = string }) | ||
| 34 | M.table = setmetatable({}, { __index = table }) | ||
| 35 | M.utf8 = {} | ||
| 36 | |||
| 37 | |||
| 38 | -- select the most powerful getmetatable function available | ||
| 39 | local gmt = type(debug) == "table" and debug.getmetatable or | ||
| 40 | getmetatable or function() return false end | ||
| 41 | |||
| 42 | -- type checking functions | ||
| 43 | local checkinteger -- forward declararation | ||
| 44 | |||
| 45 | local function argcheck(cond, i, f, extra) | ||
| 46 | if not cond then | ||
| 47 | error("bad argument #"..i.." to '"..f.."' ("..extra..")", 0) | ||
| 48 | end | ||
| 49 | end | ||
| 50 | |||
| 51 | |||
| 52 | -- load utf8 library | ||
| 53 | local utf8_ok, utf8lib = pcall(require, "compat53.utf8") | ||
| 54 | if utf8_ok then | ||
| 55 | if lua_version == "5.1" then | ||
| 56 | utf8lib.charpattern = "[%z\1-\127\194-\244][\128-\191]*" | ||
| 57 | end | ||
| 58 | for k,v in pairs(utf8lib) do | ||
| 59 | M.utf8[k] = v | ||
| 60 | end | ||
| 61 | package.loaded["utf8"] = M.utf8 | ||
| 62 | end | ||
| 63 | |||
| 64 | |||
| 65 | -- load table library | ||
| 66 | local table_ok, tablib = pcall(require, "compat53.table") | ||
| 67 | if table_ok then | ||
| 68 | for k,v in pairs(tablib) do | ||
| 69 | M.table[k] = v | ||
| 70 | end | ||
| 71 | end | ||
| 72 | |||
| 73 | |||
| 74 | -- load io functions | ||
| 75 | local io_ok, iolib = pcall(require, "compat53.io") | ||
| 76 | if io_ok then | ||
| 77 | for k,v in pairs(iolib) do | ||
| 78 | M.io[k] = v | ||
| 79 | end | ||
| 80 | end | ||
| 81 | |||
| 82 | |||
| 83 | -- load string packing functions | ||
| 84 | local str_ok, strlib = pcall(require, "compat53.string") | ||
| 85 | if str_ok then | ||
| 86 | for k,v in pairs(strlib) do | ||
| 87 | M.string[k] = v | ||
| 88 | end | ||
| 89 | end | ||
| 90 | |||
| 91 | |||
| 92 | -- try Roberto's struct module for string packing/unpacking if | ||
| 93 | -- compat53.string is unavailable | ||
| 94 | if not str_ok then | ||
| 95 | local struct_ok, struct = pcall(require, "struct") | ||
| 96 | if struct_ok then | ||
| 97 | M.string.pack = struct.pack | ||
| 98 | M.string.packsize = struct.size | ||
| 99 | M.string.unpack = struct.unpack | ||
| 100 | end | ||
| 101 | end | ||
| 102 | |||
| 103 | |||
| 104 | -- update math library | ||
| 105 | do | ||
| 106 | local maxint, minint = 1 | ||
| 107 | |||
| 108 | while maxint+1 > maxint and 2*maxint > maxint do | ||
| 109 | maxint = maxint * 2 | ||
| 110 | end | ||
| 111 | if 2*maxint <= maxint then | ||
| 112 | maxint = 2*maxint-1 | ||
| 113 | minint = -maxint-1 | ||
| 114 | else | ||
| 115 | maxint = maxint | ||
| 116 | minint = -maxint | ||
| 117 | end | ||
| 118 | M.math.maxinteger = maxint | ||
| 119 | M.math.mininteger = minint | ||
| 120 | |||
| 121 | function M.math.tointeger(n) | ||
| 122 | n = tonumber(n) | ||
| 123 | if type(n) == "number" and n <= maxint and n >= minint and n % 1 == 0 then | ||
| 124 | return n | ||
| 125 | end | ||
| 126 | return nil | ||
| 127 | end | ||
| 128 | |||
| 129 | function M.math.type(n) | ||
| 130 | if type(n) == "number" then | ||
| 131 | if n <= maxint and n >= minint and n % 1 == 0 then | ||
| 132 | return "integer" | ||
| 133 | else | ||
| 134 | return "float" | ||
| 135 | end | ||
| 136 | else | ||
| 137 | return nil | ||
| 138 | end | ||
| 139 | end | ||
| 140 | |||
| 141 | function checkinteger(x, i, f) | ||
| 142 | local t = type(x) | ||
| 143 | if t ~= "number" then | ||
| 144 | error("bad argument #"..i.." to '"..f.. | ||
| 145 | "' (number expected, got "..t..")", 0) | ||
| 146 | elseif x > maxint or x < minint or x % 1 ~= 0 then | ||
| 147 | error("bad argument #"..i.." to '"..f.. | ||
| 148 | "' (number has no integer representation)", 0) | ||
| 149 | else | ||
| 150 | return x | ||
| 151 | end | ||
| 152 | end | ||
| 153 | |||
| 154 | function M.math.ult(m, n) | ||
| 155 | m = checkinteger(m, "1", "math.ult") | ||
| 156 | n = checkinteger(n, "2", "math.ult") | ||
| 157 | if m >= 0 and n < 0 then | ||
| 158 | return true | ||
| 159 | elseif m < 0 and n >= 0 then | ||
| 160 | return false | ||
| 161 | else | ||
| 162 | return m < n | ||
| 163 | end | ||
| 164 | end | ||
| 165 | end | ||
| 166 | |||
| 167 | |||
| 168 | -- assert should allow non-string error objects | ||
| 169 | function M.assert(cond, ...) | ||
| 170 | if cond then | ||
| 171 | return cond, ... | ||
| 172 | elseif select('#', ...) > 0 then | ||
| 173 | error((...), 2) | ||
| 174 | else | ||
| 175 | error("assertion failed!", 2) | ||
| 176 | end | ||
| 177 | end | ||
| 178 | |||
| 179 | |||
| 180 | -- ipairs should respect __index metamethod | ||
| 181 | do | ||
| 182 | local function ipairs_iterator(st, var) | ||
| 183 | var = var + 1 | ||
| 184 | local val = st[var] | ||
| 185 | if val ~= nil then | ||
| 186 | return var, st[var] | ||
| 187 | end | ||
| 188 | end | ||
| 189 | function M.ipairs(t) | ||
| 190 | if gmt(t) ~= nil then -- t has metatable | ||
| 191 | return ipairs_iterator, t, 0 | ||
| 192 | else | ||
| 193 | return ipairs(t) | ||
| 194 | end | ||
| 195 | end | ||
| 196 | end | ||
| 197 | |||
| 198 | |||
| 199 | -- make '*' optional for io.read and io.lines | ||
| 200 | do | ||
| 201 | local function addasterisk(fmt) | ||
| 202 | if type(fmt) == "string" and fmt:sub(1, 1) ~= "*" then | ||
| 203 | return "*"..fmt | ||
| 204 | else | ||
| 205 | return fmt | ||
| 206 | end | ||
| 207 | end | ||
| 208 | |||
| 209 | function M.io.read(...) | ||
| 210 | local n = select('#', ...) | ||
| 211 | for i = 1, n do | ||
| 212 | local a = select(i, ...) | ||
| 213 | local b = addasterisk(a) | ||
| 214 | -- as an optimization we only allocate a table for the | ||
| 215 | -- modified format arguments when we have a '*' somewhere. | ||
| 216 | if a ~= b then | ||
| 217 | local args = { ... } | ||
| 218 | args[i] = b | ||
| 219 | for j = i+1, n do | ||
| 220 | args[j] = addasterisk(args[j]) | ||
| 221 | end | ||
| 222 | return io_read(unpack(args, 1, n)) | ||
| 223 | end | ||
| 224 | end | ||
| 225 | return io_read(...) | ||
| 226 | end | ||
| 227 | |||
| 228 | -- PUC-Rio Lua 5.1 uses a different implementation for io.lines! | ||
| 229 | function M.io.lines(...) | ||
| 230 | local n = select('#', ...) | ||
| 231 | for i = 2, n do | ||
| 232 | local a = select(i, ...) | ||
| 233 | local b = addasterisk(a) | ||
| 234 | -- as an optimization we only allocate a table for the | ||
| 235 | -- modified format arguments when we have a '*' somewhere. | ||
| 236 | if a ~= b then | ||
| 237 | local args = { ... } | ||
| 238 | args[i] = b | ||
| 239 | for j = i+1, n do | ||
| 240 | args[j] = addasterisk(args[j]) | ||
| 241 | end | ||
| 242 | return io_lines(unpack(args, 1, n)) | ||
| 243 | end | ||
| 244 | end | ||
| 245 | return io_lines(...) | ||
| 246 | end | ||
| 247 | end | ||
| 248 | |||
| 249 | |||
| 250 | -- update table library (if C module not available) | ||
| 251 | if not table_ok then | ||
| 252 | local table_concat = table.concat | ||
| 253 | local table_insert = table.insert | ||
| 254 | local table_remove = table.remove | ||
| 255 | local table_sort = table.sort | ||
| 256 | |||
| 257 | function M.table.concat(list, sep, i, j) | ||
| 258 | local mt = gmt(list) | ||
| 259 | if type(mt) == "table" and type(mt.__len) == "function" then | ||
| 260 | local src = list | ||
| 261 | list, i, j = {}, i or 1, j or mt.__len(src) | ||
| 262 | for k = i, j do | ||
| 263 | list[k] = src[k] | ||
| 264 | end | ||
| 265 | end | ||
| 266 | return table_concat(list, sep, i, j) | ||
| 267 | end | ||
| 268 | |||
| 269 | function M.table.insert(list, ...) | ||
| 270 | local mt = gmt(list) | ||
| 271 | local has_mt = type(mt) == "table" | ||
| 272 | local has_len = has_mt and type(mt.__len) == "function" | ||
| 273 | if has_mt and (has_len or mt.__index or mt.__newindex) then | ||
| 274 | local e = (has_len and mt.__len(list) or #list)+1 | ||
| 275 | local nargs, pos, value = select('#', ...), ... | ||
| 276 | if nargs == 1 then | ||
| 277 | pos, value = e, pos | ||
| 278 | elseif nargs == 2 then | ||
| 279 | pos = checkinteger(pos, "2", "table.insert") | ||
| 280 | argcheck(1 <= pos and pos <= e, "2", "table.insert", | ||
| 281 | "position out of bounds" ) | ||
| 282 | else | ||
| 283 | error("wrong number of arguments to 'insert'", 0) | ||
| 284 | end | ||
| 285 | for i = e-1, pos, -1 do | ||
| 286 | list[i+1] = list[i] | ||
| 287 | end | ||
| 288 | list[pos] = value | ||
| 289 | else | ||
| 290 | return table_insert(list, ...) | ||
| 291 | end | ||
| 292 | end | ||
| 293 | |||
| 294 | function M.table.move(a1, f, e, t, a2) | ||
| 295 | a2 = a2 or a1 | ||
| 296 | f = checkinteger(f, "2", "table.move") | ||
| 297 | argcheck(f > 0, "2", "table.move", | ||
| 298 | "initial position must be positive") | ||
| 299 | e = checkinteger(e, "3", "table.move") | ||
| 300 | t = checkinteger(t, "4", "table.move") | ||
| 301 | if e >= f then | ||
| 302 | local m, n, d = 0, e-f, 1 | ||
| 303 | if t > f then m, n, d = n, m, -1 end | ||
| 304 | for i = m, n, d do | ||
| 305 | a2[t+i] = a1[f+i] | ||
| 306 | end | ||
| 307 | end | ||
| 308 | return a2 | ||
| 309 | end | ||
| 310 | |||
| 311 | function M.table.remove(list, pos) | ||
| 312 | local mt = gmt(list) | ||
| 313 | local has_mt = type(mt) == "table" | ||
| 314 | local has_len = has_mt and type(mt.__len) == "function" | ||
| 315 | if has_mt and (has_len or mt.__index or mt.__newindex) then | ||
| 316 | local e = (has_len and mt.__len(list) or #list) | ||
| 317 | pos = pos ~= nil and checkinteger(pos, "2", "table.remove") or e | ||
| 318 | if pos ~= e then | ||
| 319 | argcheck(1 <= pos and pos <= e+1, "2", "table.remove", | ||
| 320 | "position out of bounds" ) | ||
| 321 | end | ||
| 322 | local result = list[pos] | ||
| 323 | while pos < e do | ||
| 324 | list[pos] = list[pos+1] | ||
| 325 | pos = pos + 1 | ||
| 326 | end | ||
| 327 | list[pos] = nil | ||
| 328 | return result | ||
| 329 | else | ||
| 330 | return table_remove(list, pos) | ||
| 331 | end | ||
| 332 | end | ||
| 333 | |||
| 334 | do | ||
| 335 | local function pivot(list, cmp, a, b) | ||
| 336 | local m = b - a | ||
| 337 | if m > 2 then | ||
| 338 | local c = a + (m-m%2)/2 | ||
| 339 | local x, y, z = list[a], list[b], list[c] | ||
| 340 | if not cmp(x, y) then | ||
| 341 | x, y, a, b = y, x, b, a | ||
| 342 | end | ||
| 343 | if not cmp(y, z) then | ||
| 344 | y, b = z, c | ||
| 345 | end | ||
| 346 | if not cmp(x, y) then | ||
| 347 | y, b = x, a | ||
| 348 | end | ||
| 349 | return b, y | ||
| 350 | else | ||
| 351 | return b, list[b] | ||
| 352 | end | ||
| 353 | end | ||
| 354 | |||
| 355 | local function lt_cmp(a, b) | ||
| 356 | return a < b | ||
| 357 | end | ||
| 358 | |||
| 359 | local function qsort(list, cmp, b, e) | ||
| 360 | if b < e then | ||
| 361 | local i, j, k, val = b, e, pivot(list, cmp, b, e) | ||
| 362 | while i < j do | ||
| 363 | while i < j and cmp(list[i], val) do | ||
| 364 | i = i + 1 | ||
| 365 | end | ||
| 366 | while i < j and not cmp(list[j], val) do | ||
| 367 | j = j - 1 | ||
| 368 | end | ||
| 369 | if i < j then | ||
| 370 | list[i], list[j] = list[j], list[i] | ||
| 371 | if i == k then k = j end -- update pivot position | ||
| 372 | i, j = i+1, j-1 | ||
| 373 | end | ||
| 374 | end | ||
| 375 | if i ~= k and not cmp(list[i], val) then | ||
| 376 | list[i], list[k] = val, list[i] | ||
| 377 | k = i -- update pivot position | ||
| 378 | end | ||
| 379 | qsort(list, cmp, b, i == k and i-1 or i) | ||
| 380 | return qsort(list, cmp, i+1, e) | ||
| 381 | end | ||
| 382 | end | ||
| 383 | |||
| 384 | function M.table.sort(list, cmp) | ||
| 385 | local mt = gmt(list) | ||
| 386 | local has_mt = type(mt) == "table" | ||
| 387 | local has_len = has_mt and type(mt.__len) == "function" | ||
| 388 | if has_len then | ||
| 389 | cmp = cmp or lt_cmp | ||
| 390 | local len = mt.__len(list) | ||
| 391 | return qsort(list, cmp, 1, len) | ||
| 392 | else | ||
| 393 | return table_sort(list, cmp) | ||
| 394 | end | ||
| 395 | end | ||
| 396 | end | ||
| 397 | |||
| 398 | local function unpack_helper(list, i, j, ...) | ||
| 399 | if j < i then | ||
| 400 | return ... | ||
| 401 | else | ||
| 402 | return unpack_helper(list, i, j-1, list[j], ...) | ||
| 403 | end | ||
| 404 | end | ||
| 405 | function M.table.unpack(list, i, j) | ||
| 406 | local mt = gmt(list) | ||
| 407 | local has_mt = type(mt) == "table" | ||
| 408 | local has_len = has_mt and type(mt.__len) == "function" | ||
| 409 | if has_mt and (has_len or mt.__index) then | ||
| 410 | i, j = i or 1, j or (has_len and mt.__len(list)) or #list | ||
| 411 | return unpack_helper(list, i, j) | ||
| 412 | else | ||
| 413 | return unpack(list, i, j) | ||
| 414 | end | ||
| 415 | end | ||
| 416 | end -- update table library | ||
| 417 | |||
| 418 | |||
| 419 | -- bring Lua 5.1 (and LuaJIT) up to speed with Lua 5.2 | ||
| 420 | if lua_version == "5.1" then | ||
| 421 | -- detect LuaJIT (including LUAJIT_ENABLE_LUA52COMPAT compilation flag) | ||
| 422 | local is_luajit = (string.dump(function() end) or ""):sub(1, 3) == "\027LJ" | ||
| 423 | local is_luajit52 = is_luajit and | ||
| 424 | #setmetatable({}, { __len = function() return 1 end }) == 1 | ||
| 425 | |||
| 426 | -- cache globals in upvalues | ||
| 427 | local load, loadfile, loadstring, setfenv, xpcall = | ||
| 428 | load, loadfile, loadstring, setfenv, xpcall | ||
| 429 | local coroutine, os = coroutine, os | ||
| 430 | local coroutine_create = coroutine.create | ||
| 431 | local coroutine_resume = coroutine.resume | ||
| 432 | local coroutine_running = coroutine.running | ||
| 433 | local coroutine_status = coroutine.status | ||
| 434 | local coroutine_yield = coroutine.yield | ||
| 435 | local io_input = io.input | ||
| 436 | local io_open = io.open | ||
| 437 | local io_output = io.output | ||
| 438 | local io_write = io.write | ||
| 439 | local math_log = math.log | ||
| 440 | local os_execute = os.execute | ||
| 441 | local string_find = string.find | ||
| 442 | local string_format = string.format | ||
| 443 | local string_gmatch = string.gmatch | ||
| 444 | local string_gsub = string.gsub | ||
| 445 | local string_match = string.match | ||
| 446 | local string_rep = string.rep | ||
| 447 | local table_concat = table.concat | ||
| 448 | |||
| 449 | -- create subtables | ||
| 450 | M.coroutine = setmetatable({}, { __index = coroutine }) | ||
| 451 | M.os = setmetatable({}, { __index = os }) | ||
| 452 | M.package = setmetatable({}, { __index = package }) | ||
| 453 | |||
| 454 | -- handle debug functions | ||
| 455 | if type(debug) == "table" then | ||
| 456 | local debug_setfenv = debug.setfenv | ||
| 457 | local debug_getfenv = debug.getfenv | ||
| 458 | |||
| 459 | M.debug = setmetatable({}, { __index = debug }) | ||
| 460 | |||
| 461 | if not is_luajit52 then | ||
| 462 | function M.debug.setuservalue(obj, value) | ||
| 463 | if type(obj) ~= "userdata" then | ||
| 464 | error("bad argument #1 to 'setuservalue' (userdata expected, got ".. | ||
| 465 | type(obj)..")", 2) | ||
| 466 | end | ||
| 467 | if value == nil then value = _G end | ||
| 468 | if type(value) ~= "table" then | ||
| 469 | error("bad argument #2 to 'setuservalue' (table expected, got ".. | ||
| 470 | type(value)..")", 2) | ||
| 471 | end | ||
| 472 | return debug_setfenv(obj, value) | ||
| 473 | end | ||
| 474 | |||
| 475 | function M.debug.getuservalue(obj) | ||
| 476 | if type(obj) ~= "userdata" then | ||
| 477 | return nil | ||
| 478 | else | ||
| 479 | local v = debug_getfenv(obj) | ||
| 480 | if v == _G or v == package then | ||
| 481 | return nil | ||
| 482 | end | ||
| 483 | return v | ||
| 484 | end | ||
| 485 | end | ||
| 486 | |||
| 487 | function M.debug.setmetatable(value, tab) | ||
| 488 | debug_setmetatable(value, tab) | ||
| 489 | return value | ||
| 490 | end | ||
| 491 | end -- not luajit with compat52 enabled | ||
| 492 | end -- debug table available | ||
| 493 | |||
| 494 | |||
| 495 | if not is_luajit52 then | ||
| 496 | function M.pairs(t) | ||
| 497 | local mt = gmt(t) | ||
| 498 | if type(mt) == "table" and type(mt.__pairs) == "function" then | ||
| 499 | return mt.__pairs(t) | ||
| 500 | else | ||
| 501 | return pairs(t) | ||
| 502 | end | ||
| 503 | end | ||
| 504 | end | ||
| 505 | |||
| 506 | |||
| 507 | if not is_luajit then | ||
| 508 | local function check_mode(mode, prefix) | ||
| 509 | local has = { text = false, binary = false } | ||
| 510 | for i = 1,#mode do | ||
| 511 | local c = mode:sub(i, i) | ||
| 512 | if c == "t" then has.text = true end | ||
| 513 | if c == "b" then has.binary = true end | ||
| 514 | end | ||
| 515 | local t = prefix:sub(1, 1) == "\27" and "binary" or "text" | ||
| 516 | if not has[t] then | ||
| 517 | return "attempt to load a "..t.." chunk (mode is '"..mode.."')" | ||
| 518 | end | ||
| 519 | end | ||
| 520 | |||
| 521 | function M.load(ld, source, mode, env) | ||
| 522 | mode = mode or "bt" | ||
| 523 | local chunk, msg | ||
| 524 | if type( ld ) == "string" then | ||
| 525 | if mode ~= "bt" then | ||
| 526 | local merr = check_mode(mode, ld) | ||
| 527 | if merr then return nil, merr end | ||
| 528 | end | ||
| 529 | chunk, msg = loadstring(ld, source) | ||
| 530 | else | ||
| 531 | local ld_type = type(ld) | ||
| 532 | if ld_type ~= "function" then | ||
| 533 | error("bad argument #1 to 'load' (function expected, got ".. | ||
| 534 | ld_type..")", 2) | ||
| 535 | end | ||
| 536 | if mode ~= "bt" then | ||
| 537 | local checked, merr = false, nil | ||
| 538 | local function checked_ld() | ||
| 539 | if checked then | ||
| 540 | return ld() | ||
| 541 | else | ||
| 542 | checked = true | ||
| 543 | local v = ld() | ||
| 544 | merr = check_mode(mode, v or "") | ||
| 545 | if merr then return nil end | ||
| 546 | return v | ||
| 547 | end | ||
| 548 | end | ||
| 549 | chunk, msg = load(checked_ld, source) | ||
| 550 | if merr then return nil, merr end | ||
| 551 | else | ||
| 552 | chunk, msg = load(ld, source) | ||
| 553 | end | ||
| 554 | end | ||
| 555 | if not chunk then | ||
| 556 | return chunk, msg | ||
| 557 | end | ||
| 558 | if env ~= nil then | ||
| 559 | setfenv(chunk, env) | ||
| 560 | end | ||
| 561 | return chunk | ||
| 562 | end | ||
| 563 | |||
| 564 | M.loadstring = M.load | ||
| 565 | |||
| 566 | function M.loadfile(file, mode, env) | ||
| 567 | mode = mode or "bt" | ||
| 568 | if mode ~= "bt" then | ||
| 569 | local f = io_open(file, "rb") | ||
| 570 | if f then | ||
| 571 | local prefix = f:read(1) | ||
| 572 | f:close() | ||
| 573 | if prefix then | ||
| 574 | local merr = check_mode(mode, prefix) | ||
| 575 | if merr then return nil, merr end | ||
| 576 | end | ||
| 577 | end | ||
| 578 | end | ||
| 579 | local chunk, msg = loadfile(file) | ||
| 580 | if not chunk then | ||
| 581 | return chunk, msg | ||
| 582 | end | ||
| 583 | if env ~= nil then | ||
| 584 | setfenv(chunk, env) | ||
| 585 | end | ||
| 586 | return chunk | ||
| 587 | end | ||
| 588 | end -- not luajit | ||
| 589 | |||
| 590 | |||
| 591 | if not is_luajit52 then | ||
| 592 | function M.rawlen(v) | ||
| 593 | local t = type(v) | ||
| 594 | if t ~= "string" and t ~= "table" then | ||
| 595 | error("bad argument #1 to 'rawlen' (table or string expected)", 2) | ||
| 596 | end | ||
| 597 | return #v | ||
| 598 | end | ||
| 599 | end | ||
| 600 | |||
| 601 | |||
| 602 | if not is_luajit then | ||
| 603 | function M.xpcall(f, msgh, ...) | ||
| 604 | local args, n = { ... }, select('#', ...) | ||
| 605 | return xpcall(function() return f(unpack(args, 1, n)) end, msgh) | ||
| 606 | end | ||
| 607 | end | ||
| 608 | |||
| 609 | |||
| 610 | if not is_luajit52 then | ||
| 611 | function M.os.execute(cmd) | ||
| 612 | local code = os_execute(cmd) | ||
| 613 | -- Lua 5.1 does not report exit by signal. | ||
| 614 | if code == 0 then | ||
| 615 | return true, "exit", code | ||
| 616 | else | ||
| 617 | if package.config:sub(1, 1) == '/' then | ||
| 618 | code = code/256 -- only correct on Linux! | ||
| 619 | end | ||
| 620 | return nil, "exit", code | ||
| 621 | end | ||
| 622 | end | ||
| 623 | end | ||
| 624 | |||
| 625 | |||
| 626 | if not table_ok and not is_luajit52 then | ||
| 627 | M.table.pack = function(...) | ||
| 628 | return { n = select('#', ...), ... } | ||
| 629 | end | ||
| 630 | end | ||
| 631 | |||
| 632 | |||
| 633 | local main_coroutine = coroutine_create(function() end) | ||
| 634 | |||
| 635 | function M.coroutine.create(func) | ||
| 636 | local success, result = pcall(coroutine_create, func) | ||
| 637 | if not success then | ||
| 638 | if type(func) ~= "function" then | ||
| 639 | error("bad argument #1 (function expected)", 0) | ||
| 640 | end | ||
| 641 | result = coroutine_create(function(...) return func(...) end) | ||
| 642 | end | ||
| 643 | return result | ||
| 644 | end | ||
| 645 | |||
| 646 | if not is_luajit52 then | ||
| 647 | function M.coroutine.running() | ||
| 648 | local co = coroutine_running() | ||
| 649 | if co then | ||
| 650 | return co, false | ||
| 651 | else | ||
| 652 | return main_coroutine, true | ||
| 653 | end | ||
| 654 | end | ||
| 655 | end | ||
| 656 | |||
| 657 | function M.coroutine.yield(...) | ||
| 658 | local co, flag = coroutine_running() | ||
| 659 | if co and not flag then | ||
| 660 | return coroutine_yield(...) | ||
| 661 | else | ||
| 662 | error("attempt to yield from outside a coroutine", 0) | ||
| 663 | end | ||
| 664 | end | ||
| 665 | |||
| 666 | if not is_luajit then | ||
| 667 | function M.coroutine.resume(co, ...) | ||
| 668 | if co == main_coroutine then | ||
| 669 | return false, "cannot resume non-suspended coroutine" | ||
| 670 | else | ||
| 671 | return coroutine_resume(co, ...) | ||
| 672 | end | ||
| 673 | end | ||
| 674 | |||
| 675 | function M.coroutine.status(co) | ||
| 676 | local notmain = coroutine_running() | ||
| 677 | if co == main_coroutine then | ||
| 678 | return notmain and "normal" or "running" | ||
| 679 | else | ||
| 680 | return coroutine_status(co) | ||
| 681 | end | ||
| 682 | end | ||
| 683 | end -- not luajit | ||
| 684 | |||
| 685 | |||
| 686 | if not is_luajit then | ||
| 687 | M.math.log = function(x, base) | ||
| 688 | if base ~= nil then | ||
| 689 | return math_log(x)/math_log(base) | ||
| 690 | else | ||
| 691 | return math_log(x) | ||
| 692 | end | ||
| 693 | end | ||
| 694 | end | ||
| 695 | |||
| 696 | |||
| 697 | if not is_luajit then | ||
| 698 | function M.package.searchpath(name, path, sep, rep) | ||
| 699 | sep = (sep or "."):gsub("(%p)", "%%%1") | ||
| 700 | rep = (rep or package.config:sub(1, 1)):gsub("(%%)", "%%%1") | ||
| 701 | local pname = name:gsub(sep, rep):gsub("(%%)", "%%%1") | ||
| 702 | local msg = {} | ||
| 703 | for subpath in path:gmatch("[^;]+") do | ||
| 704 | local fpath = subpath:gsub("%?", pname) | ||
| 705 | local f = io_open(fpath, "r") | ||
| 706 | if f then | ||
| 707 | f:close() | ||
| 708 | return fpath | ||
| 709 | end | ||
| 710 | msg[#msg+1] = "\n\tno file '" .. fpath .. "'" | ||
| 711 | end | ||
| 712 | return nil, table_concat(msg) | ||
| 713 | end | ||
| 714 | end | ||
| 715 | |||
| 716 | |||
| 717 | local function fix_pattern(pattern) | ||
| 718 | return (string_gsub(pattern, "%z", "%%z")) | ||
| 719 | end | ||
| 720 | |||
| 721 | function M.string.find(s, pattern, ...) | ||
| 722 | return string_find(s, fix_pattern(pattern), ...) | ||
| 723 | end | ||
| 724 | |||
| 725 | function M.string.gmatch(s, pattern) | ||
| 726 | return string_gmatch(s, fix_pattern(pattern)) | ||
| 727 | end | ||
| 728 | |||
| 729 | function M.string.gsub(s, pattern, ...) | ||
| 730 | return string_gsub(s, fix_pattern(pattern), ...) | ||
| 731 | end | ||
| 732 | |||
| 733 | function M.string.match(s, pattern, ...) | ||
| 734 | return string_match(s, fix_pattern(pattern), ...) | ||
| 735 | end | ||
| 736 | |||
| 737 | if not is_luajit then | ||
| 738 | function M.string.rep(s, n, sep) | ||
| 739 | if sep ~= nil and sep ~= "" and n >= 2 then | ||
| 740 | return s .. string_rep(sep..s, n-1) | ||
| 741 | else | ||
| 742 | return string_rep(s, n) | ||
| 743 | end | ||
| 744 | end | ||
| 745 | end | ||
| 746 | |||
| 747 | if not is_luajit then | ||
| 748 | do | ||
| 749 | local addqt = { | ||
| 750 | ["\n"] = "\\\n", | ||
| 751 | ["\\"] = "\\\\", | ||
| 752 | ["\""] = "\\\"" | ||
| 753 | } | ||
| 754 | |||
| 755 | local function addquoted(c, d) | ||
| 756 | return (addqt[c] or string_format(d~="" and "\\%03d" or "\\%d", c:byte()))..d | ||
| 757 | end | ||
| 758 | |||
| 759 | function M.string.format(fmt, ...) | ||
| 760 | local args, n = { ... }, select('#', ...) | ||
| 761 | local i = 0 | ||
| 762 | local function adjust_fmt(lead, mods, kind) | ||
| 763 | if #lead % 2 == 0 then | ||
| 764 | i = i + 1 | ||
| 765 | if kind == "s" then | ||
| 766 | args[i] = _G.tostring(args[i]) | ||
| 767 | elseif kind == "q" then | ||
| 768 | args[i] = '"'..string_gsub(args[i], "([%z%c\\\"\n])(%d?)", addquoted)..'"' | ||
| 769 | return lead.."%"..mods.."s" | ||
| 770 | end | ||
| 771 | end | ||
| 772 | end | ||
| 773 | fmt = string_gsub(fmt, "(%%*)%%([%d%.%-%+%# ]*)(%a)", adjust_fmt) | ||
| 774 | return string_format(fmt, unpack(args, 1, n)) | ||
| 775 | end | ||
| 776 | end | ||
| 777 | end | ||
| 778 | |||
| 779 | |||
| 780 | function M.io.write(...) | ||
| 781 | local res, msg, errno = io_write(...) | ||
| 782 | if res then | ||
| 783 | return io_output() | ||
| 784 | else | ||
| 785 | return nil, msg, errno | ||
| 786 | end | ||
| 787 | end | ||
| 788 | |||
| 789 | if not is_luajit then | ||
| 790 | local function helper(st, var_1, ...) | ||
| 791 | if var_1 == nil then | ||
| 792 | if st.doclose then st.f:close() end | ||
| 793 | if (...) ~= nil then | ||
| 794 | error((...), 2) | ||
| 795 | end | ||
| 796 | end | ||
| 797 | return var_1, ... | ||
| 798 | end | ||
| 799 | |||
| 800 | local function lines_iterator(st) | ||
| 801 | return helper(st, st.f:read(unpack(st, 1, st.n))) | ||
| 802 | end | ||
| 803 | |||
| 804 | function M.io.lines(fname, ...) | ||
| 805 | local doclose, file, msg | ||
| 806 | if fname ~= nil then | ||
| 807 | doclose, file, msg = true, io_open(fname, "r") | ||
| 808 | if not file then error(msg, 2) end | ||
| 809 | else | ||
| 810 | doclose, file = false, io_input() | ||
| 811 | end | ||
| 812 | local st = { f=file, doclose=doclose, n=select('#', ...), ... } | ||
| 813 | for i = 1, st.n do | ||
| 814 | local t = type(st[i]) | ||
| 815 | if t == "string" then | ||
| 816 | local fmt = st[i]:match("^%*?([aln])") | ||
| 817 | if not fmt then | ||
| 818 | error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2) | ||
| 819 | end | ||
| 820 | st[i] = "*"..fmt | ||
| 821 | elseif t ~= "number" then | ||
| 822 | error("bad argument #"..(i+1).." to 'for iterator' (invalid format)", 2) | ||
| 823 | end | ||
| 824 | end | ||
| 825 | return lines_iterator, st | ||
| 826 | end | ||
| 827 | end -- not luajit | ||
| 828 | |||
| 829 | if is_luajit then | ||
| 830 | local compat_file_meta = {} | ||
| 831 | local compat_file_meta_loaded = 0 | ||
| 832 | |||
| 833 | local function load_compat_file_meta(file_meta) | ||
| 834 | -- fill compat_file_meta with original entries | ||
| 835 | for k, v in pairs(file_meta) do | ||
| 836 | compat_file_meta[k] = v | ||
| 837 | end | ||
| 838 | compat_file_meta.__index = {} | ||
| 839 | for k, v in pairs(file_meta.__index) do | ||
| 840 | compat_file_meta.__index[k] = v | ||
| 841 | end | ||
| 842 | |||
| 843 | compat_file_meta_loaded = 1 | ||
| 844 | |||
| 845 | -- update it with compatibility functions | ||
| 846 | local file_mt_ok, file_mt = pcall(require, "compat53.file_mt") | ||
| 847 | if file_mt_ok then | ||
| 848 | file_mt.update_file_meta(compat_file_meta, is_luajit52) | ||
| 849 | |||
| 850 | compat_file_meta_loaded = 2 | ||
| 851 | end | ||
| 852 | end | ||
| 853 | |||
| 854 | local function return_fd(fd, err, code) | ||
| 855 | if not fd then | ||
| 856 | return fd, err, code | ||
| 857 | end | ||
| 858 | if fd and debug_setmetatable then | ||
| 859 | if compat_file_meta_loaded == 0 then | ||
| 860 | local file_meta = gmt(fd) | ||
| 861 | load_compat_file_meta(file_meta) | ||
| 862 | end | ||
| 863 | if compat_file_meta_loaded == 2 then | ||
| 864 | debug_setmetatable(fd, compat_file_meta) | ||
| 865 | end | ||
| 866 | end | ||
| 867 | return fd | ||
| 868 | end | ||
| 869 | |||
| 870 | function M.io.open(...) | ||
| 871 | return return_fd(io_open(...)) | ||
| 872 | end | ||
| 873 | |||
| 874 | function M.io.popen(...) | ||
| 875 | return return_fd(io_popen(...)) | ||
| 876 | end | ||
| 877 | |||
| 878 | function M.io.tmpfile(...) | ||
| 879 | return return_fd(io_tmpfile(...)) | ||
| 880 | end | ||
| 881 | end | ||
| 882 | |||
| 883 | end -- lua 5.1 | ||
| 884 | |||
| 885 | -- further write should be forwarded to _G | ||
| 886 | M_meta.__newindex = _G | ||
| 887 | |||
| 888 | end -- lua < 5.3 | ||
| 889 | |||
| 890 | |||
| 891 | -- return module table | ||
| 892 | return M | ||
| 893 | |||
| 894 | -- vi: set expandtab softtabstop=3 shiftwidth=3 : | ||
diff --git a/vendor/dkjson.d.tl b/vendor/dkjson.d.tl new file mode 100644 index 00000000..4245e8aa --- /dev/null +++ b/vendor/dkjson.d.tl | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | |||
| 2 | --[[ | ||
| 3 | - local type json = {string:json} | ||
| 4 | ]] | ||
| 5 | |||
| 6 | local record dkjson | ||
| 7 | record JsonState | ||
| 8 | indent: boolean | ||
| 9 | keyorder: {string} | ||
| 10 | level: number | ||
| 11 | buffer: {string} | ||
| 12 | bufferlen: number | ||
| 13 | tables: {table:boolean} | ||
| 14 | exception: function(string, string, string, string): boolean|string, string | ||
| 15 | end | ||
| 16 | encode: function({string:any}, ?JsonState): string | ||
| 17 | |||
| 18 | decode: function(string, ?number, ?any, ?table): {string:any}, integer, string | ||
| 19 | |||
| 20 | null: table | ||
| 21 | |||
| 22 | version: string | ||
| 23 | |||
| 24 | quotestring: function(string): string | ||
| 25 | |||
| 26 | addnewline: function(JsonState) | ||
| 27 | |||
| 28 | encodeexception: function(string, any, JsonState, string): string | ||
| 29 | |||
| 30 | use_lpeg: function(): dkjson | ||
| 31 | end | ||
| 32 | |||
| 33 | return dkjson | ||
diff --git a/vendor/dkjson.lua b/vendor/dkjson.lua new file mode 100644 index 00000000..7a867241 --- /dev/null +++ b/vendor/dkjson.lua | |||
| @@ -0,0 +1,749 @@ | |||
| 1 | -- Module options: | ||
| 2 | local always_use_lpeg = false | ||
| 3 | local register_global_module_table = false | ||
| 4 | local global_module_name = 'json' | ||
| 5 | |||
| 6 | --[==[ | ||
| 7 | |||
| 8 | David Kolf's JSON module for Lua 5.1 - 5.4 | ||
| 9 | |||
| 10 | Version 2.7 | ||
| 11 | |||
| 12 | |||
| 13 | For the documentation see the corresponding readme.txt or visit | ||
| 14 | <http://dkolf.de/src/dkjson-lua.fsl/>. | ||
| 15 | |||
| 16 | You can contact the author by sending an e-mail to 'david' at the | ||
| 17 | domain 'dkolf.de'. | ||
| 18 | |||
| 19 | |||
| 20 | Copyright (C) 2010-2024 David Heiko Kolf | ||
| 21 | |||
| 22 | Permission is hereby granted, free of charge, to any person obtaining | ||
| 23 | a copy of this software and associated documentation files (the | ||
| 24 | "Software"), to deal in the Software without restriction, including | ||
| 25 | without limitation the rights to use, copy, modify, merge, publish, | ||
| 26 | distribute, sublicense, and/or sell copies of the Software, and to | ||
| 27 | permit persons to whom the Software is furnished to do so, subject to | ||
| 28 | the following conditions: | ||
| 29 | |||
| 30 | The above copyright notice and this permission notice shall be | ||
| 31 | included in all copies or substantial portions of the Software. | ||
| 32 | |||
| 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
| 34 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
| 35 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
| 36 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
| 37 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
| 38 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
| 39 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 40 | SOFTWARE. | ||
| 41 | |||
| 42 | --]==] | ||
| 43 | |||
| 44 | -- global dependencies: | ||
| 45 | local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = | ||
| 46 | pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset | ||
| 47 | local error, require, pcall, select = error, require, pcall, select | ||
| 48 | local floor, huge = math.floor, math.huge | ||
| 49 | local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = | ||
| 50 | string.rep, string.gsub, string.sub, string.byte, string.char, | ||
| 51 | string.find, string.len, string.format | ||
| 52 | local strmatch = string.match | ||
| 53 | local concat = table.concat | ||
| 54 | |||
| 55 | local json = { version = "dkjson 2.7" } | ||
| 56 | |||
| 57 | local jsonlpeg = {} | ||
| 58 | |||
| 59 | if register_global_module_table then | ||
| 60 | if always_use_lpeg then | ||
| 61 | _G[global_module_name] = jsonlpeg | ||
| 62 | else | ||
| 63 | _G[global_module_name] = json | ||
| 64 | end | ||
| 65 | end | ||
| 66 | |||
| 67 | local _ENV = nil -- blocking globals in Lua 5.2 and later | ||
| 68 | |||
| 69 | pcall (function() | ||
| 70 | -- Enable access to blocked metatables. | ||
| 71 | -- Don't worry, this module doesn't change anything in them. | ||
| 72 | local debmeta = require "debug".getmetatable | ||
| 73 | if debmeta then getmetatable = debmeta end | ||
| 74 | end) | ||
| 75 | |||
| 76 | json.null = setmetatable ({}, { | ||
| 77 | __tojson = function () return "null" end | ||
| 78 | }) | ||
| 79 | |||
| 80 | local function isarray (tbl) | ||
| 81 | local max, n, arraylen = 0, 0, 0 | ||
| 82 | for k,v in pairs (tbl) do | ||
| 83 | if k == 'n' and type(v) == 'number' then | ||
| 84 | arraylen = v | ||
| 85 | if v > max then | ||
| 86 | max = v | ||
| 87 | end | ||
| 88 | else | ||
| 89 | if type(k) ~= 'number' or k < 1 or floor(k) ~= k then | ||
| 90 | return false | ||
| 91 | end | ||
| 92 | if k > max then | ||
| 93 | max = k | ||
| 94 | end | ||
| 95 | n = n + 1 | ||
| 96 | end | ||
| 97 | end | ||
| 98 | if max > 10 and max > arraylen and max > n * 2 then | ||
| 99 | return false -- don't create an array with too many holes | ||
| 100 | end | ||
| 101 | return true, max | ||
| 102 | end | ||
| 103 | |||
| 104 | local escapecodes = { | ||
| 105 | ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", | ||
| 106 | ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" | ||
| 107 | } | ||
| 108 | |||
| 109 | local function escapeutf8 (uchar) | ||
| 110 | local value = escapecodes[uchar] | ||
| 111 | if value then | ||
| 112 | return value | ||
| 113 | end | ||
| 114 | local a, b, c, d = strbyte (uchar, 1, 4) | ||
| 115 | a, b, c, d = a or 0, b or 0, c or 0, d or 0 | ||
| 116 | if a <= 0x7f then | ||
| 117 | value = a | ||
| 118 | elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then | ||
| 119 | value = (a - 0xc0) * 0x40 + b - 0x80 | ||
| 120 | elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then | ||
| 121 | value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 | ||
| 122 | elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then | ||
| 123 | value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 | ||
| 124 | else | ||
| 125 | return "" | ||
| 126 | end | ||
| 127 | if value <= 0xffff then | ||
| 128 | return strformat ("\\u%.4x", value) | ||
| 129 | elseif value <= 0x10ffff then | ||
| 130 | -- encode as UTF-16 surrogate pair | ||
| 131 | value = value - 0x10000 | ||
| 132 | local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) | ||
| 133 | return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) | ||
| 134 | else | ||
| 135 | return "" | ||
| 136 | end | ||
| 137 | end | ||
| 138 | |||
| 139 | local function fsub (str, pattern, repl) | ||
| 140 | -- gsub always builds a new string in a buffer, even when no match | ||
| 141 | -- exists. First using find should be more efficient when most strings | ||
| 142 | -- don't contain the pattern. | ||
| 143 | if strfind (str, pattern) then | ||
| 144 | return gsub (str, pattern, repl) | ||
| 145 | else | ||
| 146 | return str | ||
| 147 | end | ||
| 148 | end | ||
| 149 | |||
| 150 | local function quotestring (value) | ||
| 151 | -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js | ||
| 152 | value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) | ||
| 153 | if strfind (value, "[\194\216\220\225\226\239]") then | ||
| 154 | value = fsub (value, "\194[\128-\159\173]", escapeutf8) | ||
| 155 | value = fsub (value, "\216[\128-\132]", escapeutf8) | ||
| 156 | value = fsub (value, "\220\143", escapeutf8) | ||
| 157 | value = fsub (value, "\225\158[\180\181]", escapeutf8) | ||
| 158 | value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) | ||
| 159 | value = fsub (value, "\226\129[\160-\175]", escapeutf8) | ||
| 160 | value = fsub (value, "\239\187\191", escapeutf8) | ||
| 161 | value = fsub (value, "\239\191[\176-\191]", escapeutf8) | ||
| 162 | end | ||
| 163 | return "\"" .. value .. "\"" | ||
| 164 | end | ||
| 165 | json.quotestring = quotestring | ||
| 166 | |||
| 167 | local function replace(str, o, n) | ||
| 168 | local i, j = strfind (str, o, 1, true) | ||
| 169 | if i then | ||
| 170 | return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) | ||
| 171 | else | ||
| 172 | return str | ||
| 173 | end | ||
| 174 | end | ||
| 175 | |||
| 176 | -- locale independent num2str and str2num functions | ||
| 177 | local decpoint, numfilter | ||
| 178 | |||
| 179 | local function updatedecpoint () | ||
| 180 | decpoint = strmatch(tostring(0.5), "([^05+])") | ||
| 181 | -- build a filter that can be used to remove group separators | ||
| 182 | numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" | ||
| 183 | end | ||
| 184 | |||
| 185 | updatedecpoint() | ||
| 186 | |||
| 187 | local function num2str (num) | ||
| 188 | return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") | ||
| 189 | end | ||
| 190 | |||
| 191 | local function str2num (str) | ||
| 192 | local num = tonumber(replace(str, ".", decpoint)) | ||
| 193 | if not num then | ||
| 194 | updatedecpoint() | ||
| 195 | num = tonumber(replace(str, ".", decpoint)) | ||
| 196 | end | ||
| 197 | return num | ||
| 198 | end | ||
| 199 | |||
| 200 | local function addnewline2 (level, buffer, buflen) | ||
| 201 | buffer[buflen+1] = "\n" | ||
| 202 | buffer[buflen+2] = strrep (" ", level) | ||
| 203 | buflen = buflen + 2 | ||
| 204 | return buflen | ||
| 205 | end | ||
| 206 | |||
| 207 | function json.addnewline (state) | ||
| 208 | if state.indent then | ||
| 209 | state.bufferlen = addnewline2 (state.level or 0, | ||
| 210 | state.buffer, state.bufferlen or #(state.buffer)) | ||
| 211 | end | ||
| 212 | end | ||
| 213 | |||
| 214 | local encode2 -- forward declaration | ||
| 215 | |||
| 216 | local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) | ||
| 217 | local kt = type (key) | ||
| 218 | if kt ~= 'string' and kt ~= 'number' then | ||
| 219 | return nil, "type '" .. kt .. "' is not supported as a key by JSON." | ||
| 220 | end | ||
| 221 | if prev then | ||
| 222 | buflen = buflen + 1 | ||
| 223 | buffer[buflen] = "," | ||
| 224 | end | ||
| 225 | if indent then | ||
| 226 | buflen = addnewline2 (level, buffer, buflen) | ||
| 227 | end | ||
| 228 | buffer[buflen+1] = quotestring (key) | ||
| 229 | buffer[buflen+2] = ":" | ||
| 230 | return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) | ||
| 231 | end | ||
| 232 | |||
| 233 | local function appendcustom(res, buffer, state) | ||
| 234 | local buflen = state.bufferlen | ||
| 235 | if type (res) == 'string' then | ||
| 236 | buflen = buflen + 1 | ||
| 237 | buffer[buflen] = res | ||
| 238 | end | ||
| 239 | return buflen | ||
| 240 | end | ||
| 241 | |||
| 242 | local function exception(reason, value, state, buffer, buflen, defaultmessage) | ||
| 243 | defaultmessage = defaultmessage or reason | ||
| 244 | local handler = state.exception | ||
| 245 | if not handler then | ||
| 246 | return nil, defaultmessage | ||
| 247 | else | ||
| 248 | state.bufferlen = buflen | ||
| 249 | local ret, msg = handler (reason, value, state, defaultmessage) | ||
| 250 | if not ret then return nil, msg or defaultmessage end | ||
| 251 | return appendcustom(ret, buffer, state) | ||
| 252 | end | ||
| 253 | end | ||
| 254 | |||
| 255 | function json.encodeexception(reason, value, state, defaultmessage) | ||
| 256 | return quotestring("<" .. defaultmessage .. ">") | ||
| 257 | end | ||
| 258 | |||
| 259 | encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) | ||
| 260 | local valtype = type (value) | ||
| 261 | local valmeta = getmetatable (value) | ||
| 262 | valmeta = type (valmeta) == 'table' and valmeta -- only tables | ||
| 263 | local valtojson = valmeta and valmeta.__tojson | ||
| 264 | if valtojson then | ||
| 265 | if tables[value] then | ||
| 266 | return exception('reference cycle', value, state, buffer, buflen) | ||
| 267 | end | ||
| 268 | tables[value] = true | ||
| 269 | state.bufferlen = buflen | ||
| 270 | local ret, msg = valtojson (value, state) | ||
| 271 | if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end | ||
| 272 | tables[value] = nil | ||
| 273 | buflen = appendcustom(ret, buffer, state) | ||
| 274 | elseif value == nil then | ||
| 275 | buflen = buflen + 1 | ||
| 276 | buffer[buflen] = "null" | ||
| 277 | elseif valtype == 'number' then | ||
| 278 | local s | ||
| 279 | if value ~= value or value >= huge or -value >= huge then | ||
| 280 | -- This is the behaviour of the original JSON implementation. | ||
| 281 | s = "null" | ||
| 282 | else | ||
| 283 | s = num2str (value) | ||
| 284 | end | ||
| 285 | buflen = buflen + 1 | ||
| 286 | buffer[buflen] = s | ||
| 287 | elseif valtype == 'boolean' then | ||
| 288 | buflen = buflen + 1 | ||
| 289 | buffer[buflen] = value and "true" or "false" | ||
| 290 | elseif valtype == 'string' then | ||
| 291 | buflen = buflen + 1 | ||
| 292 | buffer[buflen] = quotestring (value) | ||
| 293 | elseif valtype == 'table' then | ||
| 294 | if tables[value] then | ||
| 295 | return exception('reference cycle', value, state, buffer, buflen) | ||
| 296 | end | ||
| 297 | tables[value] = true | ||
| 298 | level = level + 1 | ||
| 299 | local isa, n = isarray (value) | ||
| 300 | if n == 0 and valmeta and valmeta.__jsontype == 'object' then | ||
| 301 | isa = false | ||
| 302 | end | ||
| 303 | local msg | ||
| 304 | if isa then -- JSON array | ||
| 305 | buflen = buflen + 1 | ||
| 306 | buffer[buflen] = "[" | ||
| 307 | for i = 1, n do | ||
| 308 | buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) | ||
| 309 | if not buflen then return nil, msg end | ||
| 310 | if i < n then | ||
| 311 | buflen = buflen + 1 | ||
| 312 | buffer[buflen] = "," | ||
| 313 | end | ||
| 314 | end | ||
| 315 | buflen = buflen + 1 | ||
| 316 | buffer[buflen] = "]" | ||
| 317 | else -- JSON object | ||
| 318 | local prev = false | ||
| 319 | buflen = buflen + 1 | ||
| 320 | buffer[buflen] = "{" | ||
| 321 | local order = valmeta and valmeta.__jsonorder or globalorder | ||
| 322 | if order then | ||
| 323 | local used = {} | ||
| 324 | n = #order | ||
| 325 | for i = 1, n do | ||
| 326 | local k = order[i] | ||
| 327 | local v = value[k] | ||
| 328 | if v ~= nil then | ||
| 329 | used[k] = true | ||
| 330 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) | ||
| 331 | if not buflen then return nil, msg end | ||
| 332 | prev = true -- add a seperator before the next element | ||
| 333 | end | ||
| 334 | end | ||
| 335 | for k,v in pairs (value) do | ||
| 336 | if not used[k] then | ||
| 337 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) | ||
| 338 | if not buflen then return nil, msg end | ||
| 339 | prev = true -- add a seperator before the next element | ||
| 340 | end | ||
| 341 | end | ||
| 342 | else -- unordered | ||
| 343 | for k,v in pairs (value) do | ||
| 344 | buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) | ||
| 345 | if not buflen then return nil, msg end | ||
| 346 | prev = true -- add a seperator before the next element | ||
| 347 | end | ||
| 348 | end | ||
| 349 | if indent then | ||
| 350 | buflen = addnewline2 (level - 1, buffer, buflen) | ||
| 351 | end | ||
| 352 | buflen = buflen + 1 | ||
| 353 | buffer[buflen] = "}" | ||
| 354 | end | ||
| 355 | tables[value] = nil | ||
| 356 | else | ||
| 357 | return exception ('unsupported type', value, state, buffer, buflen, | ||
| 358 | "type '" .. valtype .. "' is not supported by JSON.") | ||
| 359 | end | ||
| 360 | return buflen | ||
| 361 | end | ||
| 362 | |||
| 363 | function json.encode (value, state) | ||
| 364 | state = state or {} | ||
| 365 | local oldbuffer = state.buffer | ||
| 366 | local buffer = oldbuffer or {} | ||
| 367 | state.buffer = buffer | ||
| 368 | updatedecpoint() | ||
| 369 | local ret, msg = encode2 (value, state.indent, state.level or 0, | ||
| 370 | buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) | ||
| 371 | if not ret then | ||
| 372 | error (msg, 2) | ||
| 373 | elseif oldbuffer == buffer then | ||
| 374 | state.bufferlen = ret | ||
| 375 | return true | ||
| 376 | else | ||
| 377 | state.bufferlen = nil | ||
| 378 | state.buffer = nil | ||
| 379 | return concat (buffer) | ||
| 380 | end | ||
| 381 | end | ||
| 382 | |||
| 383 | local function loc (str, where) | ||
| 384 | local line, pos, linepos = 1, 1, 0 | ||
| 385 | while true do | ||
| 386 | pos = strfind (str, "\n", pos, true) | ||
| 387 | if pos and pos < where then | ||
| 388 | line = line + 1 | ||
| 389 | linepos = pos | ||
| 390 | pos = pos + 1 | ||
| 391 | else | ||
| 392 | break | ||
| 393 | end | ||
| 394 | end | ||
| 395 | return "line " .. line .. ", column " .. (where - linepos) | ||
| 396 | end | ||
| 397 | |||
| 398 | local function unterminated (str, what, where) | ||
| 399 | return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) | ||
| 400 | end | ||
| 401 | |||
| 402 | local function scanwhite (str, pos) | ||
| 403 | while true do | ||
| 404 | pos = strfind (str, "%S", pos) | ||
| 405 | if not pos then return nil end | ||
| 406 | local sub2 = strsub (str, pos, pos + 1) | ||
| 407 | if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then | ||
| 408 | -- UTF-8 Byte Order Mark | ||
| 409 | pos = pos + 3 | ||
| 410 | elseif sub2 == "//" then | ||
| 411 | pos = strfind (str, "[\n\r]", pos + 2) | ||
| 412 | if not pos then return nil end | ||
| 413 | elseif sub2 == "/*" then | ||
| 414 | pos = strfind (str, "*/", pos + 2) | ||
| 415 | if not pos then return nil end | ||
| 416 | pos = pos + 2 | ||
| 417 | else | ||
| 418 | return pos | ||
| 419 | end | ||
| 420 | end | ||
| 421 | end | ||
| 422 | |||
| 423 | local escapechars = { | ||
| 424 | ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", | ||
| 425 | ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" | ||
| 426 | } | ||
| 427 | |||
| 428 | local function unichar (value) | ||
| 429 | if value < 0 then | ||
| 430 | return nil | ||
| 431 | elseif value <= 0x007f then | ||
| 432 | return strchar (value) | ||
| 433 | elseif value <= 0x07ff then | ||
| 434 | return strchar (0xc0 + floor(value/0x40), | ||
| 435 | 0x80 + (floor(value) % 0x40)) | ||
| 436 | elseif value <= 0xffff then | ||
| 437 | return strchar (0xe0 + floor(value/0x1000), | ||
| 438 | 0x80 + (floor(value/0x40) % 0x40), | ||
| 439 | 0x80 + (floor(value) % 0x40)) | ||
| 440 | elseif value <= 0x10ffff then | ||
| 441 | return strchar (0xf0 + floor(value/0x40000), | ||
| 442 | 0x80 + (floor(value/0x1000) % 0x40), | ||
| 443 | 0x80 + (floor(value/0x40) % 0x40), | ||
| 444 | 0x80 + (floor(value) % 0x40)) | ||
| 445 | else | ||
| 446 | return nil | ||
| 447 | end | ||
| 448 | end | ||
| 449 | |||
| 450 | local function scanstring (str, pos) | ||
| 451 | local lastpos = pos + 1 | ||
| 452 | local buffer, n = {}, 0 | ||
| 453 | while true do | ||
| 454 | local nextpos = strfind (str, "[\"\\]", lastpos) | ||
| 455 | if not nextpos then | ||
| 456 | return unterminated (str, "string", pos) | ||
| 457 | end | ||
| 458 | if nextpos > lastpos then | ||
| 459 | n = n + 1 | ||
| 460 | buffer[n] = strsub (str, lastpos, nextpos - 1) | ||
| 461 | end | ||
| 462 | if strsub (str, nextpos, nextpos) == "\"" then | ||
| 463 | lastpos = nextpos + 1 | ||
| 464 | break | ||
| 465 | else | ||
| 466 | local escchar = strsub (str, nextpos + 1, nextpos + 1) | ||
| 467 | local value | ||
| 468 | if escchar == "u" then | ||
| 469 | value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) | ||
| 470 | if value then | ||
| 471 | local value2 | ||
| 472 | if 0xD800 <= value and value <= 0xDBff then | ||
| 473 | -- we have the high surrogate of UTF-16. Check if there is a | ||
| 474 | -- low surrogate escaped nearby to combine them. | ||
| 475 | if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then | ||
| 476 | value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) | ||
| 477 | if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then | ||
| 478 | value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 | ||
| 479 | else | ||
| 480 | value2 = nil -- in case it was out of range for a low surrogate | ||
| 481 | end | ||
| 482 | end | ||
| 483 | end | ||
| 484 | value = value and unichar (value) | ||
| 485 | if value then | ||
| 486 | if value2 then | ||
| 487 | lastpos = nextpos + 12 | ||
| 488 | else | ||
| 489 | lastpos = nextpos + 6 | ||
| 490 | end | ||
| 491 | end | ||
| 492 | end | ||
| 493 | end | ||
| 494 | if not value then | ||
| 495 | value = escapechars[escchar] or escchar | ||
| 496 | lastpos = nextpos + 2 | ||
| 497 | end | ||
| 498 | n = n + 1 | ||
| 499 | buffer[n] = value | ||
| 500 | end | ||
| 501 | end | ||
| 502 | if n == 1 then | ||
| 503 | return buffer[1], lastpos | ||
| 504 | elseif n > 1 then | ||
| 505 | return concat (buffer), lastpos | ||
| 506 | else | ||
| 507 | return "", lastpos | ||
| 508 | end | ||
| 509 | end | ||
| 510 | |||
| 511 | local scanvalue -- forward declaration | ||
| 512 | |||
| 513 | local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) | ||
| 514 | local len = strlen (str) | ||
| 515 | local tbl, n = {}, 0 | ||
| 516 | local pos = startpos + 1 | ||
| 517 | if what == 'object' then | ||
| 518 | setmetatable (tbl, objectmeta) | ||
| 519 | else | ||
| 520 | setmetatable (tbl, arraymeta) | ||
| 521 | end | ||
| 522 | while true do | ||
| 523 | pos = scanwhite (str, pos) | ||
| 524 | if not pos then return unterminated (str, what, startpos) end | ||
| 525 | local char = strsub (str, pos, pos) | ||
| 526 | if char == closechar then | ||
| 527 | return tbl, pos + 1 | ||
| 528 | end | ||
| 529 | local val1, err | ||
| 530 | val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) | ||
| 531 | if err then return nil, pos, err end | ||
| 532 | pos = scanwhite (str, pos) | ||
| 533 | if not pos then return unterminated (str, what, startpos) end | ||
| 534 | char = strsub (str, pos, pos) | ||
| 535 | if char == ":" then | ||
| 536 | if val1 == nil then | ||
| 537 | return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" | ||
| 538 | end | ||
| 539 | pos = scanwhite (str, pos + 1) | ||
| 540 | if not pos then return unterminated (str, what, startpos) end | ||
| 541 | local val2 | ||
| 542 | val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) | ||
| 543 | if err then return nil, pos, err end | ||
| 544 | tbl[val1] = val2 | ||
| 545 | pos = scanwhite (str, pos) | ||
| 546 | if not pos then return unterminated (str, what, startpos) end | ||
| 547 | char = strsub (str, pos, pos) | ||
| 548 | else | ||
| 549 | n = n + 1 | ||
| 550 | tbl[n] = val1 | ||
| 551 | end | ||
| 552 | if char == "," then | ||
| 553 | pos = pos + 1 | ||
| 554 | end | ||
| 555 | end | ||
| 556 | end | ||
| 557 | |||
| 558 | scanvalue = function (str, pos, nullval, objectmeta, arraymeta) | ||
| 559 | pos = pos or 1 | ||
| 560 | pos = scanwhite (str, pos) | ||
| 561 | if not pos then | ||
| 562 | return nil, strlen (str) + 1, "no valid JSON value (reached the end)" | ||
| 563 | end | ||
| 564 | local char = strsub (str, pos, pos) | ||
| 565 | if char == "{" then | ||
| 566 | return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) | ||
| 567 | elseif char == "[" then | ||
| 568 | return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) | ||
| 569 | elseif char == "\"" then | ||
| 570 | return scanstring (str, pos) | ||
| 571 | else | ||
| 572 | local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) | ||
| 573 | if pstart then | ||
| 574 | local number = str2num (strsub (str, pstart, pend)) | ||
| 575 | if number then | ||
| 576 | return number, pend + 1 | ||
| 577 | end | ||
| 578 | end | ||
| 579 | pstart, pend = strfind (str, "^%a%w*", pos) | ||
| 580 | if pstart then | ||
| 581 | local name = strsub (str, pstart, pend) | ||
| 582 | if name == "true" then | ||
| 583 | return true, pend + 1 | ||
| 584 | elseif name == "false" then | ||
| 585 | return false, pend + 1 | ||
| 586 | elseif name == "null" then | ||
| 587 | return nullval, pend + 1 | ||
| 588 | end | ||
| 589 | end | ||
| 590 | return nil, pos, "no valid JSON value at " .. loc (str, pos) | ||
| 591 | end | ||
| 592 | end | ||
| 593 | |||
| 594 | local function optionalmetatables(...) | ||
| 595 | if select("#", ...) > 0 then | ||
| 596 | return ... | ||
| 597 | else | ||
| 598 | return {__jsontype = 'object'}, {__jsontype = 'array'} | ||
| 599 | end | ||
| 600 | end | ||
| 601 | |||
| 602 | function json.decode (str, pos, nullval, ...) | ||
| 603 | local objectmeta, arraymeta = optionalmetatables(...) | ||
| 604 | return scanvalue (str, pos, nullval, objectmeta, arraymeta) | ||
| 605 | end | ||
| 606 | |||
| 607 | function json.use_lpeg () | ||
| 608 | local g = require ("lpeg") | ||
| 609 | |||
| 610 | if type(g.version) == 'function' and g.version() == "0.11" then | ||
| 611 | error "due to a bug in LPeg 0.11, it cannot be used for JSON matching" | ||
| 612 | end | ||
| 613 | |||
| 614 | local pegmatch = g.match | ||
| 615 | local P, S, R = g.P, g.S, g.R | ||
| 616 | |||
| 617 | local function ErrorCall (str, pos, msg, state) | ||
| 618 | if not state.msg then | ||
| 619 | state.msg = msg .. " at " .. loc (str, pos) | ||
| 620 | state.pos = pos | ||
| 621 | end | ||
| 622 | return false | ||
| 623 | end | ||
| 624 | |||
| 625 | local function Err (msg) | ||
| 626 | return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) | ||
| 627 | end | ||
| 628 | |||
| 629 | local function ErrorUnterminatedCall (str, pos, what, state) | ||
| 630 | return ErrorCall (str, pos - 1, "unterminated " .. what, state) | ||
| 631 | end | ||
| 632 | |||
| 633 | local SingleLineComment = P"//" * (1 - S"\n\r")^0 | ||
| 634 | local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/" | ||
| 635 | local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0 | ||
| 636 | |||
| 637 | local function ErrUnterminated (what) | ||
| 638 | return g.Cmt (g.Cc (what) * g.Carg (2), ErrorUnterminatedCall) | ||
| 639 | end | ||
| 640 | |||
| 641 | local PlainChar = 1 - S"\"\\\n\r" | ||
| 642 | local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars | ||
| 643 | local HexDigit = R("09", "af", "AF") | ||
| 644 | local function UTF16Surrogate (match, pos, high, low) | ||
| 645 | high, low = tonumber (high, 16), tonumber (low, 16) | ||
| 646 | if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then | ||
| 647 | return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) | ||
| 648 | else | ||
| 649 | return false | ||
| 650 | end | ||
| 651 | end | ||
| 652 | local function UTF16BMP (hex) | ||
| 653 | return unichar (tonumber (hex, 16)) | ||
| 654 | end | ||
| 655 | local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) | ||
| 656 | local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP | ||
| 657 | local Char = UnicodeEscape + EscapeSequence + PlainChar | ||
| 658 | local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string") | ||
| 659 | local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) | ||
| 660 | local Fractal = P"." * R"09"^0 | ||
| 661 | local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 | ||
| 662 | local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num | ||
| 663 | local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) | ||
| 664 | local SimpleValue = Number + String + Constant | ||
| 665 | local ArrayContent, ObjectContent | ||
| 666 | |||
| 667 | -- The functions parsearray and parseobject parse only a single value/pair | ||
| 668 | -- at a time and store them directly to avoid hitting the LPeg limits. | ||
| 669 | local function parsearray (str, pos, nullval, state) | ||
| 670 | local obj, cont | ||
| 671 | local start = pos | ||
| 672 | local npos | ||
| 673 | local t, nt = {}, 0 | ||
| 674 | repeat | ||
| 675 | obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) | ||
| 676 | if cont == 'end' then | ||
| 677 | return ErrorUnterminatedCall (str, start, "array", state) | ||
| 678 | end | ||
| 679 | pos = npos | ||
| 680 | if cont == 'cont' or cont == 'last' then | ||
| 681 | nt = nt + 1 | ||
| 682 | t[nt] = obj | ||
| 683 | end | ||
| 684 | until cont ~= 'cont' | ||
| 685 | return pos, setmetatable (t, state.arraymeta) | ||
| 686 | end | ||
| 687 | |||
| 688 | local function parseobject (str, pos, nullval, state) | ||
| 689 | local obj, key, cont | ||
| 690 | local start = pos | ||
| 691 | local npos | ||
| 692 | local t = {} | ||
| 693 | repeat | ||
| 694 | key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) | ||
| 695 | if cont == 'end' then | ||
| 696 | return ErrorUnterminatedCall (str, start, "object", state) | ||
| 697 | end | ||
| 698 | pos = npos | ||
| 699 | if cont == 'cont' or cont == 'last' then | ||
| 700 | t[key] = obj | ||
| 701 | end | ||
| 702 | until cont ~= 'cont' | ||
| 703 | return pos, setmetatable (t, state.objectmeta) | ||
| 704 | end | ||
| 705 | |||
| 706 | local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) | ||
| 707 | local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) | ||
| 708 | local Value = Space * (Array + Object + SimpleValue) | ||
| 709 | local ExpectedValue = Value + Space * Err "value expected" | ||
| 710 | local ExpectedKey = String + Err "key expected" | ||
| 711 | local End = P(-1) * g.Cc'end' | ||
| 712 | local ErrInvalid = Err "invalid JSON" | ||
| 713 | ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp() | ||
| 714 | local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue) | ||
| 715 | 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() | ||
| 716 | local DecodeValue = ExpectedValue * g.Cp () | ||
| 717 | |||
| 718 | jsonlpeg.version = json.version | ||
| 719 | jsonlpeg.encode = json.encode | ||
| 720 | jsonlpeg.null = json.null | ||
| 721 | jsonlpeg.quotestring = json.quotestring | ||
| 722 | jsonlpeg.addnewline = json.addnewline | ||
| 723 | jsonlpeg.encodeexception = json.encodeexception | ||
| 724 | jsonlpeg.using_lpeg = true | ||
| 725 | |||
| 726 | function jsonlpeg.decode (str, pos, nullval, ...) | ||
| 727 | local state = {} | ||
| 728 | state.objectmeta, state.arraymeta = optionalmetatables(...) | ||
| 729 | local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) | ||
| 730 | if state.msg then | ||
| 731 | return nil, state.pos, state.msg | ||
| 732 | else | ||
| 733 | return obj, retpos | ||
| 734 | end | ||
| 735 | end | ||
| 736 | |||
| 737 | -- cache result of this function: | ||
| 738 | json.use_lpeg = function () return jsonlpeg end | ||
| 739 | jsonlpeg.use_lpeg = json.use_lpeg | ||
| 740 | |||
| 741 | return jsonlpeg | ||
| 742 | end | ||
| 743 | |||
| 744 | if always_use_lpeg then | ||
| 745 | return json.use_lpeg() | ||
| 746 | end | ||
| 747 | |||
| 748 | return json | ||
| 749 | |||
