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 end 134 135 -- Do not set _name as with other properties. 136 return true 137end} 138 139local 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 165end 166 167local 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} 179end 180 181local actions = {} 182 183local 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 189end} 190 191local option_init = {"init", function(self) 192 self._has_init = true 193end} 194 195local 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 201end} 202 203local 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 229end} 230 231local 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 259local 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 284local 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 308local 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 331function 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 347end 348 349function 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 378end 379 380function 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 390end 391 392function actions.store_true(result, target) 393 result[target] = true 394end 395 396function actions.store_false(result, target) 397 result[target] = false 398end 399 400function actions.store(result, target, argument) 401 result[target] = argument 402end 403 404function actions.count(result, target, _, overwrite) 405 if not overwrite then 406 result[target] = result[target] + 1 407 end 408end 409 410function 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 417end 418 419function 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 429end 430 431function 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 461end 462 463-- Returns placeholder for `narg`-th argument. 464function 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 472end 473 474function Argument:_get_choices_list() 475 return "{" .. table.concat(self._choices, ",") .. "}" 476end 477 478function Argument:_get_default_argname() 479 if self._choices then 480 return self:_get_choices_list() 481 else 482 return "<" .. self._name .. ">" 483 end 484end 485 486function 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 492end 493 494-- Returns labels to be shown in the help message. 495function Argument:_get_label_lines() 496 if self._choices then 497 return {self:_get_choices_list()} 498 else 499 return {self._name} 500 end 501end 502 503function 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 531end 532 533function Command:_get_label_lines() 534 return {table.concat(self._aliases, ", ")} 535end 536 537function 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 547end 548 549function Command:_get_description() 550 return self._summary or self._description or "" 551end 552 553function 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 563end 564 565function Argument:_get_default_target() 566 return self._name 567end 568 569function 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("-", "_")) 581end 582 583function Option:_is_vararg() 584 return self._maxargs ~= self._minargs 585end 586 587function Parser:_get_fullname(exclude_root) 588 local parent = self._parent 589 if exclude_root and not parent then 590 return "" 591 end 592 local buf = {self._name} 593 594 while parent do 595 if not exclude_root or parent._parent then 596 table.insert(buf, 1, parent._name) 597 end 598 parent = parent._parent 599 end 600 601 return table.concat(buf, " ") 602end 603 604function Parser:_update_charset(charset) 605 charset = charset or {} 606 607 for _, command in ipairs(self._commands) do 608 command:_update_charset(charset) 609 end 610 611 for _, option in ipairs(self._options) do 612 for _, alias in ipairs(option._aliases) do 613 charset[alias:sub(1, 1)] = true 614 end 615 end 616 617 return charset 618end 619 620function Parser:argument(...) 621 local argument = Argument(...) 622 table.insert(self._arguments, argument) 623 return argument 624end 625 626function Parser:option(...) 627 local option = Option(...) 628 table.insert(self._options, option) 629 return option 630end 631 632function Parser:flag(...) 633 return self:option():args(0)(...) 634end 635 636function Parser:command(...) 637 local command = Command():add_help(true)(...) 638 command._parent = self 639 table.insert(self._commands, command) 640 return command 641end 642 643function Parser:mutex(...) 644 local elements = {...} 645 646 for i, element in ipairs(elements) do 647 local mt = getmetatable(element) 648 assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) 649 end 650 651 table.insert(self._mutexes, elements) 652 return self 653end 654 655function Parser:group(name, ...) 656 assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) 657 658 local group = {name = name, ...} 659 660 for i, element in ipairs(group) do 661 local mt = getmetatable(element) 662 assert(mt == Option or mt == Argument or mt == Command, 663 ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) 664 end 665 666 table.insert(self._groups, group) 667 return self 668end 669 670local usage_welcome = "Usage: " 671 672function Parser:get_usage() 673 if self._usage then 674 return self._usage 675 end 676 677 local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) 678 local max_usage_width = self:_inherit_property("usage_max_width", 70) 679 local lines = {usage_welcome .. self:_get_fullname()} 680 681 local function add(s) 682 if #lines[#lines]+1+#s <= max_usage_width then 683 lines[#lines] = lines[#lines] .. " " .. s 684 else 685 lines[#lines+1] = (" "):rep(usage_margin) .. s 686 end 687 end 688 689 -- Normally options are before positional arguments in usage messages. 690 -- However, vararg options should be after, because they can't be reliable used 691 -- before a positional argument. 692 -- Mutexes come into play, too, and are shown as soon as possible. 693 -- Overall, output usages in the following order: 694 -- 1. Mutexes that don't have positional arguments or vararg options. 695 -- 2. Options that are not in any mutexes and are not vararg. 696 -- 3. Positional arguments - on their own or as a part of a mutex. 697 -- 4. Remaining mutexes. 698 -- 5. Remaining options. 699 700 local elements_in_mutexes = {} 701 local added_elements = {} 702 local added_mutexes = {} 703 local argument_to_mutexes = {} 704 705 local function add_mutex(mutex, main_argument) 706 if added_mutexes[mutex] then 707 return 708 end 709 710 added_mutexes[mutex] = true 711 local buf = {} 712 713 for _, element in ipairs(mutex) do 714 if not element._hidden and not added_elements[element] then 715 if getmetatable(element) == Option or element == main_argument then 716 table.insert(buf, element:_get_usage()) 717 added_elements[element] = true 718 end 719 end 720 end 721 722 if #buf == 1 then 723 add(buf[1]) 724 elseif #buf > 1 then 725 add("(" .. table.concat(buf, " | ") .. ")") 726 end 727 end 728 729 local function add_element(element) 730 if not element._hidden and not added_elements[element] then 731 add(element:_get_usage()) 732 added_elements[element] = true 733 end 734 end 735 736 for _, mutex in ipairs(self._mutexes) do 737 local is_vararg = false 738 local has_argument = false 739 740 for _, element in ipairs(mutex) do 741 if getmetatable(element) == Option then 742 if element:_is_vararg() then 743 is_vararg = true 744 end 745 else 746 has_argument = true 747 argument_to_mutexes[element] = argument_to_mutexes[element] or {} 748 table.insert(argument_to_mutexes[element], mutex) 749 end 750 751 elements_in_mutexes[element] = true 752 end 753 754 if not is_vararg and not has_argument then 755 add_mutex(mutex) 756 end 757 end 758 759 for _, option in ipairs(self._options) do 760 if not elements_in_mutexes[option] and not option:_is_vararg() then 761 add_element(option) 762 end 763 end 764 765 -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. 766 for _, argument in ipairs(self._arguments) do 767 -- Pick a mutex as a part of which to show this argument, take the first one that's still available. 768 local mutex 769 770 if elements_in_mutexes[argument] then 771 for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do 772 if not added_mutexes[argument_mutex] then 773 mutex = argument_mutex 774 end 775 end 776 end 777 778 if mutex then 779 add_mutex(mutex, argument) 780 else 781 add_element(argument) 782 end 783 end 784 785 for _, mutex in ipairs(self._mutexes) do 786 add_mutex(mutex) 787 end 788 789 for _, option in ipairs(self._options) do 790 add_element(option) 791 end 792 793 if #self._commands > 0 then 794 if self._require_command then 795 add("<command>") 796 else 797 add("[<command>]") 798 end 799 800 add("...") 801 end 802 803 return table.concat(lines, "\n") 804end 805 806local function split_lines(s) 807 if s == "" then 808 return {} 809 end 810 811 local lines = {} 812 813 if s:sub(-1) ~= "\n" then 814 s = s .. "\n" 815 end 816 817 for line in s:gmatch("([^\n]*)\n") do 818 table.insert(lines, line) 819 end 820 821 return lines 822end 823 824local function autowrap_line(line, max_length) 825 -- Algorithm for splitting lines is simple and greedy. 826 local result_lines = {} 827 828 -- Preserve original indentation of the line, put this at the beginning of each result line. 829 -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts 830 -- of the second and the following lines vertically align with the start of the second word. 831 local indentation = line:match("^ *") 832 833 if line:find("^ *[%*%+%-]") then 834 indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") 835 end 836 837 -- Parts of the last line being assembled. 838 local line_parts = {} 839 840 -- Length of the current line. 841 local line_length = 0 842 843 -- Index of the next character to consider. 844 local index = 1 845 846 while true do 847 local word_start, word_finish, word = line:find("([^ ]+)", index) 848 849 if not word_start then 850 -- Ignore trailing spaces, if any. 851 break 852 end 853 854 local preceding_spaces = line:sub(index, word_start - 1) 855 index = word_finish + 1 856 857 if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then 858 -- Either this is the very first word or it fits as an addition to the current line, add it. 859 table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. 860 table.insert(line_parts, word) 861 line_length = line_length + #preceding_spaces + #word 862 else 863 -- Does not fit, finish current line and put the word into a new one. 864 table.insert(result_lines, table.concat(line_parts)) 865 line_parts = {indentation, word} 866 line_length = #indentation + #word 867 end 868 end 869 870 if #line_parts > 0 then 871 table.insert(result_lines, table.concat(line_parts)) 872 end 873 874 if #result_lines == 0 then 875 -- Preserve empty lines. 876 result_lines[1] = "" 877 end 878 879 return result_lines 880end 881 882-- Automatically wraps lines within given array, 883-- attempting to limit line length to `max_length`. 884-- Existing line splits are preserved. 885local function autowrap(lines, max_length) 886 local result_lines = {} 887 888 for _, line in ipairs(lines) do 889 local autowrapped_lines = autowrap_line(line, max_length) 890 891 for _, autowrapped_line in ipairs(autowrapped_lines) do 892 table.insert(result_lines, autowrapped_line) 893 end 894 end 895 896 return result_lines 897end 898 899function Parser:_get_element_help(element) 900 local label_lines = element:_get_label_lines() 901 local description_lines = split_lines(element:_get_description()) 902 903 local result_lines = {} 904 905 -- All label lines should have the same length (except the last one, it has no comma). 906 -- If too long, start description after all the label lines. 907 -- Otherwise, combine label and description lines. 908 909 local usage_margin_len = self:_inherit_property("help_usage_margin", 3) 910 local usage_margin = (" "):rep(usage_margin_len) 911 local description_margin_len = self:_inherit_property("help_description_margin", 25) 912 local description_margin = (" "):rep(description_margin_len) 913 914 local help_max_width = self:_inherit_property("help_max_width") 915 916 if help_max_width then 917 local description_max_width = math.max(help_max_width - description_margin_len, 10) 918 description_lines = autowrap(description_lines, description_max_width) 919 end 920 921 if #label_lines[1] >= (description_margin_len - usage_margin_len) then 922 for _, label_line in ipairs(label_lines) do 923 table.insert(result_lines, usage_margin .. label_line) 924 end 925 926 for _, description_line in ipairs(description_lines) do 927 table.insert(result_lines, description_margin .. description_line) 928 end 929 else 930 for i = 1, math.max(#label_lines, #description_lines) do 931 local label_line = label_lines[i] 932 local description_line = description_lines[i] 933 934 local line = "" 935 936 if label_line then 937 line = usage_margin .. label_line 938 end 939 940 if description_line and description_line ~= "" then 941 line = line .. (" "):rep(description_margin_len - #line) .. description_line 942 end 943 944 table.insert(result_lines, line) 945 end 946 end 947 948 return table.concat(result_lines, "\n") 949end 950 951local function get_group_types(group) 952 local types = {} 953 954 for _, element in ipairs(group) do 955 types[getmetatable(element)] = true 956 end 957 958 return types 959end 960 961function Parser:_add_group_help(blocks, added_elements, label, elements) 962 local buf = {label} 963 964 for _, element in ipairs(elements) do 965 if not element._hidden and not added_elements[element] then 966 added_elements[element] = true 967 table.insert(buf, self:_get_element_help(element)) 968 end 969 end 970 971 if #buf > 1 then 972 table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) 973 end 974end 975 976function Parser:get_help() 977 if self._help then 978 return self._help 979 end 980 981 local blocks = {self:get_usage()} 982 983 local help_max_width = self:_inherit_property("help_max_width") 984 985 if self._description then 986 local description = self._description 987 988 if help_max_width then 989 description = table.concat(autowrap(split_lines(description), help_max_width), "\n") 990 end 991 992 table.insert(blocks, description) 993 end 994 995 -- 1. Put groups containing arguments first, then other arguments. 996 -- 2. Put remaining groups containing options, then other options. 997 -- 3. Put remaining groups containing commands, then other commands. 998 -- Assume that an element can't be in several groups. 999 local groups_by_type = { 1000 [Argument] = {}, 1001 [Option] = {}, 1002 [Command] = {} 1003 } 1004 1005 for _, group in ipairs(self._groups) do 1006 local group_types = get_group_types(group) 1007 1008 for _, mt in ipairs({Argument, Option, Command}) do 1009 if group_types[mt] then 1010 table.insert(groups_by_type[mt], group) 1011 break 1012 end 1013 end 1014 end 1015 1016 local default_groups = { 1017 {name = "Arguments", type = Argument, elements = self._arguments}, 1018 {name = "Options", type = Option, elements = self._options}, 1019 {name = "Commands", type = Command, elements = self._commands} 1020 } 1021 1022 local added_elements = {} 1023 1024 for _, default_group in ipairs(default_groups) do 1025 local type_groups = groups_by_type[default_group.type] 1026 1027 for _, group in ipairs(type_groups) do 1028 self:_add_group_help(blocks, added_elements, group.name .. ":", group) 1029 end 1030 1031 local default_label = default_group.name .. ":" 1032 1033 if #type_groups > 0 then 1034 default_label = "Other " .. default_label:gsub("^.", string.lower) 1035 end 1036 1037 self:_add_group_help(blocks, added_elements, default_label, default_group.elements) 1038 end 1039 1040 if self._epilog then 1041 local epilog = self._epilog 1042 1043 if help_max_width then 1044 epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") 1045 end 1046 1047 table.insert(blocks, epilog) 1048 end 1049 1050 return table.concat(blocks, "\n\n") 1051end 1052 1053function Parser:add_help_command(value) 1054 if value then 1055 assert(type(value) == "string" or type(value) == "table", 1056 ("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(value))) 1057 end 1058 1059 local help = self:command() 1060 :description "Show help for commands." 1061 help:argument "command" 1062 :description "The command to show help for." 1063 :args "?" 1064 :action(function(_, _, cmd) 1065 if not cmd then 1066 print(self:get_help()) 1067 os.exit(0) 1068 else 1069 for _, command in ipairs(self._commands) do 1070 for _, alias in ipairs(command._aliases) do 1071 if alias == cmd then 1072 print(command:get_help()) 1073 os.exit(0) 1074 end 1075 end 1076 end 1077 end 1078 help:error(("unknown command '%s'"):format(cmd)) 1079 end) 1080 1081 if value then 1082 help = help(value) 1083 end 1084 1085 if not help._name then 1086 help "help" 1087 end 1088 1089 help._is_help_command = true 1090 return self 1091end 1092 1093function Parser:_is_shell_safe() 1094 if self._basename then 1095 if self._basename:find("[^%w_%-%+%.]") then 1096 return false 1097 end 1098 else 1099 for _, alias in ipairs(self._aliases) do 1100 if alias:find("[^%w_%-%+%.]") then 1101 return false 1102 end 1103 end 1104 end 1105 for _, option in ipairs(self._options) do 1106 for _, alias in ipairs(option._aliases) do 1107 if alias:find("[^%w_%-%+%.]") then 1108 return false 1109 end 1110 end 1111 if option._choices then 1112 for _, choice in ipairs(option._choices) do 1113 if choice:find("[%s'\"]") then 1114 return false 1115 end 1116 end 1117 end 1118 end 1119 for _, argument in ipairs(self._arguments) do 1120 if argument._choices then 1121 for _, choice in ipairs(argument._choices) do 1122 if choice:find("[%s'\"]") then 1123 return false 1124 end 1125 end 1126 end 1127 end 1128 for _, command in ipairs(self._commands) do 1129 if not command:_is_shell_safe() then 1130 return false 1131 end 1132 end 1133 return true 1134end 1135 1136function Parser:add_complete(value) 1137 if value then 1138 assert(type(value) == "string" or type(value) == "table", 1139 ("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(value))) 1140 end 1141 1142 local complete = self:option() 1143 :description "Output a shell completion script for the specified shell." 1144 :args(1) 1145 :choices {"bash", "zsh", "fish"} 1146 :action(function(_, _, shell) 1147 io.write(self["get_" .. shell .. "_complete"](self)) 1148 os.exit(0) 1149 end) 1150 1151 if value then 1152 complete = complete(value) 1153 end 1154 1155 if not complete._name then 1156 complete "--completion" 1157 end 1158 1159 return self 1160end 1161 1162function Parser:add_complete_command(value) 1163 if value then 1164 assert(type(value) == "string" or type(value) == "table", 1165 ("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(value))) 1166 end 1167 1168 local complete = self:command() 1169 :description "Output a shell completion script." 1170 complete:argument "shell" 1171 :description "The shell to output a completion script for." 1172 :choices {"bash", "zsh", "fish"} 1173 :action(function(_, _, shell) 1174 io.write(self["get_" .. shell .. "_complete"](self)) 1175 os.exit(0) 1176 end) 1177 1178 if value then 1179 complete = complete(value) 1180 end 1181 1182 if not complete._name then 1183 complete "completion" 1184 end 1185 1186 return self 1187end 1188 1189local function base_name(pathname) 1190 return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname 1191end 1192 1193local function get_short_description(element) 1194 local short = element:_get_description():match("^(.-)%.%s") 1195 return short or element:_get_description():match("^(.-)%.?$") 1196end 1197 1198function Parser:_get_options() 1199 local options = {} 1200 for _, option in ipairs(self._options) do 1201 for _, alias in ipairs(option._aliases) do 1202 table.insert(options, alias) 1203 end 1204 end 1205 return table.concat(options, " ") 1206end 1207 1208function Parser:_get_commands() 1209 local commands = {} 1210 for _, command in ipairs(self._commands) do 1211 for _, alias in ipairs(command._aliases) do 1212 table.insert(commands, alias) 1213 end 1214 end 1215 return table.concat(commands, " ") 1216end 1217 1218function Parser:_bash_option_args(buf, indent) 1219 local opts = {} 1220 for _, option in ipairs(self._options) do 1221 if option._choices or option._minargs > 0 then 1222 local compreply 1223 if option._choices then 1224 compreply = 'COMPREPLY=($(compgen -W "' .. table.concat(option._choices, " ") .. '" -- "$cur"))' 1225 else 1226 compreply = 'COMPREPLY=($(compgen -f -- "$cur"))' 1227 end 1228 table.insert(opts, (" "):rep(indent + 4) .. table.concat(option._aliases, "|") .. ")") 1229 table.insert(opts, (" "):rep(indent + 8) .. compreply) 1230 table.insert(opts, (" "):rep(indent + 8) .. "return 0") 1231 table.insert(opts, (" "):rep(indent + 8) .. ";;") 1232 end 1233 end 1234 1235 if #opts > 0 then 1236 table.insert(buf, (" "):rep(indent) .. 'case "$prev" in') 1237 table.insert(buf, table.concat(opts, "\n")) 1238 table.insert(buf, (" "):rep(indent) .. "esac\n") 1239 end 1240end 1241 1242function Parser:_bash_get_cmd(buf, indent) 1243 if #self._commands == 0 then 1244 return 1245 end 1246 1247 table.insert(buf, (" "):rep(indent) .. 'args=("${args[@]:1}")') 1248 table.insert(buf, (" "):rep(indent) .. 'for arg in "${args[@]}"; do') 1249 table.insert(buf, (" "):rep(indent + 4) .. 'case "$arg" in') 1250 1251 for _, command in ipairs(self._commands) do 1252 table.insert(buf, (" "):rep(indent + 8) .. table.concat(command._aliases, "|") .. ")") 1253 if self._parent then 1254 table.insert(buf, (" "):rep(indent + 12) .. 'cmd="$cmd ' .. command._name .. '"') 1255 else 1256 table.insert(buf, (" "):rep(indent + 12) .. 'cmd="' .. command._name .. '"') 1257 end 1258 table.insert(buf, (" "):rep(indent + 12) .. 'opts="$opts ' .. command:_get_options() .. '"') 1259 command:_bash_get_cmd(buf, indent + 12) 1260 table.insert(buf, (" "):rep(indent + 12) .. "break") 1261 table.insert(buf, (" "):rep(indent + 12) .. ";;") 1262 end 1263 1264 table.insert(buf, (" "):rep(indent + 4) .. "esac") 1265 table.insert(buf, (" "):rep(indent) .. "done") 1266end 1267 1268function Parser:_bash_cmd_completions(buf) 1269 local cmd_buf = {} 1270 if self._parent then 1271 self:_bash_option_args(cmd_buf, 12) 1272 end 1273 if #self._commands > 0 then 1274 table.insert(cmd_buf, (" "):rep(12) .. 'COMPREPLY=($(compgen -W "' .. self:_get_commands() .. '" -- "$cur"))') 1275 elseif self._is_help_command then 1276 table.insert(cmd_buf, (" "):rep(12) 1277 .. 'COMPREPLY=($(compgen -W "' 1278 .. self._parent:_get_commands() 1279 .. '" -- "$cur"))') 1280 end 1281 if #cmd_buf > 0 then 1282 table.insert(buf, (" "):rep(8) .. "'" .. self:_get_fullname(true) .. "')") 1283 table.insert(buf, table.concat(cmd_buf, "\n")) 1284 table.insert(buf, (" "):rep(12) .. ";;") 1285 end 1286 1287 for _, command in ipairs(self._commands) do 1288 command:_bash_cmd_completions(buf) 1289 end 1290end 1291 1292function Parser:get_bash_complete() 1293 self._basename = base_name(self._name) 1294 assert(self:_is_shell_safe()) 1295 local buf = {([[ 1296_%s() { 1297 local IFS=$' \t\n' 1298 local args cur prev cmd opts arg 1299 args=("${COMP_WORDS[@]}") 1300 cur="${COMP_WORDS[COMP_CWORD]}" 1301 prev="${COMP_WORDS[COMP_CWORD-1]}" 1302 opts="%s" 1303]]):format(self._basename, self:_get_options())} 1304 1305 self:_bash_option_args(buf, 4) 1306 self:_bash_get_cmd(buf, 4) 1307 if #self._commands > 0 then 1308 table.insert(buf, "") 1309 table.insert(buf, (" "):rep(4) .. 'case "$cmd" in') 1310 self:_bash_cmd_completions(buf) 1311 table.insert(buf, (" "):rep(4) .. "esac\n") 1312 end 1313 1314 table.insert(buf, ([=[ 1315 if [[ "$cur" = -* ]]; then 1316 COMPREPLY=($(compgen -W "$opts" -- "$cur")) 1317 fi 1318} 1319 1320complete -F _%s -o bashdefault -o default %s 1321]=]):format(self._basename, self._basename)) 1322 1323 return table.concat(buf, "\n") 1324end 1325 1326function Parser:_zsh_arguments(buf, cmd_name, indent) 1327 if self._parent then 1328 table.insert(buf, (" "):rep(indent) .. "options=(") 1329 table.insert(buf, (" "):rep(indent + 2) .. "$options") 1330 else 1331 table.insert(buf, (" "):rep(indent) .. "local -a options=(") 1332 end 1333 1334 for _, option in ipairs(self._options) do 1335 local line = {} 1336 if #option._aliases > 1 then 1337 if option._maxcount > 1 then 1338 table.insert(line, '"*"') 1339 end 1340 table.insert(line, "{" .. table.concat(option._aliases, ",") .. '}"') 1341 else 1342 table.insert(line, '"') 1343 if option._maxcount > 1 then 1344 table.insert(line, "*") 1345 end 1346 table.insert(line, option._name) 1347 end 1348 if option._description then 1349 local description = get_short_description(option):gsub('["%]:`$]', "\\%0") 1350 table.insert(line, "[" .. description .. "]") 1351 end 1352 if option._maxargs == math.huge then 1353 table.insert(line, ":*") 1354 end 1355 if option._choices then 1356 table.insert(line, ": :(" .. table.concat(option._choices, " ") .. ")") 1357 elseif option._maxargs > 0 then 1358 table.insert(line, ": :_files") 1359 end 1360 table.insert(line, '"') 1361 table.insert(buf, (" "):rep(indent + 2) .. table.concat(line)) 1362 end 1363 1364 table.insert(buf, (" "):rep(indent) .. ")") 1365 table.insert(buf, (" "):rep(indent) .. "_arguments -s -S \\") 1366 table.insert(buf, (" "):rep(indent + 2) .. "$options \\") 1367 1368 if self._is_help_command then 1369 table.insert(buf, (" "):rep(indent + 2) .. '": :(' .. self._parent:_get_commands() .. ')" \\') 1370 else 1371 for _, argument in ipairs(self._arguments) do 1372 local spec 1373 if argument._choices then 1374 spec = ": :(" .. table.concat(argument._choices, " ") .. ")" 1375 else 1376 spec = ": :_files" 1377 end 1378 if argument._maxargs == math.huge then 1379 table.insert(buf, (" "):rep(indent + 2) .. '"*' .. spec .. '" \\') 1380 break 1381 end 1382 for _ = 1, argument._maxargs do 1383 table.insert(buf, (" "):rep(indent + 2) .. '"' .. spec .. '" \\') 1384 end 1385 end 1386 1387 if #self._commands > 0 then 1388 table.insert(buf, (" "):rep(indent + 2) .. '": :_' .. cmd_name .. '_cmds" \\') 1389 table.insert(buf, (" "):rep(indent + 2) .. '"*:: :->args" \\') 1390 end 1391 end 1392 1393 table.insert(buf, (" "):rep(indent + 2) .. "&& return 0") 1394end 1395 1396function Parser:_zsh_cmds(buf, cmd_name) 1397 table.insert(buf, "\n_" .. cmd_name .. "_cmds() {") 1398 table.insert(buf, " local -a commands=(") 1399 1400 for _, command in ipairs(self._commands) do 1401 local line = {} 1402 if #command._aliases > 1 then 1403 table.insert(line, "{" .. table.concat(command._aliases, ",") .. '}"') 1404 else 1405 table.insert(line, '"' .. command._name) 1406 end 1407 if command._description then 1408 table.insert(line, ":" .. get_short_description(command):gsub('["`$]', "\\%0")) 1409 end 1410 table.insert(buf, " " .. table.concat(line) .. '"') 1411 end 1412 1413 table.insert(buf, ' )\n _describe "command" commands\n}') 1414end 1415 1416function Parser:_zsh_complete_help(buf, cmds_buf, cmd_name, indent) 1417 if #self._commands == 0 then 1418 return 1419 end 1420 1421 self:_zsh_cmds(cmds_buf, cmd_name) 1422 table.insert(buf, "\n" .. (" "):rep(indent) .. "case $words[1] in") 1423 1424 for _, command in ipairs(self._commands) do 1425 local name = cmd_name .. "_" .. command._name 1426 table.insert(buf, (" "):rep(indent + 2) .. table.concat(command._aliases, "|") .. ")") 1427 command:_zsh_arguments(buf, name, indent + 4) 1428 command:_zsh_complete_help(buf, cmds_buf, name, indent + 4) 1429 table.insert(buf, (" "):rep(indent + 4) .. ";;\n") 1430 end 1431 1432 table.insert(buf, (" "):rep(indent) .. "esac") 1433end 1434 1435function Parser:get_zsh_complete() 1436 self._basename = base_name(self._name) 1437 assert(self:_is_shell_safe()) 1438 local buf = {("#compdef %s\n"):format(self._basename)} 1439 local cmds_buf = {} 1440 table.insert(buf, "_" .. self._basename .. "() {") 1441 if #self._commands > 0 then 1442 table.insert(buf, " local context state state_descr line") 1443 table.insert(buf, " typeset -A opt_args\n") 1444 end 1445 self:_zsh_arguments(buf, self._basename, 2) 1446 self:_zsh_complete_help(buf, cmds_buf, self._basename, 2) 1447 table.insert(buf, "\n return 1") 1448 table.insert(buf, "}") 1449 1450 local result = table.concat(buf, "\n") 1451 if #cmds_buf > 0 then 1452 result = result .. "\n" .. table.concat(cmds_buf, "\n") 1453 end 1454 return result .. "\n\n_" .. self._basename .. "\n" 1455end 1456 1457local function fish_escape(string) 1458 return string:gsub("[\\']", "\\%0") 1459end 1460 1461function Parser:_fish_get_cmd(buf, indent) 1462 if #self._commands == 0 then 1463 return 1464 end 1465 1466 table.insert(buf, (" "):rep(indent) .. "set -e cmdline[1]") 1467 table.insert(buf, (" "):rep(indent) .. "for arg in $cmdline") 1468 table.insert(buf, (" "):rep(indent + 4) .. "switch $arg") 1469 1470 for _, command in ipairs(self._commands) do 1471 table.insert(buf, (" "):rep(indent + 8) .. "case " .. table.concat(command._aliases, " ")) 1472 table.insert(buf, (" "):rep(indent + 12) .. "set cmd $cmd " .. command._name) 1473 command:_fish_get_cmd(buf, indent + 12) 1474 table.insert(buf, (" "):rep(indent + 12) .. "break") 1475 end 1476 1477 table.insert(buf, (" "):rep(indent + 4) .. "end") 1478 table.insert(buf, (" "):rep(indent) .. "end") 1479end 1480 1481function Parser:_fish_complete_help(buf, basename) 1482 local prefix = "complete -c " .. basename 1483 table.insert(buf, "") 1484 1485 for _, command in ipairs(self._commands) do 1486 local aliases = table.concat(command._aliases, " ") 1487 local line 1488 if self._parent then 1489 line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") 1490 :format(prefix, basename, self:_get_fullname(true), aliases) 1491 else 1492 line = ("%s -n '__fish_%s_using_command' -xa '%s'"):format(prefix, basename, aliases) 1493 end 1494 if command._description then 1495 line = ("%s -d '%s'"):format(line, fish_escape(get_short_description(command))) 1496 end 1497 table.insert(buf, line) 1498 end 1499 1500 if self._is_help_command then 1501 local line = ("%s -n '__fish_%s_using_command %s' -xa '%s'") 1502 :format(prefix, basename, self:_get_fullname(true), self._parent:_get_commands()) 1503 table.insert(buf, line) 1504 end 1505 1506 for _, option in ipairs(self._options) do 1507 local parts = {prefix} 1508 1509 if self._parent then 1510 table.insert(parts, "-n '__fish_" .. basename .. "_seen_command " .. self:_get_fullname(true) .. "'") 1511 end 1512 1513 for _, alias in ipairs(option._aliases) do 1514 if alias:match("^%-.$") then 1515 table.insert(parts, "-s " .. alias:sub(2)) 1516 elseif alias:match("^%-%-.+") then 1517 table.insert(parts, "-l " .. alias:sub(3)) 1518 end 1519 end 1520 1521 if option._choices then 1522 table.insert(parts, "-xa '" .. table.concat(option._choices, " ") .. "'") 1523 elseif option._minargs > 0 then 1524 table.insert(parts, "-r") 1525 end 1526 1527 if option._description then 1528 table.insert(parts, "-d '" .. fish_escape(get_short_description(option)) .. "'") 1529 end 1530 1531 table.insert(buf, table.concat(parts, " ")) 1532 end 1533 1534 for _, command in ipairs(self._commands) do 1535 command:_fish_complete_help(buf, basename) 1536 end 1537end 1538 1539function Parser:get_fish_complete() 1540 self._basename = base_name(self._name) 1541 assert(self:_is_shell_safe()) 1542 local buf = {} 1543 1544 if #self._commands > 0 then 1545 table.insert(buf, ([[ 1546function __fish_%s_print_command 1547 set -l cmdline (commandline -poc) 1548 set -l cmd]]):format(self._basename)) 1549 self:_fish_get_cmd(buf, 4) 1550 table.insert(buf, ([[ 1551 echo "$cmd" 1552end 1553 1554function __fish_%s_using_command 1555 test (__fish_%s_print_command) = "$argv" 1556 and return 0 1557 or return 1 1558end 1559 1560function __fish_%s_seen_command 1561 string match -q "$argv*" (__fish_%s_print_command) 1562 and return 0 1563 or return 1 1564end]]):format(self._basename, self._basename, self._basename, self._basename)) 1565 end 1566 1567 self:_fish_complete_help(buf, self._basename) 1568 return table.concat(buf, "\n") .. "\n" 1569end 1570 1571local function get_tip(context, wrong_name) 1572 local context_pool = {} 1573 local possible_name 1574 local possible_names = {} 1575 1576 for name in pairs(context) do 1577 if type(name) == "string" then 1578 for i = 1, #name do 1579 possible_name = name:sub(1, i - 1) .. name:sub(i + 1) 1580 1581 if not context_pool[possible_name] then 1582 context_pool[possible_name] = {} 1583 end 1584 1585 table.insert(context_pool[possible_name], name) 1586 end 1587 end 1588 end 1589 1590 for i = 1, #wrong_name + 1 do 1591 possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) 1592 1593 if context[possible_name] then 1594 possible_names[possible_name] = true 1595 elseif context_pool[possible_name] then 1596 for _, name in ipairs(context_pool[possible_name]) do 1597 possible_names[name] = true 1598 end 1599 end 1600 end 1601 1602 local first = next(possible_names) 1603 1604 if first then 1605 if next(possible_names, first) then 1606 local possible_names_arr = {} 1607 1608 for name in pairs(possible_names) do 1609 table.insert(possible_names_arr, "'" .. name .. "'") 1610 end 1611 1612 table.sort(possible_names_arr) 1613 return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" 1614 else 1615 return "\nDid you mean '" .. first .. "'?" 1616 end 1617 else 1618 return "" 1619 end 1620end 1621 1622local ElementState = class({ 1623 invocations = 0 1624}) 1625 1626function ElementState:__call(state, element) 1627 self.state = state 1628 self.result = state.result 1629 self.element = element 1630 self.target = element._target or element:_get_default_target() 1631 self.action, self.result[self.target] = element:_get_action() 1632 return self 1633end 1634 1635function ElementState:error(fmt, ...) 1636 self.state:error(fmt, ...) 1637end 1638 1639function ElementState:convert(argument, index) 1640 local converter = self.element._convert 1641 1642 if converter then 1643 local ok, err 1644 1645 if type(converter) == "function" then 1646 ok, err = converter(argument) 1647 elseif type(converter[index]) == "function" then 1648 ok, err = converter[index](argument) 1649 else 1650 ok = converter[argument] 1651 end 1652 1653 if ok == nil then 1654 self:error(err and "%s" or "malformed argument '%s'", err or argument) 1655 end 1656 1657 argument = ok 1658 end 1659 1660 return argument 1661end 1662 1663function ElementState:default(mode) 1664 return self.element._defmode:find(mode) and self.element._default 1665end 1666 1667local function bound(noun, min, max, is_max) 1668 local res = "" 1669 1670 if min ~= max then 1671 res = "at " .. (is_max and "most" or "least") .. " " 1672 end 1673 1674 local number = is_max and max or min 1675 return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") 1676end 1677 1678function ElementState:set_name(alias) 1679 self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) 1680end 1681 1682function ElementState:invoke() 1683 self.open = true 1684 self.overwrite = false 1685 1686 if self.invocations >= self.element._maxcount then 1687 if self.element._overwrite then 1688 self.overwrite = true 1689 else 1690 local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) 1691 self:error("%s must be used %s", self.name, num_times_repr) 1692 end 1693 else 1694 self.invocations = self.invocations + 1 1695 end 1696 1697 self.args = {} 1698 1699 if self.element._maxargs <= 0 then 1700 self:close() 1701 end 1702 1703 return self.open 1704end 1705 1706function ElementState:check_choices(argument) 1707 if self.element._choices then 1708 for _, choice in ipairs(self.element._choices) do 1709 if argument == choice then 1710 return 1711 end 1712 end 1713 local choices_list = "'" .. table.concat(self.element._choices, "', '") .. "'" 1714 local is_option = getmetatable(self.element) == Option 1715 self:error("%s%s must be one of %s", is_option and "argument for " or "", self.name, choices_list) 1716 end 1717end 1718 1719function ElementState:pass(argument) 1720 self:check_choices(argument) 1721 argument = self:convert(argument, #self.args + 1) 1722 table.insert(self.args, argument) 1723 1724 if #self.args >= self.element._maxargs then 1725 self:close() 1726 end 1727 1728 return self.open 1729end 1730 1731function ElementState:complete_invocation() 1732 while #self.args < self.element._minargs do 1733 self:pass(self.element._default) 1734 end 1735end 1736 1737function ElementState:close() 1738 if self.open then 1739 self.open = false 1740 1741 if #self.args < self.element._minargs then 1742 if self:default("a") then 1743 self:complete_invocation() 1744 else 1745 if #self.args == 0 then 1746 if getmetatable(self.element) == Argument then 1747 self:error("missing %s", self.name) 1748 elseif self.element._maxargs == 1 then 1749 self:error("%s requires an argument", self.name) 1750 end 1751 end 1752 1753 self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) 1754 end 1755 end 1756 1757 local args 1758 1759 if self.element._maxargs == 0 then 1760 args = self.args[1] 1761 elseif self.element._maxargs == 1 then 1762 if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then 1763 args = self.args 1764 else 1765 args = self.args[1] 1766 end 1767 else 1768 args = self.args 1769 end 1770 1771 self.action(self.result, self.target, args, self.overwrite) 1772 end 1773end 1774 1775local ParseState = class({ 1776 result = {}, 1777 options = {}, 1778 arguments = {}, 1779 argument_i = 1, 1780 element_to_mutexes = {}, 1781 mutex_to_element_state = {}, 1782 command_actions = {} 1783}) 1784 1785function ParseState:__call(parser, error_handler) 1786 self.parser = parser 1787 self.error_handler = error_handler 1788 self.charset = parser:_update_charset() 1789 self:switch(parser) 1790 return self 1791end 1792 1793function ParseState:error(fmt, ...) 1794 self.error_handler(self.parser, fmt:format(...)) 1795end 1796 1797function ParseState:switch(parser) 1798 self.parser = parser 1799 1800 if parser._action then 1801 table.insert(self.command_actions, {action = parser._action, name = parser._name}) 1802 end 1803 1804 for _, option in ipairs(parser._options) do 1805 option = ElementState(self, option) 1806 table.insert(self.options, option) 1807 1808 for _, alias in ipairs(option.element._aliases) do 1809 self.options[alias] = option 1810 end 1811 end 1812 1813 for _, mutex in ipairs(parser._mutexes) do 1814 for _, element in ipairs(mutex) do 1815 if not self.element_to_mutexes[element] then 1816 self.element_to_mutexes[element] = {} 1817 end 1818 1819 table.insert(self.element_to_mutexes[element], mutex) 1820 end 1821 end 1822 1823 for _, argument in ipairs(parser._arguments) do 1824 argument = ElementState(self, argument) 1825 table.insert(self.arguments, argument) 1826 argument:set_name() 1827 argument:invoke() 1828 end 1829 1830 self.handle_options = parser._handle_options 1831 self.argument = self.arguments[self.argument_i] 1832 self.commands = parser._commands 1833 1834 for _, command in ipairs(self.commands) do 1835 for _, alias in ipairs(command._aliases) do 1836 self.commands[alias] = command 1837 end 1838 end 1839end 1840 1841function ParseState:get_option(name) 1842 local option = self.options[name] 1843 1844 if not option then 1845 self:error("unknown option '%s'%s", name, get_tip(self.options, name)) 1846 else 1847 return option 1848 end 1849end 1850 1851function ParseState:get_command(name) 1852 local command = self.commands[name] 1853 1854 if not command then 1855 if #self.commands > 0 then 1856 self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) 1857 else 1858 self:error("too many arguments") 1859 end 1860 else 1861 return command 1862 end 1863end 1864 1865function ParseState:check_mutexes(element_state) 1866 if self.element_to_mutexes[element_state.element] then 1867 for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do 1868 local used_element_state = self.mutex_to_element_state[mutex] 1869 1870 if used_element_state and used_element_state ~= element_state then 1871 self:error("%s can not be used together with %s", element_state.name, used_element_state.name) 1872 else 1873 self.mutex_to_element_state[mutex] = element_state 1874 end 1875 end 1876 end 1877end 1878 1879function ParseState:invoke(option, name) 1880 self:close() 1881 option:set_name(name) 1882 self:check_mutexes(option, name) 1883 1884 if option:invoke() then 1885 self.option = option 1886 end 1887end 1888 1889function ParseState:pass(arg) 1890 if self.option then 1891 if not self.option:pass(arg) then 1892 self.option = nil 1893 end 1894 elseif self.argument then 1895 self:check_mutexes(self.argument) 1896 1897 if not self.argument:pass(arg) then 1898 self.argument_i = self.argument_i + 1 1899 self.argument = self.arguments[self.argument_i] 1900 end 1901 else 1902 local command = self:get_command(arg) 1903 self.result[command._target or command._name] = true 1904 1905 if self.parser._command_target then 1906 self.result[self.parser._command_target] = command._name 1907 end 1908 1909 self:switch(command) 1910 end 1911end 1912 1913function ParseState:close() 1914 if self.option then 1915 self.option:close() 1916 self.option = nil 1917 end 1918end 1919 1920function ParseState:finalize() 1921 self:close() 1922 1923 for i = self.argument_i, #self.arguments do 1924 local argument = self.arguments[i] 1925 if #argument.args == 0 and argument:default("u") then 1926 argument:complete_invocation() 1927 else 1928 argument:close() 1929 end 1930 end 1931 1932 if self.parser._require_command and #self.commands > 0 then 1933 self:error("a command is required") 1934 end 1935 1936 for _, option in ipairs(self.options) do 1937 option.name = option.name or ("option '%s'"):format(option.element._name) 1938 1939 if option.invocations == 0 then 1940 if option:default("u") then 1941 option:invoke() 1942 option:complete_invocation() 1943 option:close() 1944 end 1945 end 1946 1947 local mincount = option.element._mincount 1948 1949 if option.invocations < mincount then 1950 if option:default("a") then 1951 while option.invocations < mincount do 1952 option:invoke() 1953 option:close() 1954 end 1955 elseif option.invocations == 0 then 1956 self:error("missing %s", option.name) 1957 else 1958 self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) 1959 end 1960 end 1961 end 1962 1963 for i = #self.command_actions, 1, -1 do 1964 self.command_actions[i].action(self.result, self.command_actions[i].name) 1965 end 1966end 1967 1968function ParseState:parse(args) 1969 for _, arg in ipairs(args) do 1970 local plain = true 1971 1972 if self.handle_options then 1973 local first = arg:sub(1, 1) 1974 1975 if self.charset[first] then 1976 if #arg > 1 then 1977 plain = false 1978 1979 if arg:sub(2, 2) == first then 1980 if #arg == 2 then 1981 if self.options[arg] then 1982 local option = self:get_option(arg) 1983 self:invoke(option, arg) 1984 else 1985 self:close() 1986 end 1987 1988 self.handle_options = false 1989 else 1990 local equals = arg:find "=" 1991 if equals then 1992 local name = arg:sub(1, equals - 1) 1993 local option = self:get_option(name) 1994 1995 if option.element._maxargs <= 0 then 1996 self:error("option '%s' does not take arguments", name) 1997 end 1998 1999 self:invoke(option, name) 2000 self:pass(arg:sub(equals + 1)) 2001 else 2002 local option = self:get_option(arg) 2003 self:invoke(option, arg) 2004 end 2005 end 2006 else 2007 for i = 2, #arg do 2008 local name = first .. arg:sub(i, i) 2009 local option = self:get_option(name) 2010 self:invoke(option, name) 2011 2012 if i ~= #arg and option.element._maxargs > 0 then 2013 self:pass(arg:sub(i + 1)) 2014 break 2015 end 2016 end 2017 end 2018 end 2019 end 2020 end 2021 2022 if plain then 2023 self:pass(arg) 2024 end 2025 end 2026 2027 self:finalize() 2028 return self.result 2029end 2030 2031function Parser:error(msg) 2032 io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) 2033 os.exit(1) 2034end 2035 2036-- Compatibility with strict.lua and other checkers: 2037local default_cmdline = rawget(_G, "arg") or {} 2038 2039function Parser:_parse(args, error_handler) 2040 return ParseState(self, error_handler):parse(args or default_cmdline) 2041end 2042 2043function Parser:parse(args) 2044 return self:_parse(args, self.error) 2045end 2046 2047local function xpcall_error_handler(err) 2048 return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) 2049end 2050 2051function Parser:pparse(args) 2052 local parse_error 2053 2054 local ok, result = xpcall(function() 2055 return self:_parse(args, function(_, err) 2056 parse_error = err 2057 error(err, 0) 2058 end) 2059 end, xpcall_error_handler) 2060 2061 if ok then 2062 return true, result 2063 elseif not parse_error then 2064 error(result, 0) 2065 else 2066 return false, parse_error 2067 end 2068end 2069 2070local argparse = {} 2071 2072argparse.version = "0.7.0" 2073 2074setmetatable(argparse, {__call = function(_, ...) 2075 return Parser(default_cmdline[0]):add_help(true)(...) 2076end}) 2077 2078return argparse 2079