aboutsummaryrefslogtreecommitdiff
path: root/vendor/argparse.lua
diff options
context:
space:
mode:
authortobil4sk <tobil4sk@outlook.com>2026-02-03 22:47:50 +0000
committerGitHub <noreply@github.com>2026-02-03 19:47:50 -0300
commit47301d83aba58925e1b9594023621ebb27070cdb (patch)
tree73021b5366687ec1683b9e66505e74f22f71d31b /vendor/argparse.lua
parentacf1f47e7f1b1ecbc147e41cae51ddfd06ad898d (diff)
downloadluarocks-main.tar.gz
luarocks-main.tar.bz2
luarocks-main.zip
Improve flexibility around vendored librariesmain
compat53 is vendored since #1757 as it is required to run luarocks with lua 5.1 or 5.2. However, this introduced some issues as the GNUmakefile install rule places these in the same place where `luarocks install compat53` would install them. This means you get conflicts if you install the actual package: ``` Warning: /.../prefix/share/lua/5.1/compat53/init.lua is not tracked by this installation of LuaRocks. Moving it to /.../prefix/share/lua/5.1/compat53/init.lua~ Warning: /.../prefix/share/lua/5.1/compat53/module.lua is not tracked by this installation of LuaRocks. Moving it to /.../prefix/share/lua/5.1/compat53/module.lua~ Warning: /.../prefix/share/lua/5.1/compat53/file_mt.lua is not tracked by this installation of LuaRocks. Moving it to /.../prefix/share/lua/5.1/compat53/file_mt.lua~ ``` It is also not ideal for linux package maintainers to include a vendored package, see: https://github.com/luarocks/luarocks/pull/1757#issuecomment-3409873412. To solve these issues, this patchset makes the following changes: - GNUmakefile now places the compat53 files under `luarocks/vendor/compat53` (which is added internally to the luarocks script's `package.path`). This way a user's installation of compat53 does not interfere at all with luarocks one. - Added `--with-system-compat53` option to configure script for external packaging systems. - Fixed install.bat's logic for deciding whether to vendor compat53, as the current script includes it for every version. install.bat already places luarocks sources outside of LUAPATH, so that part can stay as is. I've also inverted the version check to avoid the need for future patches like: #1850.
Diffstat (limited to 'vendor/argparse.lua')
-rw-r--r--vendor/argparse.lua2103
1 files changed, 2103 insertions, 0 deletions
diff --git a/vendor/argparse.lua b/vendor/argparse.lua
new file mode 100644
index 00000000..2c2585dd
--- /dev/null
+++ b/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