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 23local 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 33end 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. 38local 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) 110end 111 112local 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))) 120end 121 122local function typechecked(name, ...) 123 local types = {...} 124 return {name, function(_, value) typecheck(name, types, value) end} 125end 126 127local 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 142end} 143 144local 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 155end} 156 157local 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 183end 184 185local 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} 197end 198 199local actions = {} 200 201local 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 207end} 208 209local option_init = {"init", function(self) 210 self._has_init = true 211end} 212 213local 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 219end} 220 221local 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 247end} 248 249local 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 277local 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 304local 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 328local 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 353function 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 369end 370 371function 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 400end 401 402function 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 412end 413 414function actions.store_true(result, target) 415 result[target] = true 416end 417 418function actions.store_false(result, target) 419 result[target] = false 420end 421 422function actions.store(result, target, argument) 423 result[target] = argument 424end 425 426function actions.count(result, target, _, overwrite) 427 if not overwrite then 428 result[target] = result[target] + 1 429 end 430end 431 432function 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 439end 440 441function 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 451end 452 453function 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 483end 484 485-- Returns placeholder for `narg`-th argument. 486function 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 494end 495 496function Argument:_get_choices_list() 497 return "{" .. table.concat(self._choices, ",") .. "}" 498end 499 500function Argument:_get_default_argname() 501 if self._choices then 502 return self:_get_choices_list() 503 else 504 return "<" .. self._name .. ">" 505 end 506end 507 508function 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 514end 515 516-- Returns labels to be shown in the help message. 517function Argument:_get_label_lines() 518 if self._choices then 519 return {self:_get_choices_list()} 520 else 521 return {self._name} 522 end 523end 524 525function 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 553end 554 555function Command:_get_label_lines() 556 return {table.concat(self._public_aliases, ", ")} 557end 558 559function 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 569end 570 571function Command:_get_description() 572 return self._summary or self._description or "" 573end 574 575function 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 585end 586 587function Argument:_get_default_target() 588 return self._name 589end 590 591function 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("-", "_")) 603end 604 605function Option:_is_vararg() 606 return self._maxargs ~= self._minargs 607end 608 609function 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, " ") 624end 625 626function 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 640end 641 642function Parser:argument(...) 643 local argument = Argument(...) 644 table.insert(self._arguments, argument) 645 return argument 646end 647 648function Parser:option(...) 649 local option = Option(...) 650 table.insert(self._options, option) 651 return option 652end 653 654function Parser:flag(...) 655 return self:option():args(0)(...) 656end 657 658function Parser:command(...) 659 local command = Command():add_help(true)(...) 660 command._parent = self 661 table.insert(self._commands, command) 662 return command 663end 664 665function 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 675end 676 677function 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 690end 691 692local usage_welcome = "Usage: " 693 694function 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") 826end 827 828local 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 844end 845 846local 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 902end 903 904-- Automatically wraps lines within given array, 905-- attempting to limit line length to `max_length`. 906-- Existing line splits are preserved. 907local 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 919end 920 921function 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") 971end 972 973local 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 981end 982 983function 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 996end 997 998function 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") 1073end 1074 1075function 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 1113end 1114 1115function 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 1156end 1157 1158function 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 1182end 1183 1184function 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 1209end 1210 1211local function base_name(pathname) 1212 return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname 1213end 1214 1215local function get_short_description(element) 1216 local short = element:_get_description():match("^(.-)%.%s") 1217 return short or element:_get_description():match("^(.-)%.?$") 1218end 1219 1220function 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, " ") 1228end 1229 1230function 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, " ") 1238end 1239 1240function 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 1262end 1263 1264function 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") 1288end 1289 1290function 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 1312end 1313 1314function 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 1342complete -F _%s -o bashdefault -o default %s 1343]=]):format(self._basename, self._basename)) 1344 1345 return table.concat(buf, "\n") 1346end 1347 1348function 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") 1416end 1417 1418function 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}') 1436end 1437 1438function 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") 1455end 1456 1457function 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" 1477end 1478 1479local function fish_escape(string) 1480 return string:gsub("[\\']", "\\%0") 1481end 1482 1483function 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") 1501end 1502 1503function 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 1559end 1560 1561function 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, ([[ 1568function __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" 1574end 1575 1576function __fish_%s_using_command 1577 test (__fish_%s_print_command) = "$argv" 1578 and return 0 1579 or return 1 1580end 1581 1582function __fish_%s_seen_command 1583 string match -q "$argv*" (__fish_%s_print_command) 1584 and return 0 1585 or return 1 1586end]]):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" 1591end 1592 1593local 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 1642end 1643 1644local ElementState = class({ 1645 invocations = 0 1646}) 1647 1648function 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 1655end 1656 1657function ElementState:error(fmt, ...) 1658 self.state:error(fmt, ...) 1659end 1660 1661function 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 1683end 1684 1685function ElementState:default(mode) 1686 return self.element._defmode:find(mode) and self.element._default 1687end 1688 1689local 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") 1698end 1699 1700function ElementState:set_name(alias) 1701 self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) 1702end 1703 1704function 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 1726end 1727 1728function 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 1739end 1740 1741function 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 1751end 1752 1753function ElementState:complete_invocation() 1754 while #self.args < self.element._minargs do 1755 self:pass(self.element._default) 1756 end 1757end 1758 1759function 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 1795end 1796 1797local 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 1807function 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 1813end 1814 1815function ParseState:error(fmt, ...) 1816 self.error_handler(self.parser, fmt:format(...)) 1817end 1818 1819function 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 1861end 1862 1863function 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 1871end 1872 1873function 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 1885end 1886 1887function 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 1899end 1900 1901function 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 1909end 1910 1911function 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 1933end 1934 1935function ParseState:close() 1936 if self.option then 1937 self.option:close() 1938 self.option = nil 1939 end 1940end 1941 1942function 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 1988end 1989 1990function 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 2051end 2052 2053function Parser:error(msg) 2054 io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) 2055 os.exit(1) 2056end 2057 2058-- Compatibility with strict.lua and other checkers: 2059local default_cmdline = rawget(_G, "arg") or {} 2060 2061function Parser:_parse(args, error_handler) 2062 return ParseState(self, error_handler):parse(args or default_cmdline) 2063end 2064 2065function Parser:parse(args) 2066 return self:_parse(args, self.error) 2067end 2068 2069local function xpcall_error_handler(err) 2070 return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) 2071end 2072 2073function Parser:pparse(args) 2074 local parse_error 2075 2076 local ok, result = xpcall(function() 2077 return self:_parse(args, function(_, err) 2078 parse_error = err 2079 error(err, 0) 2080 end) 2081 end, xpcall_error_handler) 2082 2083 if ok then 2084 return true, result 2085 elseif not parse_error then 2086 error(result, 0) 2087 else 2088 return false, parse_error 2089 end 2090end 2091 2092local argparse = {} 2093 2094argparse.version = "0.7.1" 2095 2096setmetatable(argparse, {__call = function(_, ...) 2097 return Parser(default_cmdline[0]):add_help(true)(...) 2098end}) 2099 2100return argparse 2101