diff options
| author | Paul Ouellette <oue.paul18@gmail.com> | 2019-07-02 00:25:13 -0400 |
|---|---|---|
| committer | Paul Ouellette <oue.paul18@gmail.com> | 2019-08-01 23:53:03 -0400 |
| commit | 9ca43ac0e8864ac3098c95b56b9e45bc10a4709d (patch) | |
| tree | f8cf3ceae26a2ad179da790c9f08e7f5553c83c7 /src | |
| parent | 0d0c705f6b808b9ab819c19c4d8bbb5a850e9e13 (diff) | |
| download | luarocks-9ca43ac0e8864ac3098c95b56b9e45bc10a4709d.tar.gz luarocks-9ca43ac0e8864ac3098c95b56b9e45bc10a4709d.tar.bz2 luarocks-9ca43ac0e8864ac3098c95b56b9e45bc10a4709d.zip | |
Add argparse
Diffstat (limited to 'src')
| -rw-r--r-- | src/luarocks/argparse.lua | 2013 | ||||
| -rw-r--r-- | src/luarocks/cmd.lua | 2 |
2 files changed, 2014 insertions, 1 deletions
diff --git a/src/luarocks/argparse.lua b/src/luarocks/argparse.lua new file mode 100644 index 00000000..a91da957 --- /dev/null +++ b/src/luarocks/argparse.lua | |||
| @@ -0,0 +1,2013 @@ | |||
| 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 | end | ||
| 134 | |||
| 135 | -- Do not set _name as with other properties. | ||
| 136 | return true | ||
| 137 | end} | ||
| 138 | |||
| 139 | local function parse_boundaries(str) | ||
| 140 | if tonumber(str) then | ||
| 141 | return tonumber(str), tonumber(str) | ||
| 142 | end | ||
| 143 | |||
| 144 | if str == "*" then | ||
| 145 | return 0, math.huge | ||
| 146 | end | ||
| 147 | |||
| 148 | if str == "+" then | ||
| 149 | return 1, math.huge | ||
| 150 | end | ||
| 151 | |||
| 152 | if str == "?" then | ||
| 153 | return 0, 1 | ||
| 154 | end | ||
| 155 | |||
| 156 | if str:match "^%d+%-%d+$" then | ||
| 157 | local min, max = str:match "^(%d+)%-(%d+)$" | ||
| 158 | return tonumber(min), tonumber(max) | ||
| 159 | end | ||
| 160 | |||
| 161 | if str:match "^%d+%+$" then | ||
| 162 | local min = str:match "^(%d+)%+$" | ||
| 163 | return tonumber(min), math.huge | ||
| 164 | end | ||
| 165 | end | ||
| 166 | |||
| 167 | local function boundaries(name) | ||
| 168 | return {name, function(self, value) | ||
| 169 | typecheck(name, {"number", "string"}, value) | ||
| 170 | |||
| 171 | local min, max = parse_boundaries(value) | ||
| 172 | |||
| 173 | if not min then | ||
| 174 | error(("bad property '%s'"):format(name)) | ||
| 175 | end | ||
| 176 | |||
| 177 | self["_min" .. name], self["_max" .. name] = min, max | ||
| 178 | end} | ||
| 179 | end | ||
| 180 | |||
| 181 | local actions = {} | ||
| 182 | |||
| 183 | local option_action = {"action", function(_, value) | ||
| 184 | typecheck("action", {"function", "string"}, value) | ||
| 185 | |||
| 186 | if type(value) == "string" and not actions[value] then | ||
| 187 | error(("unknown action '%s'"):format(value)) | ||
| 188 | end | ||
| 189 | end} | ||
| 190 | |||
| 191 | local option_init = {"init", function(self) | ||
| 192 | self._has_init = true | ||
| 193 | end} | ||
| 194 | |||
| 195 | local option_default = {"default", function(self, value) | ||
| 196 | if type(value) ~= "string" then | ||
| 197 | self._init = value | ||
| 198 | self._has_init = true | ||
| 199 | return true | ||
| 200 | end | ||
| 201 | end} | ||
| 202 | |||
| 203 | local add_help = {"add_help", function(self, value) | ||
| 204 | typecheck("add_help", {"boolean", "string", "table"}, value) | ||
| 205 | |||
| 206 | if self._help_option_idx then | ||
| 207 | table.remove(self._options, self._help_option_idx) | ||
| 208 | self._help_option_idx = nil | ||
| 209 | end | ||
| 210 | |||
| 211 | if value then | ||
| 212 | local help = self:flag() | ||
| 213 | :description "Show this help message and exit." | ||
| 214 | :action(function() | ||
| 215 | print(self:get_help()) | ||
| 216 | os.exit(0) | ||
| 217 | end) | ||
| 218 | |||
| 219 | if value ~= true then | ||
| 220 | help = help(value) | ||
| 221 | end | ||
| 222 | |||
| 223 | if not help._name then | ||
| 224 | help "-h" "--help" | ||
| 225 | end | ||
| 226 | |||
| 227 | self._help_option_idx = #self._options | ||
| 228 | end | ||
| 229 | end} | ||
| 230 | |||
| 231 | local Parser = class({ | ||
| 232 | _arguments = {}, | ||
| 233 | _options = {}, | ||
| 234 | _commands = {}, | ||
| 235 | _mutexes = {}, | ||
| 236 | _groups = {}, | ||
| 237 | _require_command = true, | ||
| 238 | _handle_options = true | ||
| 239 | }, { | ||
| 240 | args = 3, | ||
| 241 | typechecked("name", "string"), | ||
| 242 | typechecked("description", "string"), | ||
| 243 | typechecked("epilog", "string"), | ||
| 244 | typechecked("usage", "string"), | ||
| 245 | typechecked("help", "string"), | ||
| 246 | typechecked("require_command", "boolean"), | ||
| 247 | typechecked("handle_options", "boolean"), | ||
| 248 | typechecked("action", "function"), | ||
| 249 | typechecked("command_target", "string"), | ||
| 250 | typechecked("help_vertical_space", "number"), | ||
| 251 | typechecked("usage_margin", "number"), | ||
| 252 | typechecked("usage_max_width", "number"), | ||
| 253 | typechecked("help_usage_margin", "number"), | ||
| 254 | typechecked("help_description_margin", "number"), | ||
| 255 | typechecked("help_max_width", "number"), | ||
| 256 | add_help | ||
| 257 | }) | ||
| 258 | |||
| 259 | local Command = class({ | ||
| 260 | _aliases = {} | ||
| 261 | }, { | ||
| 262 | args = 3, | ||
| 263 | multiname, | ||
| 264 | typechecked("description", "string"), | ||
| 265 | typechecked("epilog", "string"), | ||
| 266 | typechecked("summary", "string"), | ||
| 267 | typechecked("target", "string"), | ||
| 268 | typechecked("usage", "string"), | ||
| 269 | typechecked("help", "string"), | ||
| 270 | typechecked("require_command", "boolean"), | ||
| 271 | typechecked("handle_options", "boolean"), | ||
| 272 | typechecked("action", "function"), | ||
| 273 | typechecked("command_target", "string"), | ||
| 274 | typechecked("help_vertical_space", "number"), | ||
| 275 | typechecked("usage_margin", "number"), | ||
| 276 | typechecked("usage_max_width", "number"), | ||
| 277 | typechecked("help_usage_margin", "number"), | ||
| 278 | typechecked("help_description_margin", "number"), | ||
| 279 | typechecked("help_max_width", "number"), | ||
| 280 | typechecked("hidden", "boolean"), | ||
| 281 | add_help | ||
| 282 | }, Parser) | ||
| 283 | |||
| 284 | local Argument = class({ | ||
| 285 | _minargs = 1, | ||
| 286 | _maxargs = 1, | ||
| 287 | _mincount = 1, | ||
| 288 | _maxcount = 1, | ||
| 289 | _defmode = "unused", | ||
| 290 | _show_default = true | ||
| 291 | }, { | ||
| 292 | args = 5, | ||
| 293 | typechecked("name", "string"), | ||
| 294 | typechecked("description", "string"), | ||
| 295 | option_default, | ||
| 296 | typechecked("convert", "function", "table"), | ||
| 297 | boundaries("args"), | ||
| 298 | typechecked("target", "string"), | ||
| 299 | typechecked("defmode", "string"), | ||
| 300 | typechecked("show_default", "boolean"), | ||
| 301 | typechecked("argname", "string", "table"), | ||
| 302 | typechecked("choices", "table"), | ||
| 303 | typechecked("hidden", "boolean"), | ||
| 304 | option_action, | ||
| 305 | option_init | ||
| 306 | }) | ||
| 307 | |||
| 308 | local Option = class({ | ||
| 309 | _aliases = {}, | ||
| 310 | _mincount = 0, | ||
| 311 | _overwrite = true | ||
| 312 | }, { | ||
| 313 | args = 6, | ||
| 314 | multiname, | ||
| 315 | typechecked("description", "string"), | ||
| 316 | option_default, | ||
| 317 | typechecked("convert", "function", "table"), | ||
| 318 | boundaries("args"), | ||
| 319 | boundaries("count"), | ||
| 320 | typechecked("target", "string"), | ||
| 321 | typechecked("defmode", "string"), | ||
| 322 | typechecked("show_default", "boolean"), | ||
| 323 | typechecked("overwrite", "boolean"), | ||
| 324 | typechecked("argname", "string", "table"), | ||
| 325 | typechecked("choices", "table"), | ||
| 326 | typechecked("hidden", "boolean"), | ||
| 327 | option_action, | ||
| 328 | option_init | ||
| 329 | }, Argument) | ||
| 330 | |||
| 331 | function Parser:_inherit_property(name, default) | ||
| 332 | local element = self | ||
| 333 | |||
| 334 | while true do | ||
| 335 | local value = element["_" .. name] | ||
| 336 | |||
| 337 | if value ~= nil then | ||
| 338 | return value | ||
| 339 | end | ||
| 340 | |||
| 341 | if not element._parent then | ||
| 342 | return default | ||
| 343 | end | ||
| 344 | |||
| 345 | element = element._parent | ||
| 346 | end | ||
| 347 | end | ||
| 348 | |||
| 349 | function Argument:_get_argument_list() | ||
| 350 | local buf = {} | ||
| 351 | local i = 1 | ||
| 352 | |||
| 353 | while i <= math.min(self._minargs, 3) do | ||
| 354 | local argname = self:_get_argname(i) | ||
| 355 | |||
| 356 | if self._default and self._defmode:find "a" then | ||
| 357 | argname = "[" .. argname .. "]" | ||
| 358 | end | ||
| 359 | |||
| 360 | table.insert(buf, argname) | ||
| 361 | i = i+1 | ||
| 362 | end | ||
| 363 | |||
| 364 | while i <= math.min(self._maxargs, 3) do | ||
| 365 | table.insert(buf, "[" .. self:_get_argname(i) .. "]") | ||
| 366 | i = i+1 | ||
| 367 | |||
| 368 | if self._maxargs == math.huge then | ||
| 369 | break | ||
| 370 | end | ||
| 371 | end | ||
| 372 | |||
| 373 | if i < self._maxargs then | ||
| 374 | table.insert(buf, "...") | ||
| 375 | end | ||
| 376 | |||
| 377 | return buf | ||
| 378 | end | ||
| 379 | |||
| 380 | function Argument:_get_usage() | ||
| 381 | local usage = table.concat(self:_get_argument_list(), " ") | ||
| 382 | |||
| 383 | if self._default and self._defmode:find "u" then | ||
| 384 | if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then | ||
| 385 | usage = "[" .. usage .. "]" | ||
| 386 | end | ||
| 387 | end | ||
| 388 | |||
| 389 | return usage | ||
| 390 | end | ||
| 391 | |||
| 392 | function actions.store_true(result, target) | ||
| 393 | result[target] = true | ||
| 394 | end | ||
| 395 | |||
| 396 | function actions.store_false(result, target) | ||
| 397 | result[target] = false | ||
| 398 | end | ||
| 399 | |||
| 400 | function actions.store(result, target, argument) | ||
| 401 | result[target] = argument | ||
| 402 | end | ||
| 403 | |||
| 404 | function actions.count(result, target, _, overwrite) | ||
| 405 | if not overwrite then | ||
| 406 | result[target] = result[target] + 1 | ||
| 407 | end | ||
| 408 | end | ||
| 409 | |||
| 410 | function actions.append(result, target, argument, overwrite) | ||
| 411 | result[target] = result[target] or {} | ||
| 412 | table.insert(result[target], argument) | ||
| 413 | |||
| 414 | if overwrite then | ||
| 415 | table.remove(result[target], 1) | ||
| 416 | end | ||
| 417 | end | ||
| 418 | |||
| 419 | function actions.concat(result, target, arguments, overwrite) | ||
| 420 | if overwrite then | ||
| 421 | error("'concat' action can't handle too many invocations") | ||
| 422 | end | ||
| 423 | |||
| 424 | result[target] = result[target] or {} | ||
| 425 | |||
| 426 | for _, argument in ipairs(arguments) do | ||
| 427 | table.insert(result[target], argument) | ||
| 428 | end | ||
| 429 | end | ||
| 430 | |||
| 431 | function Argument:_get_action() | ||
| 432 | local action, init | ||
| 433 | |||
| 434 | if self._maxcount == 1 then | ||
| 435 | if self._maxargs == 0 then | ||
| 436 | action, init = "store_true", nil | ||
| 437 | else | ||
| 438 | action, init = "store", nil | ||
| 439 | end | ||
| 440 | else | ||
| 441 | if self._maxargs == 0 then | ||
| 442 | action, init = "count", 0 | ||
| 443 | else | ||
| 444 | action, init = "append", {} | ||
| 445 | end | ||
| 446 | end | ||
| 447 | |||
| 448 | if self._action then | ||
| 449 | action = self._action | ||
| 450 | end | ||
| 451 | |||
| 452 | if self._has_init then | ||
| 453 | init = self._init | ||
| 454 | end | ||
| 455 | |||
| 456 | if type(action) == "string" then | ||
| 457 | action = actions[action] | ||
| 458 | end | ||
| 459 | |||
| 460 | return action, init | ||
| 461 | end | ||
| 462 | |||
| 463 | -- Returns placeholder for `narg`-th argument. | ||
| 464 | function Argument:_get_argname(narg) | ||
| 465 | local argname = self._argname or self:_get_default_argname() | ||
| 466 | |||
| 467 | if type(argname) == "table" then | ||
| 468 | return argname[narg] | ||
| 469 | else | ||
| 470 | return argname | ||
| 471 | end | ||
| 472 | end | ||
| 473 | |||
| 474 | function Argument:_get_choices_list() | ||
| 475 | return "{" .. table.concat(self._choices, ",") .. "}" | ||
| 476 | end | ||
| 477 | |||
| 478 | function Argument:_get_default_argname() | ||
| 479 | if self._choices then | ||
| 480 | return self:_get_choices_list() | ||
| 481 | else | ||
| 482 | return "<" .. self._name .. ">" | ||
| 483 | end | ||
| 484 | end | ||
| 485 | |||
| 486 | function Option:_get_default_argname() | ||
| 487 | if self._choices then | ||
| 488 | return self:_get_choices_list() | ||
| 489 | else | ||
| 490 | return "<" .. self:_get_default_target() .. ">" | ||
| 491 | end | ||
| 492 | end | ||
| 493 | |||
| 494 | -- Returns labels to be shown in the help message. | ||
| 495 | function Argument:_get_label_lines() | ||
| 496 | if self._choices then | ||
| 497 | return {self:_get_choices_list()} | ||
| 498 | else | ||
| 499 | return {self._name} | ||
| 500 | end | ||
| 501 | end | ||
| 502 | |||
| 503 | function Option:_get_label_lines() | ||
| 504 | local argument_list = self:_get_argument_list() | ||
| 505 | |||
| 506 | if #argument_list == 0 then | ||
| 507 | -- Don't put aliases for simple flags like `-h` on different lines. | ||
| 508 | return {table.concat(self._aliases, ", ")} | ||
| 509 | end | ||
| 510 | |||
| 511 | local longest_alias_length = -1 | ||
| 512 | |||
| 513 | for _, alias in ipairs(self._aliases) do | ||
| 514 | longest_alias_length = math.max(longest_alias_length, #alias) | ||
| 515 | end | ||
| 516 | |||
| 517 | local argument_list_repr = table.concat(argument_list, " ") | ||
| 518 | local lines = {} | ||
| 519 | |||
| 520 | for i, alias in ipairs(self._aliases) do | ||
| 521 | local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr | ||
| 522 | |||
| 523 | if i ~= #self._aliases then | ||
| 524 | line = line .. "," | ||
| 525 | end | ||
| 526 | |||
| 527 | table.insert(lines, line) | ||
| 528 | end | ||
| 529 | |||
| 530 | return lines | ||
| 531 | end | ||
| 532 | |||
| 533 | function Command:_get_label_lines() | ||
| 534 | return {table.concat(self._aliases, ", ")} | ||
| 535 | end | ||
| 536 | |||
| 537 | function Argument:_get_description() | ||
| 538 | if self._default and self._show_default then | ||
| 539 | if self._description then | ||
| 540 | return ("%s (default: %s)"):format(self._description, self._default) | ||
| 541 | else | ||
| 542 | return ("default: %s"):format(self._default) | ||
| 543 | end | ||
| 544 | else | ||
| 545 | return self._description or "" | ||
| 546 | end | ||
| 547 | end | ||
| 548 | |||
| 549 | function Command:_get_description() | ||
| 550 | return self._summary or self._description or "" | ||
| 551 | end | ||
| 552 | |||
| 553 | function Option:_get_usage() | ||
| 554 | local usage = self:_get_argument_list() | ||
| 555 | table.insert(usage, 1, self._name) | ||
| 556 | usage = table.concat(usage, " ") | ||
| 557 | |||
| 558 | if self._mincount == 0 or self._default then | ||
| 559 | usage = "[" .. usage .. "]" | ||
| 560 | end | ||
| 561 | |||
| 562 | return usage | ||
| 563 | end | ||
| 564 | |||
| 565 | function Argument:_get_default_target() | ||
| 566 | return self._name | ||
| 567 | end | ||
| 568 | |||
| 569 | function Option:_get_default_target() | ||
| 570 | local res | ||
| 571 | |||
| 572 | for _, alias in ipairs(self._aliases) do | ||
| 573 | if alias:sub(1, 1) == alias:sub(2, 2) then | ||
| 574 | res = alias:sub(3) | ||
| 575 | break | ||
| 576 | end | ||
| 577 | end | ||
| 578 | |||
| 579 | res = res or self._name:sub(2) | ||
| 580 | return (res:gsub("-", "_")) | ||
| 581 | end | ||
| 582 | |||
| 583 | function Option:_is_vararg() | ||
| 584 | return self._maxargs ~= self._minargs | ||
| 585 | end | ||
| 586 | |||
| 587 | function Parser:_get_fullname() | ||
| 588 | local parent = self._parent | ||
| 589 | local buf = {self._name} | ||
| 590 | |||
| 591 | while parent do | ||
| 592 | table.insert(buf, 1, parent._name) | ||
| 593 | parent = parent._parent | ||
| 594 | end | ||
| 595 | |||
| 596 | return table.concat(buf, " ") | ||
| 597 | end | ||
| 598 | |||
| 599 | function Parser:_update_charset(charset) | ||
| 600 | charset = charset or {} | ||
| 601 | |||
| 602 | for _, command in ipairs(self._commands) do | ||
| 603 | command:_update_charset(charset) | ||
| 604 | end | ||
| 605 | |||
| 606 | for _, option in ipairs(self._options) do | ||
| 607 | for _, alias in ipairs(option._aliases) do | ||
| 608 | charset[alias:sub(1, 1)] = true | ||
| 609 | end | ||
| 610 | end | ||
| 611 | |||
| 612 | return charset | ||
| 613 | end | ||
| 614 | |||
| 615 | function Parser:argument(...) | ||
| 616 | local argument = Argument(...) | ||
| 617 | table.insert(self._arguments, argument) | ||
| 618 | return argument | ||
| 619 | end | ||
| 620 | |||
| 621 | function Parser:option(...) | ||
| 622 | local option = Option(...) | ||
| 623 | table.insert(self._options, option) | ||
| 624 | return option | ||
| 625 | end | ||
| 626 | |||
| 627 | function Parser:flag(...) | ||
| 628 | return self:option():args(0)(...) | ||
| 629 | end | ||
| 630 | |||
| 631 | function Parser:command(...) | ||
| 632 | local command = Command():add_help(true)(...) | ||
| 633 | command._parent = self | ||
| 634 | table.insert(self._commands, command) | ||
| 635 | return command | ||
| 636 | end | ||
| 637 | |||
| 638 | function Parser:mutex(...) | ||
| 639 | local elements = {...} | ||
| 640 | |||
| 641 | for i, element in ipairs(elements) do | ||
| 642 | local mt = getmetatable(element) | ||
| 643 | assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) | ||
| 644 | end | ||
| 645 | |||
| 646 | table.insert(self._mutexes, elements) | ||
| 647 | return self | ||
| 648 | end | ||
| 649 | |||
| 650 | function Parser:group(name, ...) | ||
| 651 | assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) | ||
| 652 | |||
| 653 | local group = {name = name, ...} | ||
| 654 | |||
| 655 | for i, element in ipairs(group) do | ||
| 656 | local mt = getmetatable(element) | ||
| 657 | assert(mt == Option or mt == Argument or mt == Command, | ||
| 658 | ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) | ||
| 659 | end | ||
| 660 | |||
| 661 | table.insert(self._groups, group) | ||
| 662 | return self | ||
| 663 | end | ||
| 664 | |||
| 665 | local usage_welcome = "Usage: " | ||
| 666 | |||
| 667 | function Parser:get_usage() | ||
| 668 | if self._usage then | ||
| 669 | return self._usage | ||
| 670 | end | ||
| 671 | |||
| 672 | local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) | ||
| 673 | local max_usage_width = self:_inherit_property("usage_max_width", 70) | ||
| 674 | local lines = {usage_welcome .. self:_get_fullname()} | ||
| 675 | |||
| 676 | local function add(s) | ||
| 677 | if #lines[#lines]+1+#s <= max_usage_width then | ||
| 678 | lines[#lines] = lines[#lines] .. " " .. s | ||
| 679 | else | ||
| 680 | lines[#lines+1] = (" "):rep(usage_margin) .. s | ||
| 681 | end | ||
| 682 | end | ||
| 683 | |||
| 684 | -- Normally options are before positional arguments in usage messages. | ||
| 685 | -- However, vararg options should be after, because they can't be reliable used | ||
| 686 | -- before a positional argument. | ||
| 687 | -- Mutexes come into play, too, and are shown as soon as possible. | ||
| 688 | -- Overall, output usages in the following order: | ||
| 689 | -- 1. Mutexes that don't have positional arguments or vararg options. | ||
| 690 | -- 2. Options that are not in any mutexes and are not vararg. | ||
| 691 | -- 3. Positional arguments - on their own or as a part of a mutex. | ||
| 692 | -- 4. Remaining mutexes. | ||
| 693 | -- 5. Remaining options. | ||
| 694 | |||
| 695 | local elements_in_mutexes = {} | ||
| 696 | local added_elements = {} | ||
| 697 | local added_mutexes = {} | ||
| 698 | local argument_to_mutexes = {} | ||
| 699 | |||
| 700 | local function add_mutex(mutex, main_argument) | ||
| 701 | if added_mutexes[mutex] then | ||
| 702 | return | ||
| 703 | end | ||
| 704 | |||
| 705 | added_mutexes[mutex] = true | ||
| 706 | local buf = {} | ||
| 707 | |||
| 708 | for _, element in ipairs(mutex) do | ||
| 709 | if not element._hidden and not added_elements[element] then | ||
| 710 | if getmetatable(element) == Option or element == main_argument then | ||
| 711 | table.insert(buf, element:_get_usage()) | ||
| 712 | added_elements[element] = true | ||
| 713 | end | ||
| 714 | end | ||
| 715 | end | ||
| 716 | |||
| 717 | if #buf == 1 then | ||
| 718 | add(buf[1]) | ||
| 719 | elseif #buf > 1 then | ||
| 720 | add("(" .. table.concat(buf, " | ") .. ")") | ||
| 721 | end | ||
| 722 | end | ||
| 723 | |||
| 724 | local function add_element(element) | ||
| 725 | if not element._hidden and not added_elements[element] then | ||
| 726 | add(element:_get_usage()) | ||
| 727 | added_elements[element] = true | ||
| 728 | end | ||
| 729 | end | ||
| 730 | |||
| 731 | for _, mutex in ipairs(self._mutexes) do | ||
| 732 | local is_vararg = false | ||
| 733 | local has_argument = false | ||
| 734 | |||
| 735 | for _, element in ipairs(mutex) do | ||
| 736 | if getmetatable(element) == Option then | ||
| 737 | if element:_is_vararg() then | ||
| 738 | is_vararg = true | ||
| 739 | end | ||
| 740 | else | ||
| 741 | has_argument = true | ||
| 742 | argument_to_mutexes[element] = argument_to_mutexes[element] or {} | ||
| 743 | table.insert(argument_to_mutexes[element], mutex) | ||
| 744 | end | ||
| 745 | |||
| 746 | elements_in_mutexes[element] = true | ||
| 747 | end | ||
| 748 | |||
| 749 | if not is_vararg and not has_argument then | ||
| 750 | add_mutex(mutex) | ||
| 751 | end | ||
| 752 | end | ||
| 753 | |||
| 754 | for _, option in ipairs(self._options) do | ||
| 755 | if not elements_in_mutexes[option] and not option:_is_vararg() then | ||
| 756 | add_element(option) | ||
| 757 | end | ||
| 758 | end | ||
| 759 | |||
| 760 | -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. | ||
| 761 | for _, argument in ipairs(self._arguments) do | ||
| 762 | -- Pick a mutex as a part of which to show this argument, take the first one that's still available. | ||
| 763 | local mutex | ||
| 764 | |||
| 765 | if elements_in_mutexes[argument] then | ||
| 766 | for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do | ||
| 767 | if not added_mutexes[argument_mutex] then | ||
| 768 | mutex = argument_mutex | ||
| 769 | end | ||
| 770 | end | ||
| 771 | end | ||
| 772 | |||
| 773 | if mutex then | ||
| 774 | add_mutex(mutex, argument) | ||
| 775 | else | ||
| 776 | add_element(argument) | ||
| 777 | end | ||
| 778 | end | ||
| 779 | |||
| 780 | for _, mutex in ipairs(self._mutexes) do | ||
| 781 | add_mutex(mutex) | ||
| 782 | end | ||
| 783 | |||
| 784 | for _, option in ipairs(self._options) do | ||
| 785 | add_element(option) | ||
| 786 | end | ||
| 787 | |||
| 788 | if #self._commands > 0 then | ||
| 789 | if self._require_command then | ||
| 790 | add("<command>") | ||
| 791 | else | ||
| 792 | add("[<command>]") | ||
| 793 | end | ||
| 794 | |||
| 795 | add("...") | ||
| 796 | end | ||
| 797 | |||
| 798 | return table.concat(lines, "\n") | ||
| 799 | end | ||
| 800 | |||
| 801 | local function split_lines(s) | ||
| 802 | if s == "" then | ||
| 803 | return {} | ||
| 804 | end | ||
| 805 | |||
| 806 | local lines = {} | ||
| 807 | |||
| 808 | if s:sub(-1) ~= "\n" then | ||
| 809 | s = s .. "\n" | ||
| 810 | end | ||
| 811 | |||
| 812 | for line in s:gmatch("([^\n]*)\n") do | ||
| 813 | table.insert(lines, line) | ||
| 814 | end | ||
| 815 | |||
| 816 | return lines | ||
| 817 | end | ||
| 818 | |||
| 819 | local function autowrap_line(line, max_length) | ||
| 820 | -- Algorithm for splitting lines is simple and greedy. | ||
| 821 | local result_lines = {} | ||
| 822 | |||
| 823 | -- Preserve original indentation of the line, put this at the beginning of each result line. | ||
| 824 | -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts | ||
| 825 | -- of the second and the following lines vertically align with the start of the second word. | ||
| 826 | local indentation = line:match("^ *") | ||
| 827 | |||
| 828 | if line:find("^ *[%*%+%-]") then | ||
| 829 | indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") | ||
| 830 | end | ||
| 831 | |||
| 832 | -- Parts of the last line being assembled. | ||
| 833 | local line_parts = {} | ||
| 834 | |||
| 835 | -- Length of the current line. | ||
| 836 | local line_length = 0 | ||
| 837 | |||
| 838 | -- Index of the next character to consider. | ||
| 839 | local index = 1 | ||
| 840 | |||
| 841 | while true do | ||
| 842 | local word_start, word_finish, word = line:find("([^ ]+)", index) | ||
| 843 | |||
| 844 | if not word_start then | ||
| 845 | -- Ignore trailing spaces, if any. | ||
| 846 | break | ||
| 847 | end | ||
| 848 | |||
| 849 | local preceding_spaces = line:sub(index, word_start - 1) | ||
| 850 | index = word_finish + 1 | ||
| 851 | |||
| 852 | if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then | ||
| 853 | -- Either this is the very first word or it fits as an addition to the current line, add it. | ||
| 854 | table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. | ||
| 855 | table.insert(line_parts, word) | ||
| 856 | line_length = line_length + #preceding_spaces + #word | ||
| 857 | else | ||
| 858 | -- Does not fit, finish current line and put the word into a new one. | ||
| 859 | table.insert(result_lines, table.concat(line_parts)) | ||
| 860 | line_parts = {indentation, word} | ||
| 861 | line_length = #indentation + #word | ||
| 862 | end | ||
| 863 | end | ||
| 864 | |||
| 865 | if #line_parts > 0 then | ||
| 866 | table.insert(result_lines, table.concat(line_parts)) | ||
| 867 | end | ||
| 868 | |||
| 869 | if #result_lines == 0 then | ||
| 870 | -- Preserve empty lines. | ||
| 871 | result_lines[1] = "" | ||
| 872 | end | ||
| 873 | |||
| 874 | return result_lines | ||
| 875 | end | ||
| 876 | |||
| 877 | -- Automatically wraps lines within given array, | ||
| 878 | -- attempting to limit line length to `max_length`. | ||
| 879 | -- Existing line splits are preserved. | ||
| 880 | local function autowrap(lines, max_length) | ||
| 881 | local result_lines = {} | ||
| 882 | |||
| 883 | for _, line in ipairs(lines) do | ||
| 884 | local autowrapped_lines = autowrap_line(line, max_length) | ||
| 885 | |||
| 886 | for _, autowrapped_line in ipairs(autowrapped_lines) do | ||
| 887 | table.insert(result_lines, autowrapped_line) | ||
| 888 | end | ||
| 889 | end | ||
| 890 | |||
| 891 | return result_lines | ||
| 892 | end | ||
| 893 | |||
| 894 | function Parser:_get_element_help(element) | ||
| 895 | local label_lines = element:_get_label_lines() | ||
| 896 | local description_lines = split_lines(element:_get_description()) | ||
| 897 | |||
| 898 | local result_lines = {} | ||
| 899 | |||
| 900 | -- All label lines should have the same length (except the last one, it has no comma). | ||
| 901 | -- If too long, start description after all the label lines. | ||
| 902 | -- Otherwise, combine label and description lines. | ||
| 903 | |||
| 904 | local usage_margin_len = self:_inherit_property("help_usage_margin", 3) | ||
| 905 | local usage_margin = (" "):rep(usage_margin_len) | ||
| 906 | local description_margin_len = self:_inherit_property("help_description_margin", 25) | ||
| 907 | local description_margin = (" "):rep(description_margin_len) | ||
| 908 | |||
| 909 | local help_max_width = self:_inherit_property("help_max_width") | ||
| 910 | |||
| 911 | if help_max_width then | ||
| 912 | local description_max_width = math.max(help_max_width - description_margin_len, 10) | ||
| 913 | description_lines = autowrap(description_lines, description_max_width) | ||
| 914 | end | ||
| 915 | |||
| 916 | if #label_lines[1] >= (description_margin_len - usage_margin_len) then | ||
| 917 | for _, label_line in ipairs(label_lines) do | ||
| 918 | table.insert(result_lines, usage_margin .. label_line) | ||
| 919 | end | ||
| 920 | |||
| 921 | for _, description_line in ipairs(description_lines) do | ||
| 922 | table.insert(result_lines, description_margin .. description_line) | ||
| 923 | end | ||
| 924 | else | ||
| 925 | for i = 1, math.max(#label_lines, #description_lines) do | ||
| 926 | local label_line = label_lines[i] | ||
| 927 | local description_line = description_lines[i] | ||
| 928 | |||
| 929 | local line = "" | ||
| 930 | |||
| 931 | if label_line then | ||
| 932 | line = usage_margin .. label_line | ||
| 933 | end | ||
| 934 | |||
| 935 | if description_line and description_line ~= "" then | ||
| 936 | line = line .. (" "):rep(description_margin_len - #line) .. description_line | ||
| 937 | end | ||
| 938 | |||
| 939 | table.insert(result_lines, line) | ||
| 940 | end | ||
| 941 | end | ||
| 942 | |||
| 943 | return table.concat(result_lines, "\n") | ||
| 944 | end | ||
| 945 | |||
| 946 | local function get_group_types(group) | ||
| 947 | local types = {} | ||
| 948 | |||
| 949 | for _, element in ipairs(group) do | ||
| 950 | types[getmetatable(element)] = true | ||
| 951 | end | ||
| 952 | |||
| 953 | return types | ||
| 954 | end | ||
| 955 | |||
| 956 | function Parser:_add_group_help(blocks, added_elements, label, elements) | ||
| 957 | local buf = {label} | ||
| 958 | |||
| 959 | for _, element in ipairs(elements) do | ||
| 960 | if not element._hidden and not added_elements[element] then | ||
| 961 | added_elements[element] = true | ||
| 962 | table.insert(buf, self:_get_element_help(element)) | ||
| 963 | end | ||
| 964 | end | ||
| 965 | |||
| 966 | if #buf > 1 then | ||
| 967 | table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) | ||
| 968 | end | ||
| 969 | end | ||
| 970 | |||
| 971 | function Parser:get_help() | ||
| 972 | if self._help then | ||
| 973 | return self._help | ||
| 974 | end | ||
| 975 | |||
| 976 | local blocks = {self:get_usage()} | ||
| 977 | |||
| 978 | local help_max_width = self:_inherit_property("help_max_width") | ||
| 979 | |||
| 980 | if self._description then | ||
| 981 | local description = self._description | ||
| 982 | |||
| 983 | if help_max_width then | ||
| 984 | description = table.concat(autowrap(split_lines(description), help_max_width), "\n") | ||
| 985 | end | ||
| 986 | |||
| 987 | table.insert(blocks, description) | ||
| 988 | end | ||
| 989 | |||
| 990 | -- 1. Put groups containing arguments first, then other arguments. | ||
| 991 | -- 2. Put remaining groups containing options, then other options. | ||
| 992 | -- 3. Put remaining groups containing commands, then other commands. | ||
| 993 | -- Assume that an element can't be in several groups. | ||
| 994 | local groups_by_type = { | ||
| 995 | [Argument] = {}, | ||
| 996 | [Option] = {}, | ||
| 997 | [Command] = {} | ||
| 998 | } | ||
| 999 | |||
| 1000 | for _, group in ipairs(self._groups) do | ||
| 1001 | local group_types = get_group_types(group) | ||
| 1002 | |||
| 1003 | for _, mt in ipairs({Argument, Option, Command}) do | ||
| 1004 | if group_types[mt] then | ||
| 1005 | table.insert(groups_by_type[mt], group) | ||
| 1006 | break | ||
| 1007 | end | ||
| 1008 | end | ||
| 1009 | end | ||
| 1010 | |||
| 1011 | local default_groups = { | ||
| 1012 | {name = "Arguments", type = Argument, elements = self._arguments}, | ||
| 1013 | {name = "Options", type = Option, elements = self._options}, | ||
| 1014 | {name = "Commands", type = Command, elements = self._commands} | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | local added_elements = {} | ||
| 1018 | |||
| 1019 | for _, default_group in ipairs(default_groups) do | ||
| 1020 | local type_groups = groups_by_type[default_group.type] | ||
| 1021 | |||
| 1022 | for _, group in ipairs(type_groups) do | ||
| 1023 | self:_add_group_help(blocks, added_elements, group.name .. ":", group) | ||
| 1024 | end | ||
| 1025 | |||
| 1026 | local default_label = default_group.name .. ":" | ||
| 1027 | |||
| 1028 | if #type_groups > 0 then | ||
| 1029 | default_label = "Other " .. default_label:gsub("^.", string.lower) | ||
| 1030 | end | ||
| 1031 | |||
| 1032 | self:_add_group_help(blocks, added_elements, default_label, default_group.elements) | ||
| 1033 | end | ||
| 1034 | |||
| 1035 | if self._epilog then | ||
| 1036 | local epilog = self._epilog | ||
| 1037 | |||
| 1038 | if help_max_width then | ||
| 1039 | epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") | ||
| 1040 | end | ||
| 1041 | |||
| 1042 | table.insert(blocks, epilog) | ||
| 1043 | end | ||
| 1044 | |||
| 1045 | return table.concat(blocks, "\n\n") | ||
| 1046 | end | ||
| 1047 | |||
| 1048 | function Parser:add_help_command(value) | ||
| 1049 | if value then | ||
| 1050 | assert(type(value) == "string" or type(value) == "table", | ||
| 1051 | ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value))) | ||
| 1052 | end | ||
| 1053 | |||
| 1054 | local help = self:command() | ||
| 1055 | :description "Show help for commands." | ||
| 1056 | help:argument "command" | ||
| 1057 | :description "The command to show help for." | ||
| 1058 | :args "?" | ||
| 1059 | :action(function(_, _, cmd) | ||
| 1060 | if not cmd then | ||
| 1061 | print(self:get_help()) | ||
| 1062 | os.exit(0) | ||
| 1063 | else | ||
| 1064 | for _, command in ipairs(self._commands) do | ||
| 1065 | for _, alias in ipairs(command._aliases) do | ||
| 1066 | if alias == cmd then | ||
| 1067 | print(command:get_help()) | ||
| 1068 | os.exit(0) | ||
| 1069 | end | ||
| 1070 | end | ||
| 1071 | end | ||
| 1072 | end | ||
| 1073 | help:error(("unknown command '%s'"):format(cmd)) | ||
| 1074 | end) | ||
| 1075 | |||
| 1076 | if value then | ||
| 1077 | help = help(value) | ||
| 1078 | end | ||
| 1079 | |||
| 1080 | if not help._name then | ||
| 1081 | help "help" | ||
| 1082 | end | ||
| 1083 | |||
| 1084 | help._is_help_command = true | ||
| 1085 | return self | ||
| 1086 | end | ||
| 1087 | |||
| 1088 | function Parser:_is_shell_safe() | ||
| 1089 | if self._basename then | ||
| 1090 | if self._basename:find("[^%w_%-%+%.]") then | ||
| 1091 | return false | ||
| 1092 | end | ||
| 1093 | else | ||
| 1094 | for _, alias in ipairs(self._aliases) do | ||
| 1095 | if alias:find("[^%w_%-%+%.]") then | ||
| 1096 | return false | ||
| 1097 | end | ||
| 1098 | end | ||
| 1099 | end | ||
| 1100 | for _, option in ipairs(self._options) do | ||
| 1101 | for _, alias in ipairs(option._aliases) do | ||
| 1102 | if alias:find("[^%w_%-%+%.]") then | ||
| 1103 | return false | ||
| 1104 | end | ||
| 1105 | end | ||
| 1106 | if option._choices then | ||
| 1107 | for _, choice in ipairs(option._choices) do | ||
| 1108 | if choice:find("[%s'\"]") then | ||
| 1109 | return false | ||
| 1110 | end | ||
| 1111 | end | ||
| 1112 | end | ||
| 1113 | end | ||
| 1114 | for _, argument in ipairs(self._arguments) do | ||
| 1115 | if argument._choices then | ||
| 1116 | for _, choice in ipairs(argument._choices) do | ||
| 1117 | if choice:find("[%s'\"]") then | ||
| 1118 | return false | ||
| 1119 | end | ||
| 1120 | end | ||
| 1121 | end | ||
| 1122 | end | ||
| 1123 | for _, command in ipairs(self._commands) do | ||
| 1124 | if not command:_is_shell_safe() then | ||
| 1125 | return false | ||
| 1126 | end | ||
| 1127 | end | ||
| 1128 | return true | ||
| 1129 | end | ||
| 1130 | |||
| 1131 | function Parser:add_complete(value) | ||
| 1132 | if value then | ||
| 1133 | assert(type(value) == "string" or type(value) == "table", | ||
| 1134 | ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value))) | ||
| 1135 | end | ||
| 1136 | |||
| 1137 | local complete = self:option() | ||
| 1138 | :description "Output a shell completion script for the specified shell." | ||
| 1139 | :args(1) | ||
| 1140 | :choices {"bash", "zsh", "fish"} | ||
| 1141 | :action(function(_, _, shell) | ||
| 1142 | io.write(self["get_" .. shell .. "_complete"](self)) | ||
| 1143 | os.exit(0) | ||
| 1144 | end) | ||
| 1145 | |||
| 1146 | if value then | ||
| 1147 | complete = complete(value) | ||
| 1148 | end | ||
| 1149 | |||
| 1150 | if not complete._name then | ||
| 1151 | complete "--completion" | ||
| 1152 | end | ||
| 1153 | |||
| 1154 | return self | ||
| 1155 | end | ||
| 1156 | |||
| 1157 | function Parser:add_complete_command(value) | ||
| 1158 | if value then | ||
| 1159 | assert(type(value) == "string" or type(value) == "table", | ||
| 1160 | ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value))) | ||
| 1161 | end | ||
| 1162 | |||
| 1163 | local complete = self:command() | ||
| 1164 | :description "Output a shell completion script." | ||
| 1165 | complete:argument "shell" | ||
| 1166 | :description "The shell to output a completion script for." | ||
| 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 | local function base_name(pathname) | ||
| 1185 | return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname | ||
| 1186 | end | ||
| 1187 | |||
| 1188 | local function get_short_description(element) | ||
| 1189 | local short = element:_get_description():match("^(.-)%.%s") | ||
| 1190 | return short or element:_get_description():match("^(.-)%.?$") | ||
| 1191 | end | ||
| 1192 | |||
| 1193 | function Parser:_get_options() | ||
| 1194 | local options = {} | ||
| 1195 | for _, option in ipairs(self._options) do | ||
| 1196 | for _, alias in ipairs(option._aliases) do | ||
| 1197 | table.insert(options, alias) | ||
| 1198 | end | ||
| 1199 | end | ||
| 1200 | return table.concat(options, " ") | ||
| 1201 | end | ||
| 1202 | |||
| 1203 | function Parser:_get_commands() | ||
| 1204 | local commands = {} | ||
| 1205 | for _, command in ipairs(self._commands) do | ||
| 1206 | for _, alias in ipairs(command._aliases) do | ||
| 1207 | table.insert(commands, alias) | ||
| 1208 | end | ||
| 1209 | end | ||
| 1210 | return table.concat(commands, " ") | ||
| 1211 | end | ||
| 1212 | |||
| 1213 | function Parser:_bash_option_args(buf, indent) | ||
| 1214 | local opts = {} | ||
| 1215 | for _, option in ipairs(self._options) do | ||
| 1216 | if option._choices or option._minargs > 0 then | ||
| 1217 | local compreply | ||
| 1218 | if option._choices then | ||
| 1219 | compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))' | ||
| 1220 | else | ||
| 1221 | compreply = 'COMPREPLY=($(compgen -f -- "$cur"))' | ||
| 1222 | end | ||
| 1223 | table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")") | ||
| 1224 | table.insert(opts, (" "):rep(indent + 8) .. compreply) | ||
| 1225 | table.insert(opts, (" "):rep(indent + 8) .. "return 0") | ||
| 1226 | table.insert(opts, (" "):rep(indent + 8) .. ";;") | ||
| 1227 | end | ||
| 1228 | end | ||
| 1229 | |||
| 1230 | if #opts > 0 then | ||
| 1231 | table.insert(buf, (" "):rep(indent) .. 'case "$prev" in') | ||
| 1232 | table.insert(buf, table.concat(opts, "\n")) | ||
| 1233 | table.insert(buf, (" "):rep(indent) .. "esac\n") | ||
| 1234 | end | ||
| 1235 | end | ||
| 1236 | |||
| 1237 | function Parser:_bash_get_cmd(buf) | ||
| 1238 | local cmds = {} | ||
| 1239 | for _, command in ipairs(self._commands) do | ||
| 1240 | if not command._is_help_command then | ||
| 1241 | table.insert(cmds, (" "):rep(12) .. table.concat(command._aliases, "|") .. ")") | ||
| 1242 | table.insert(cmds, (" "):rep(16) .. 'cmd="' .. command._name .. '"') | ||
| 1243 | table.insert(cmds, (" "):rep(16) .. "break") | ||
| 1244 | table.insert(cmds, (" "):rep(16) .. ";;") | ||
| 1245 | end | ||
| 1246 | end | ||
| 1247 | |||
| 1248 | if #cmds > 0 then | ||
| 1249 | table.insert(buf, (" "):rep(4) .. "for arg in ${COMP_WORDS[@]:1}; do") | ||
| 1250 | table.insert(buf, (" "):rep(8) .. 'case "$arg" in') | ||
| 1251 | table.insert(buf, table.concat(cmds, "\n")) | ||
| 1252 | table.insert(buf, (" "):rep(8) .. "esac") | ||
| 1253 | table.insert(buf, (" "):rep(4) .. "done\n") | ||
| 1254 | end | ||
| 1255 | end | ||
| 1256 | |||
| 1257 | function Parser:_bash_cmd_completions(buf) | ||
| 1258 | local subcmds = {} | ||
| 1259 | for _, command in ipairs(self._commands) do | ||
| 1260 | if #command._options > 0 and not command._is_help_command then | ||
| 1261 | table.insert(subcmds, (" "):rep(8) .. command._name .. ")") | ||
| 1262 | command:_bash_option_args(subcmds, 12) | ||
| 1263 | table.insert(subcmds, (" "):rep(12) .. 'opts="$opts ' .. command:_get_options() .. '"') | ||
| 1264 | table.insert(subcmds, (" "):rep(12) .. ";;") | ||
| 1265 | end | ||
| 1266 | end | ||
| 1267 | |||
| 1268 | table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') | ||
| 1269 | table.insert(buf, (" "):rep(8) .. self._basename .. ")") | ||
| 1270 | table.insert(buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') | ||
| 1271 | table.insert(buf, (" "):rep(12) .. ";;") | ||
| 1272 | if #subcmds > 0 then | ||
| 1273 | table.insert(buf, table.concat(subcmds, "\n")) | ||
| 1274 | end | ||
| 1275 | table.insert(buf, (" "):rep(4) .. "esac\n") | ||
| 1276 | end | ||
| 1277 | |||
| 1278 | function Parser:get_bash_complete() | ||
| 1279 | self._basename = base_name(self._name) | ||
| 1280 | assert(self:_is_shell_safe()) | ||
| 1281 | local buf = {([[ | ||
| 1282 | _%s() { | ||
| 1283 | local IFS=$' \t\n' | ||
| 1284 | local cur prev cmd opts arg | ||
| 1285 | cur="${COMP_WORDS[COMP_CWORD]}" | ||
| 1286 | prev="${COMP_WORDS[COMP_CWORD-1]}" | ||
| 1287 | cmd="%s" | ||
| 1288 | opts="%s" | ||
| 1289 | ]]):format(self._basename, self._basename, self:_get_options())} | ||
| 1290 | |||
| 1291 | self:_bash_option_args(buf, 4) | ||
| 1292 | self:_bash_get_cmd(buf) | ||
| 1293 | if #self._commands > 0 then | ||
| 1294 | self:_bash_cmd_completions(buf) | ||
| 1295 | end | ||
| 1296 | |||
| 1297 | table.insert(buf, ([=[ | ||
| 1298 | if [[ "$cur" = -* ]]; then | ||
| 1299 | COMPREPLY=($(compgen -W "$opts" -- "$cur")) | ||
| 1300 | fi | ||
| 1301 | } | ||
| 1302 | |||
| 1303 | complete -F _%s -o bashdefault -o default %s | ||
| 1304 | ]=]):format(self._basename, self._basename)) | ||
| 1305 | |||
| 1306 | return table.concat(buf, "\n") | ||
| 1307 | end | ||
| 1308 | |||
| 1309 | function Parser:_zsh_arguments(buf, cmd_name, indent) | ||
| 1310 | if self._parent then | ||
| 1311 | table.insert(buf, (" "):rep(indent) .. "options=(") | ||
| 1312 | table.insert(buf, (" "):rep(indent + 2) .. "$options") | ||
| 1313 | else | ||
| 1314 | table.insert(buf, (" "):rep(indent) .. "local -a options=(") | ||
| 1315 | end | ||
| 1316 | |||
| 1317 | for _, option in ipairs(self._options) do | ||
| 1318 | local line = {} | ||
| 1319 | if #option._aliases > 1 then | ||
| 1320 | if option._maxcount > 1 then | ||
| 1321 | table.insert(line, '"*"') | ||
| 1322 | end | ||
| 1323 | table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"') | ||
| 1324 | else | ||
| 1325 | table.insert(line, '"') | ||
| 1326 | if option._maxcount > 1 then | ||
| 1327 | table.insert(line, "*") | ||
| 1328 | end | ||
| 1329 | table.insert(line, option._name) | ||
| 1330 | end | ||
| 1331 | if option._description then | ||
| 1332 | local description = get_short_description(option):gsub('["%]:`$]', "\\%0") | ||
| 1333 | table.insert(line, "[" .. description .. "]") | ||
| 1334 | end | ||
| 1335 | if option._maxargs == math.huge then | ||
| 1336 | table.insert(line, ":*") | ||
| 1337 | end | ||
| 1338 | if option._choices then | ||
| 1339 | table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")") | ||
| 1340 | elseif option._maxargs > 0 then | ||
| 1341 | table.insert(line, ": :_files") | ||
| 1342 | end | ||
| 1343 | table.insert(line, '"') | ||
| 1344 | table.insert(buf, (" "):rep(indent + 2) .. table.concat(line)) | ||
| 1345 | end | ||
| 1346 | |||
| 1347 | table.insert(buf, (" "):rep(indent) .. ")") | ||
| 1348 | table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\") | ||
| 1349 | table.insert(buf, (" "):rep(indent + 2) .. "$options \\") | ||
| 1350 | |||
| 1351 | if self._is_help_command then | ||
| 1352 | table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\') | ||
| 1353 | else | ||
| 1354 | for _, argument in ipairs(self._arguments) do | ||
| 1355 | local spec | ||
| 1356 | if argument._choices then | ||
| 1357 | spec = ": :(" .. table.concat(argument._choices, " ") .. ")" | ||
| 1358 | else | ||
| 1359 | spec = ": :_files" | ||
| 1360 | end | ||
| 1361 | if argument._maxargs == math.huge then | ||
| 1362 | table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\') | ||
| 1363 | break | ||
| 1364 | end | ||
| 1365 | for _ = 1, argument._maxargs do | ||
| 1366 | table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\') | ||
| 1367 | end | ||
| 1368 | end | ||
| 1369 | |||
| 1370 | if #self._commands > 0 then | ||
| 1371 | table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\') | ||
| 1372 | table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\') | ||
| 1373 | end | ||
| 1374 | end | ||
| 1375 | |||
| 1376 | table.insert(buf, (" "):rep(indent + 2) .. "&& return 0") | ||
| 1377 | end | ||
| 1378 | |||
| 1379 | function Parser:_zsh_cmds(buf, cmd_name) | ||
| 1380 | table.insert(buf, "\n_" .. cmd_name .. "_cmds() {") | ||
| 1381 | table.insert(buf, " local -a commands=(") | ||
| 1382 | |||
| 1383 | for _, command in ipairs(self._commands) do | ||
| 1384 | local line = {} | ||
| 1385 | if #command._aliases > 1 then | ||
| 1386 | table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"') | ||
| 1387 | else | ||
| 1388 | table.insert(line, '"' .. command._name) | ||
| 1389 | end | ||
| 1390 | if command._description then | ||
| 1391 | table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0")) | ||
| 1392 | end | ||
| 1393 | table.insert(buf, " " .. table.concat(line) .. '"') | ||
| 1394 | end | ||
| 1395 | |||
| 1396 | table.insert(buf, ' )\n _describe "command" commands\n}') | ||
| 1397 | end | ||
| 1398 | |||
| 1399 | function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent) | ||
| 1400 | if #self._commands == 0 then | ||
| 1401 | return | ||
| 1402 | end | ||
| 1403 | |||
| 1404 | self:_zsh_cmds(cmds_buf, cmd_name) | ||
| 1405 | table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in") | ||
| 1406 | |||
| 1407 | for _, command in ipairs(self._commands) do | ||
| 1408 | local name = cmd_name .. "_" .. command._name | ||
| 1409 | table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")") | ||
| 1410 | command:_zsh_arguments(buf, name, indent + 4) | ||
| 1411 | command:_zsh_complete_help(buf, cmds_buf, name, indent + 4) | ||
| 1412 | table.insert(buf, (" "):rep(indent + 4) .. ";;\n") | ||
| 1413 | end | ||
| 1414 | |||
| 1415 | table.insert(buf, (" "):rep(indent) .. "esac") | ||
| 1416 | end | ||
| 1417 | |||
| 1418 | function Parser:get_zsh_complete() | ||
| 1419 | self._basename = base_name(self._name) | ||
| 1420 | assert(self:_is_shell_safe()) | ||
| 1421 | local buf = {("#compdef %s\n"):format(self._basename)} | ||
| 1422 | local cmds_buf = {} | ||
| 1423 | table.insert(buf, "_" .. self._basename .. "() {") | ||
| 1424 | if #self._commands > 0 then | ||
| 1425 | table.insert(buf, " local context state state_descr line") | ||
| 1426 | table.insert(buf, " typeset -A opt_args\n") | ||
| 1427 | end | ||
| 1428 | self:_zsh_arguments(buf, self._basename, 2) | ||
| 1429 | self:_zsh_complete_help(buf, cmds_buf, self._basename, 2) | ||
| 1430 | table.insert(buf, "\n return 1") | ||
| 1431 | table.insert(buf, "}") | ||
| 1432 | |||
| 1433 | local result = table.concat(buf, "\n") | ||
| 1434 | if #cmds_buf > 0 then | ||
| 1435 | result = result .. "\n" .. table.concat(cmds_buf, "\n") | ||
| 1436 | end | ||
| 1437 | return result .. "\n\n_" .. self._basename .. "\n" | ||
| 1438 | end | ||
| 1439 | |||
| 1440 | local function fish_escape(string) | ||
| 1441 | return string:gsub("[\\']", "\\%0") | ||
| 1442 | end | ||
| 1443 | |||
| 1444 | function Parser:_fish_complete_help(buf, prefix) | ||
| 1445 | table.insert(buf, "") | ||
| 1446 | |||
| 1447 | for _, command in ipairs(self._commands) do | ||
| 1448 | for _, alias in ipairs(command._aliases) do | ||
| 1449 | local line = ("%s -n '__fish_use_subcommand' -xa '%s'"):format(prefix, alias) | ||
| 1450 | if command._description then | ||
| 1451 | line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) | ||
| 1452 | end | ||
| 1453 | table.insert(buf, line) | ||
| 1454 | end | ||
| 1455 | |||
| 1456 | end | ||
| 1457 | |||
| 1458 | if self._is_help_command then | ||
| 1459 | local line = ("%s -n '__fish_seen_subcommand_from %s' -xa '%s'") | ||
| 1460 | :format(prefix, table.concat(self._aliases, " "), self._parent:_get_commands()) | ||
| 1461 | table.insert(buf, line) | ||
| 1462 | end | ||
| 1463 | |||
| 1464 | for _, option in ipairs(self._options) do | ||
| 1465 | local parts = {prefix} | ||
| 1466 | |||
| 1467 | if self._parent then | ||
| 1468 | table.insert(parts, "-n '__fish_seen_subcommand_from " .. table.concat(self._aliases, " ") .. "'") | ||
| 1469 | end | ||
| 1470 | |||
| 1471 | for _, alias in ipairs(option._aliases) do | ||
| 1472 | if alias:match("^%-.$") then | ||
| 1473 | table.insert(parts, "-s " .. alias:sub(2)) | ||
| 1474 | elseif alias:match("^%-%-.+") then | ||
| 1475 | table.insert(parts, "-l " .. alias:sub(3)) | ||
| 1476 | end | ||
| 1477 | end | ||
| 1478 | |||
| 1479 | if option._choices then | ||
| 1480 | table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'") | ||
| 1481 | elseif option._minargs > 0 then | ||
| 1482 | table.insert(parts, "-r") | ||
| 1483 | end | ||
| 1484 | |||
| 1485 | if option._description then | ||
| 1486 | table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'") | ||
| 1487 | end | ||
| 1488 | |||
| 1489 | table.insert(buf, table.concat(parts, " ")) | ||
| 1490 | end | ||
| 1491 | |||
| 1492 | for _, command in ipairs(self._commands) do | ||
| 1493 | command:_fish_complete_help(buf, prefix) | ||
| 1494 | end | ||
| 1495 | end | ||
| 1496 | |||
| 1497 | function Parser:get_fish_complete() | ||
| 1498 | self._basename = base_name(self._name) | ||
| 1499 | assert(self:_is_shell_safe()) | ||
| 1500 | local buf = {} | ||
| 1501 | local prefix = "complete -c " .. self._basename | ||
| 1502 | self:_fish_complete_help(buf, prefix) | ||
| 1503 | return table.concat(buf, "\n") .. "\n" | ||
| 1504 | end | ||
| 1505 | |||
| 1506 | local function get_tip(context, wrong_name) | ||
| 1507 | local context_pool = {} | ||
| 1508 | local possible_name | ||
| 1509 | local possible_names = {} | ||
| 1510 | |||
| 1511 | for name in pairs(context) do | ||
| 1512 | if type(name) == "string" then | ||
| 1513 | for i = 1, #name do | ||
| 1514 | possible_name = name:sub(1, i - 1) .. name:sub(i + 1) | ||
| 1515 | |||
| 1516 | if not context_pool[possible_name] then | ||
| 1517 | context_pool[possible_name] = {} | ||
| 1518 | end | ||
| 1519 | |||
| 1520 | table.insert(context_pool[possible_name], name) | ||
| 1521 | end | ||
| 1522 | end | ||
| 1523 | end | ||
| 1524 | |||
| 1525 | for i = 1, #wrong_name + 1 do | ||
| 1526 | possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) | ||
| 1527 | |||
| 1528 | if context[possible_name] then | ||
| 1529 | possible_names[possible_name] = true | ||
| 1530 | elseif context_pool[possible_name] then | ||
| 1531 | for _, name in ipairs(context_pool[possible_name]) do | ||
| 1532 | possible_names[name] = true | ||
| 1533 | end | ||
| 1534 | end | ||
| 1535 | end | ||
| 1536 | |||
| 1537 | local first = next(possible_names) | ||
| 1538 | |||
| 1539 | if first then | ||
| 1540 | if next(possible_names, first) then | ||
| 1541 | local possible_names_arr = {} | ||
| 1542 | |||
| 1543 | for name in pairs(possible_names) do | ||
| 1544 | table.insert(possible_names_arr, "'" .. name .. "'") | ||
| 1545 | end | ||
| 1546 | |||
| 1547 | table.sort(possible_names_arr) | ||
| 1548 | return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" | ||
| 1549 | else | ||
| 1550 | return "\nDid you mean '" .. first .. "'?" | ||
| 1551 | end | ||
| 1552 | else | ||
| 1553 | return "" | ||
| 1554 | end | ||
| 1555 | end | ||
| 1556 | |||
| 1557 | local ElementState = class({ | ||
| 1558 | invocations = 0 | ||
| 1559 | }) | ||
| 1560 | |||
| 1561 | function ElementState:__call(state, element) | ||
| 1562 | self.state = state | ||
| 1563 | self.result = state.result | ||
| 1564 | self.element = element | ||
| 1565 | self.target = element._target or element:_get_default_target() | ||
| 1566 | self.action, self.result[self.target] = element:_get_action() | ||
| 1567 | return self | ||
| 1568 | end | ||
| 1569 | |||
| 1570 | function ElementState:error(fmt, ...) | ||
| 1571 | self.state:error(fmt, ...) | ||
| 1572 | end | ||
| 1573 | |||
| 1574 | function ElementState:convert(argument, index) | ||
| 1575 | local converter = self.element._convert | ||
| 1576 | |||
| 1577 | if converter then | ||
| 1578 | local ok, err | ||
| 1579 | |||
| 1580 | if type(converter) == "function" then | ||
| 1581 | ok, err = converter(argument) | ||
| 1582 | elseif type(converter[index]) == "function" then | ||
| 1583 | ok, err = converter[index](argument) | ||
| 1584 | else | ||
| 1585 | ok = converter[argument] | ||
| 1586 | end | ||
| 1587 | |||
| 1588 | if ok == nil then | ||
| 1589 | self:error(err and "%s" or "malformed argument '%s'", err or argument) | ||
| 1590 | end | ||
| 1591 | |||
| 1592 | argument = ok | ||
| 1593 | end | ||
| 1594 | |||
| 1595 | return argument | ||
| 1596 | end | ||
| 1597 | |||
| 1598 | function ElementState:default(mode) | ||
| 1599 | return self.element._defmode:find(mode) and self.element._default | ||
| 1600 | end | ||
| 1601 | |||
| 1602 | local function bound(noun, min, max, is_max) | ||
| 1603 | local res = "" | ||
| 1604 | |||
| 1605 | if min ~= max then | ||
| 1606 | res = "at " .. (is_max and "most" or "least") .. " " | ||
| 1607 | end | ||
| 1608 | |||
| 1609 | local number = is_max and max or min | ||
| 1610 | return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") | ||
| 1611 | end | ||
| 1612 | |||
| 1613 | function ElementState:set_name(alias) | ||
| 1614 | self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) | ||
| 1615 | end | ||
| 1616 | |||
| 1617 | function ElementState:invoke() | ||
| 1618 | self.open = true | ||
| 1619 | self.overwrite = false | ||
| 1620 | |||
| 1621 | if self.invocations >= self.element._maxcount then | ||
| 1622 | if self.element._overwrite then | ||
| 1623 | self.overwrite = true | ||
| 1624 | else | ||
| 1625 | local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) | ||
| 1626 | self:error("%s must be used %s", self.name, num_times_repr) | ||
| 1627 | end | ||
| 1628 | else | ||
| 1629 | self.invocations = self.invocations + 1 | ||
| 1630 | end | ||
| 1631 | |||
| 1632 | self.args = {} | ||
| 1633 | |||
| 1634 | if self.element._maxargs <= 0 then | ||
| 1635 | self:close() | ||
| 1636 | end | ||
| 1637 | |||
| 1638 | return self.open | ||
| 1639 | end | ||
| 1640 | |||
| 1641 | function ElementState:check_choices(argument) | ||
| 1642 | if self.element._choices then | ||
| 1643 | for _, choice in ipairs(self.element._choices) do | ||
| 1644 | if argument == choice then | ||
| 1645 | return | ||
| 1646 | end | ||
| 1647 | end | ||
| 1648 | local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'" | ||
| 1649 | local is_option = getmetatable(self.element) == Option | ||
| 1650 | self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list) | ||
| 1651 | end | ||
| 1652 | end | ||
| 1653 | |||
| 1654 | function ElementState:pass(argument) | ||
| 1655 | self:check_choices(argument) | ||
| 1656 | argument = self:convert(argument, #self.args + 1) | ||
| 1657 | table.insert(self.args, argument) | ||
| 1658 | |||
| 1659 | if #self.args >= self.element._maxargs then | ||
| 1660 | self:close() | ||
| 1661 | end | ||
| 1662 | |||
| 1663 | return self.open | ||
| 1664 | end | ||
| 1665 | |||
| 1666 | function ElementState:complete_invocation() | ||
| 1667 | while #self.args < self.element._minargs do | ||
| 1668 | self:pass(self.element._default) | ||
| 1669 | end | ||
| 1670 | end | ||
| 1671 | |||
| 1672 | function ElementState:close() | ||
| 1673 | if self.open then | ||
| 1674 | self.open = false | ||
| 1675 | |||
| 1676 | if #self.args < self.element._minargs then | ||
| 1677 | if self:default("a") then | ||
| 1678 | self:complete_invocation() | ||
| 1679 | else | ||
| 1680 | if #self.args == 0 then | ||
| 1681 | if getmetatable(self.element) == Argument then | ||
| 1682 | self:error("missing %s", self.name) | ||
| 1683 | elseif self.element._maxargs == 1 then | ||
| 1684 | self:error("%s requires an argument", self.name) | ||
| 1685 | end | ||
| 1686 | end | ||
| 1687 | |||
| 1688 | self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) | ||
| 1689 | end | ||
| 1690 | end | ||
| 1691 | |||
| 1692 | local args | ||
| 1693 | |||
| 1694 | if self.element._maxargs == 0 then | ||
| 1695 | args = self.args[1] | ||
| 1696 | elseif self.element._maxargs == 1 then | ||
| 1697 | if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then | ||
| 1698 | args = self.args | ||
| 1699 | else | ||
| 1700 | args = self.args[1] | ||
| 1701 | end | ||
| 1702 | else | ||
| 1703 | args = self.args | ||
| 1704 | end | ||
| 1705 | |||
| 1706 | self.action(self.result, self.target, args, self.overwrite) | ||
| 1707 | end | ||
| 1708 | end | ||
| 1709 | |||
| 1710 | local ParseState = class({ | ||
| 1711 | result = {}, | ||
| 1712 | options = {}, | ||
| 1713 | arguments = {}, | ||
| 1714 | argument_i = 1, | ||
| 1715 | element_to_mutexes = {}, | ||
| 1716 | mutex_to_element_state = {}, | ||
| 1717 | command_actions = {} | ||
| 1718 | }) | ||
| 1719 | |||
| 1720 | function ParseState:__call(parser, error_handler) | ||
| 1721 | self.parser = parser | ||
| 1722 | self.error_handler = error_handler | ||
| 1723 | self.charset = parser:_update_charset() | ||
| 1724 | self:switch(parser) | ||
| 1725 | return self | ||
| 1726 | end | ||
| 1727 | |||
| 1728 | function ParseState:error(fmt, ...) | ||
| 1729 | self.error_handler(self.parser, fmt:format(...)) | ||
| 1730 | end | ||
| 1731 | |||
| 1732 | function ParseState:switch(parser) | ||
| 1733 | self.parser = parser | ||
| 1734 | |||
| 1735 | if parser._action then | ||
| 1736 | table.insert(self.command_actions, {action = parser._action, name = parser._name}) | ||
| 1737 | end | ||
| 1738 | |||
| 1739 | for _, option in ipairs(parser._options) do | ||
| 1740 | option = ElementState(self, option) | ||
| 1741 | table.insert(self.options, option) | ||
| 1742 | |||
| 1743 | for _, alias in ipairs(option.element._aliases) do | ||
| 1744 | self.options[alias] = option | ||
| 1745 | end | ||
| 1746 | end | ||
| 1747 | |||
| 1748 | for _, mutex in ipairs(parser._mutexes) do | ||
| 1749 | for _, element in ipairs(mutex) do | ||
| 1750 | if not self.element_to_mutexes[element] then | ||
| 1751 | self.element_to_mutexes[element] = {} | ||
| 1752 | end | ||
| 1753 | |||
| 1754 | table.insert(self.element_to_mutexes[element], mutex) | ||
| 1755 | end | ||
| 1756 | end | ||
| 1757 | |||
| 1758 | for _, argument in ipairs(parser._arguments) do | ||
| 1759 | argument = ElementState(self, argument) | ||
| 1760 | table.insert(self.arguments, argument) | ||
| 1761 | argument:set_name() | ||
| 1762 | argument:invoke() | ||
| 1763 | end | ||
| 1764 | |||
| 1765 | self.handle_options = parser._handle_options | ||
| 1766 | self.argument = self.arguments[self.argument_i] | ||
| 1767 | self.commands = parser._commands | ||
| 1768 | |||
| 1769 | for _, command in ipairs(self.commands) do | ||
| 1770 | for _, alias in ipairs(command._aliases) do | ||
| 1771 | self.commands[alias] = command | ||
| 1772 | end | ||
| 1773 | end | ||
| 1774 | end | ||
| 1775 | |||
| 1776 | function ParseState:get_option(name) | ||
| 1777 | local option = self.options[name] | ||
| 1778 | |||
| 1779 | if not option then | ||
| 1780 | self:error("unknown option '%s'%s", name, get_tip(self.options, name)) | ||
| 1781 | else | ||
| 1782 | return option | ||
| 1783 | end | ||
| 1784 | end | ||
| 1785 | |||
| 1786 | function ParseState:get_command(name) | ||
| 1787 | local command = self.commands[name] | ||
| 1788 | |||
| 1789 | if not command then | ||
| 1790 | if #self.commands > 0 then | ||
| 1791 | self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) | ||
| 1792 | else | ||
| 1793 | self:error("too many arguments") | ||
| 1794 | end | ||
| 1795 | else | ||
| 1796 | return command | ||
| 1797 | end | ||
| 1798 | end | ||
| 1799 | |||
| 1800 | function ParseState:check_mutexes(element_state) | ||
| 1801 | if self.element_to_mutexes[element_state.element] then | ||
| 1802 | for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do | ||
| 1803 | local used_element_state = self.mutex_to_element_state[mutex] | ||
| 1804 | |||
| 1805 | if used_element_state and used_element_state ~= element_state then | ||
| 1806 | self:error("%s can not be used together with %s", element_state.name, used_element_state.name) | ||
| 1807 | else | ||
| 1808 | self.mutex_to_element_state[mutex] = element_state | ||
| 1809 | end | ||
| 1810 | end | ||
| 1811 | end | ||
| 1812 | end | ||
| 1813 | |||
| 1814 | function ParseState:invoke(option, name) | ||
| 1815 | self:close() | ||
| 1816 | option:set_name(name) | ||
| 1817 | self:check_mutexes(option, name) | ||
| 1818 | |||
| 1819 | if option:invoke() then | ||
| 1820 | self.option = option | ||
| 1821 | end | ||
| 1822 | end | ||
| 1823 | |||
| 1824 | function ParseState:pass(arg) | ||
| 1825 | if self.option then | ||
| 1826 | if not self.option:pass(arg) then | ||
| 1827 | self.option = nil | ||
| 1828 | end | ||
| 1829 | elseif self.argument then | ||
| 1830 | self:check_mutexes(self.argument) | ||
| 1831 | |||
| 1832 | if not self.argument:pass(arg) then | ||
| 1833 | self.argument_i = self.argument_i + 1 | ||
| 1834 | self.argument = self.arguments[self.argument_i] | ||
| 1835 | end | ||
| 1836 | else | ||
| 1837 | local command = self:get_command(arg) | ||
| 1838 | self.result[command._target or command._name] = true | ||
| 1839 | |||
| 1840 | if self.parser._command_target then | ||
| 1841 | self.result[self.parser._command_target] = command._name | ||
| 1842 | end | ||
| 1843 | |||
| 1844 | self:switch(command) | ||
| 1845 | end | ||
| 1846 | end | ||
| 1847 | |||
| 1848 | function ParseState:close() | ||
| 1849 | if self.option then | ||
| 1850 | self.option:close() | ||
| 1851 | self.option = nil | ||
| 1852 | end | ||
| 1853 | end | ||
| 1854 | |||
| 1855 | function ParseState:finalize() | ||
| 1856 | self:close() | ||
| 1857 | |||
| 1858 | for i = self.argument_i, #self.arguments do | ||
| 1859 | local argument = self.arguments[i] | ||
| 1860 | if #argument.args == 0 and argument:default("u") then | ||
| 1861 | argument:complete_invocation() | ||
| 1862 | else | ||
| 1863 | argument:close() | ||
| 1864 | end | ||
| 1865 | end | ||
| 1866 | |||
| 1867 | if self.parser._require_command and #self.commands > 0 then | ||
| 1868 | self:error("a command is required") | ||
| 1869 | end | ||
| 1870 | |||
| 1871 | for _, option in ipairs(self.options) do | ||
| 1872 | option.name = option.name or ("option '%s'"):format(option.element._name) | ||
| 1873 | |||
| 1874 | if option.invocations == 0 then | ||
| 1875 | if option:default("u") then | ||
| 1876 | option:invoke() | ||
| 1877 | option:complete_invocation() | ||
| 1878 | option:close() | ||
| 1879 | end | ||
| 1880 | end | ||
| 1881 | |||
| 1882 | local mincount = option.element._mincount | ||
| 1883 | |||
| 1884 | if option.invocations < mincount then | ||
| 1885 | if option:default("a") then | ||
| 1886 | while option.invocations < mincount do | ||
| 1887 | option:invoke() | ||
| 1888 | option:close() | ||
| 1889 | end | ||
| 1890 | elseif option.invocations == 0 then | ||
| 1891 | self:error("missing %s", option.name) | ||
| 1892 | else | ||
| 1893 | self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) | ||
| 1894 | end | ||
| 1895 | end | ||
| 1896 | end | ||
| 1897 | |||
| 1898 | for i = #self.command_actions, 1, -1 do | ||
| 1899 | self.command_actions[i].action(self.result, self.command_actions[i].name) | ||
| 1900 | end | ||
| 1901 | end | ||
| 1902 | |||
| 1903 | function ParseState:parse(args) | ||
| 1904 | for _, arg in ipairs(args) do | ||
| 1905 | local plain = true | ||
| 1906 | |||
| 1907 | if self.handle_options then | ||
| 1908 | local first = arg:sub(1, 1) | ||
| 1909 | |||
| 1910 | if self.charset[first] then | ||
| 1911 | if #arg > 1 then | ||
| 1912 | plain = false | ||
| 1913 | |||
| 1914 | if arg:sub(2, 2) == first then | ||
| 1915 | if #arg == 2 then | ||
| 1916 | if self.options[arg] then | ||
| 1917 | local option = self:get_option(arg) | ||
| 1918 | self:invoke(option, arg) | ||
| 1919 | else | ||
| 1920 | self:close() | ||
| 1921 | end | ||
| 1922 | |||
| 1923 | self.handle_options = false | ||
| 1924 | else | ||
| 1925 | local equals = arg:find "=" | ||
| 1926 | if equals then | ||
| 1927 | local name = arg:sub(1, equals - 1) | ||
| 1928 | local option = self:get_option(name) | ||
| 1929 | |||
| 1930 | if option.element._maxargs <= 0 then | ||
| 1931 | self:error("option '%s' does not take arguments", name) | ||
| 1932 | end | ||
| 1933 | |||
| 1934 | self:invoke(option, name) | ||
| 1935 | self:pass(arg:sub(equals + 1)) | ||
| 1936 | else | ||
| 1937 | local option = self:get_option(arg) | ||
| 1938 | self:invoke(option, arg) | ||
| 1939 | end | ||
| 1940 | end | ||
| 1941 | else | ||
| 1942 | for i = 2, #arg do | ||
| 1943 | local name = first .. arg:sub(i, i) | ||
| 1944 | local option = self:get_option(name) | ||
| 1945 | self:invoke(option, name) | ||
| 1946 | |||
| 1947 | if i ~= #arg and option.element._maxargs > 0 then | ||
| 1948 | self:pass(arg:sub(i + 1)) | ||
| 1949 | break | ||
| 1950 | end | ||
| 1951 | end | ||
| 1952 | end | ||
| 1953 | end | ||
| 1954 | end | ||
| 1955 | end | ||
| 1956 | |||
| 1957 | if plain then | ||
| 1958 | self:pass(arg) | ||
| 1959 | end | ||
| 1960 | end | ||
| 1961 | |||
| 1962 | self:finalize() | ||
| 1963 | return self.result | ||
| 1964 | end | ||
| 1965 | |||
| 1966 | function Parser:error(msg) | ||
| 1967 | io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) | ||
| 1968 | os.exit(1) | ||
| 1969 | end | ||
| 1970 | |||
| 1971 | -- Compatibility with strict.lua and other checkers: | ||
| 1972 | local default_cmdline = rawget(_G, "arg") or {} | ||
| 1973 | |||
| 1974 | function Parser:_parse(args, error_handler) | ||
| 1975 | return ParseState(self, error_handler):parse(args or default_cmdline) | ||
| 1976 | end | ||
| 1977 | |||
| 1978 | function Parser:parse(args) | ||
| 1979 | return self:_parse(args, self.error) | ||
| 1980 | end | ||
| 1981 | |||
| 1982 | local function xpcall_error_handler(err) | ||
| 1983 | return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) | ||
| 1984 | end | ||
| 1985 | |||
| 1986 | function Parser:pparse(args) | ||
| 1987 | local parse_error | ||
| 1988 | |||
| 1989 | local ok, result = xpcall(function() | ||
| 1990 | return self:_parse(args, function(_, err) | ||
| 1991 | parse_error = err | ||
| 1992 | error(err, 0) | ||
| 1993 | end) | ||
| 1994 | end, xpcall_error_handler) | ||
| 1995 | |||
| 1996 | if ok then | ||
| 1997 | return true, result | ||
| 1998 | elseif not parse_error then | ||
| 1999 | error(result, 0) | ||
| 2000 | else | ||
| 2001 | return false, parse_error | ||
| 2002 | end | ||
| 2003 | end | ||
| 2004 | |||
| 2005 | local argparse = {} | ||
| 2006 | |||
| 2007 | argparse.version = "0.6.0" | ||
| 2008 | |||
| 2009 | setmetatable(argparse, {__call = function(_, ...) | ||
| 2010 | return Parser(default_cmdline[0]):add_help(true)(...) | ||
| 2011 | end}) | ||
| 2012 | |||
| 2013 | return argparse | ||
diff --git a/src/luarocks/cmd.lua b/src/luarocks/cmd.lua index f000e59e..60aa633c 100644 --- a/src/luarocks/cmd.lua +++ b/src/luarocks/cmd.lua | |||
| @@ -9,7 +9,7 @@ local cfg = require("luarocks.core.cfg") | |||
| 9 | local dir = require("luarocks.dir") | 9 | local dir = require("luarocks.dir") |
| 10 | local fun = require("luarocks.fun") | 10 | local fun = require("luarocks.fun") |
| 11 | local fs = require("luarocks.fs") | 11 | local fs = require("luarocks.fs") |
| 12 | local argparse = require("argparse") | 12 | local argparse = require("luarocks.argparse") |
| 13 | 13 | ||
| 14 | local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded") | 14 | local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded") |
| 15 | if not hc_ok then | 15 | if not hc_ok then |
