aboutsummaryrefslogtreecommitdiff
path: root/src/luarocks/vendor/argparse.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/luarocks/vendor/argparse.lua')
-rw-r--r--src/luarocks/vendor/argparse.lua2103
1 files changed, 2103 insertions, 0 deletions
diff --git a/src/luarocks/vendor/argparse.lua b/src/luarocks/vendor/argparse.lua
new file mode 100644
index 00000000..2c2585dd
--- /dev/null
+++ b/src/luarocks/vendor/argparse.lua
@@ -0,0 +1,2103 @@
1-- The MIT License (MIT)
2
3-- Copyright (c) 2013 - 2018 Peter Melnichenko
4-- 2019 Paul Ouellette
5
6-- Permission is hereby granted, free of charge, to any person obtaining a copy of
7-- this software and associated documentation files (the "Software"), to deal in
8-- the Software without restriction, including without limitation the rights to
9-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10-- the Software, and to permit persons to whom the Software is furnished to do so,
11-- subject to the following conditions:
12
13-- The above copyright notice and this permission notice shall be included in all
14-- copies or substantial portions of the Software.
15
16-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
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 if not debug then
2071 return tostring(err)
2072 end
2073 return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2)
2074end
2075
2076function Parser:pparse(args)
2077 local parse_error
2078
2079 local ok, result = xpcall(function()
2080 return self:_parse(args, function(_, err)
2081 parse_error = err
2082 error(err, 0)
2083 end)
2084 end, xpcall_error_handler)
2085
2086 if ok then
2087 return true, result
2088 elseif not parse_error then
2089 error(result, 0)
2090 else
2091 return false, parse_error
2092 end
2093end
2094
2095local argparse = {}
2096
2097argparse.version = "0.7.0"
2098
2099setmetatable(argparse, {__call = function(_, ...)
2100 return Parser(default_cmdline[0]):add_help(true)(...)
2101end})
2102
2103return argparse