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