diff options
Diffstat (limited to 'src/luarocks/vendor/argparse.lua')
-rw-r--r-- | src/luarocks/vendor/argparse.lua | 2103 |
1 files changed, 2103 insertions, 0 deletions
diff --git a/src/luarocks/vendor/argparse.lua b/src/luarocks/vendor/argparse.lua new file mode 100644 index 00000000..2c2585dd --- /dev/null +++ b/src/luarocks/vendor/argparse.lua | |||
@@ -0,0 +1,2103 @@ | |||
1 | -- The MIT License (MIT) | ||
2 | |||
3 | -- Copyright (c) 2013 - 2018 Peter Melnichenko | ||
4 | -- 2019 Paul Ouellette | ||
5 | |||
6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
7 | -- this software and associated documentation files (the "Software"), to deal in | ||
8 | -- the Software without restriction, including without limitation the rights to | ||
9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||
10 | -- the Software, and to permit persons to whom the Software is furnished to do so, | ||
11 | -- subject to the following conditions: | ||
12 | |||
13 | -- The above copyright notice and this permission notice shall be included in all | ||
14 | -- copies or substantial portions of the Software. | ||
15 | |||
16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||
18 | -- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||
19 | -- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||
20 | -- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
21 | -- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
22 | |||
23 | local 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 | ||
33 | end | ||
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. | ||
38 | local 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) | ||
110 | end | ||
111 | |||
112 | local 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))) | ||
120 | end | ||
121 | |||
122 | local function typechecked(name, ...) | ||
123 | local types = {...} | ||
124 | return {name, function(_, value) typecheck(name, types, value) end} | ||
125 | end | ||
126 | |||
127 | local 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 | ||
142 | end} | ||
143 | |||
144 | local 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 | ||
155 | end} | ||
156 | |||
157 | local 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 | ||
183 | end | ||
184 | |||
185 | local 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} | ||
197 | end | ||
198 | |||
199 | local actions = {} | ||
200 | |||
201 | local 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 | ||
207 | end} | ||
208 | |||
209 | local option_init = {"init", function(self) | ||
210 | self._has_init = true | ||
211 | end} | ||
212 | |||
213 | local 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 | ||
219 | end} | ||
220 | |||
221 | local 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 | ||
247 | end} | ||
248 | |||
249 | local 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 | |||
277 | local 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 | |||
304 | local 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 | |||
328 | local 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 | |||
353 | function 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 | ||
369 | end | ||
370 | |||
371 | function 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 | ||
400 | end | ||
401 | |||
402 | function 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 | ||
412 | end | ||
413 | |||
414 | function actions.store_true(result, target) | ||
415 | result[target] = true | ||
416 | end | ||
417 | |||
418 | function actions.store_false(result, target) | ||
419 | result[target] = false | ||
420 | end | ||
421 | |||
422 | function actions.store(result, target, argument) | ||
423 | result[target] = argument | ||
424 | end | ||
425 | |||
426 | function actions.count(result, target, _, overwrite) | ||
427 | if not overwrite then | ||
428 | result[target] = result[target] + 1 | ||
429 | end | ||
430 | end | ||
431 | |||
432 | function 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 | ||
439 | end | ||
440 | |||
441 | function 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 | ||
451 | end | ||
452 | |||
453 | function 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 | ||
483 | end | ||
484 | |||
485 | -- Returns placeholder for `narg`-th argument. | ||
486 | function 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 | ||
494 | end | ||
495 | |||
496 | function Argument:_get_choices_list() | ||
497 | return "{" .. table.concat(self._choices, ",") .. "}" | ||
498 | end | ||
499 | |||
500 | function Argument:_get_default_argname() | ||
501 | if self._choices then | ||
502 | return self:_get_choices_list() | ||
503 | else | ||
504 | return "<" .. self._name .. ">" | ||
505 | end | ||
506 | end | ||
507 | |||
508 | function 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 | ||
514 | end | ||
515 | |||
516 | -- Returns labels to be shown in the help message. | ||
517 | function Argument:_get_label_lines() | ||
518 | if self._choices then | ||
519 | return {self:_get_choices_list()} | ||
520 | else | ||
521 | return {self._name} | ||
522 | end | ||
523 | end | ||
524 | |||
525 | function 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 | ||
553 | end | ||
554 | |||
555 | function Command:_get_label_lines() | ||
556 | return {table.concat(self._public_aliases, ", ")} | ||
557 | end | ||
558 | |||
559 | function 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 | ||
569 | end | ||
570 | |||
571 | function Command:_get_description() | ||
572 | return self._summary or self._description or "" | ||
573 | end | ||
574 | |||
575 | function 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 | ||
585 | end | ||
586 | |||
587 | function Argument:_get_default_target() | ||
588 | return self._name | ||
589 | end | ||
590 | |||
591 | function 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("-", "_")) | ||
603 | end | ||
604 | |||
605 | function Option:_is_vararg() | ||
606 | return self._maxargs ~= self._minargs | ||
607 | end | ||
608 | |||
609 | function 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, " ") | ||
624 | end | ||
625 | |||
626 | function 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 | ||
640 | end | ||
641 | |||
642 | function Parser:argument(...) | ||
643 | local argument = Argument(...) | ||
644 | table.insert(self._arguments, argument) | ||
645 | return argument | ||
646 | end | ||
647 | |||
648 | function Parser:option(...) | ||
649 | local option = Option(...) | ||
650 | table.insert(self._options, option) | ||
651 | return option | ||
652 | end | ||
653 | |||
654 | function Parser:flag(...) | ||
655 | return self:option():args(0)(...) | ||
656 | end | ||
657 | |||
658 | function Parser:command(...) | ||
659 | local command = Command():add_help(true)(...) | ||
660 | command._parent = self | ||
661 | table.insert(self._commands, command) | ||
662 | return command | ||
663 | end | ||
664 | |||
665 | function 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 | ||
675 | end | ||
676 | |||
677 | function 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 | ||
690 | end | ||
691 | |||
692 | local usage_welcome = "Usage: " | ||
693 | |||
694 | function 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") | ||
826 | end | ||
827 | |||
828 | local 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 | ||
844 | end | ||
845 | |||
846 | local 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 | ||
902 | end | ||
903 | |||
904 | -- Automatically wraps lines within given array, | ||
905 | -- attempting to limit line length to `max_length`. | ||
906 | -- Existing line splits are preserved. | ||
907 | local 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 | ||
919 | end | ||
920 | |||
921 | function 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") | ||
971 | end | ||
972 | |||
973 | local 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 | ||
981 | end | ||
982 | |||
983 | function 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 | ||
996 | end | ||
997 | |||
998 | function 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") | ||
1073 | end | ||
1074 | |||
1075 | function 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 | ||
1113 | end | ||
1114 | |||
1115 | function 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 | ||
1156 | end | ||
1157 | |||
1158 | function 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 | ||
1182 | end | ||
1183 | |||
1184 | function 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 | ||
1209 | end | ||
1210 | |||
1211 | local function base_name(pathname) | ||
1212 | return pathname:gsub("[/\\]*$", ""):match(".*[/\\]([^/\\]*)") or pathname | ||
1213 | end | ||
1214 | |||
1215 | local function get_short_description(element) | ||
1216 | local short = element:_get_description():match("^(.-)%.%s") | ||
1217 | return short or element:_get_description():match("^(.-)%.?$") | ||
1218 | end | ||
1219 | |||
1220 | function 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, " ") | ||
1228 | end | ||
1229 | |||
1230 | function 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, " ") | ||
1238 | end | ||
1239 | |||
1240 | function 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 | ||
1262 | end | ||
1263 | |||
1264 | function 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") | ||
1288 | end | ||
1289 | |||
1290 | function 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 | ||
1312 | end | ||
1313 | |||
1314 | function 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 | |||
1342 | complete -F _%s -o bashdefault -o default %s | ||
1343 | ]=]):format(self._basename, self._basename)) | ||
1344 | |||
1345 | return table.concat(buf, "\n") | ||
1346 | end | ||
1347 | |||
1348 | function 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") | ||
1416 | end | ||
1417 | |||
1418 | function 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}') | ||
1436 | end | ||
1437 | |||
1438 | function 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") | ||
1455 | end | ||
1456 | |||
1457 | function 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" | ||
1477 | end | ||
1478 | |||
1479 | local function fish_escape(string) | ||
1480 | return string:gsub("[\\']", "\\%0") | ||
1481 | end | ||
1482 | |||
1483 | function 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") | ||
1501 | end | ||
1502 | |||
1503 | function 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 | ||
1559 | end | ||
1560 | |||
1561 | function 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, ([[ | ||
1568 | function __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" | ||
1574 | end | ||
1575 | |||
1576 | function __fish_%s_using_command | ||
1577 | test (__fish_%s_print_command) = "$argv" | ||
1578 | and return 0 | ||
1579 | or return 1 | ||
1580 | end | ||
1581 | |||
1582 | function __fish_%s_seen_command | ||
1583 | string match -q "$argv*" (__fish_%s_print_command) | ||
1584 | and return 0 | ||
1585 | or return 1 | ||
1586 | end]]):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" | ||
1591 | end | ||
1592 | |||
1593 | local 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 | ||
1642 | end | ||
1643 | |||
1644 | local ElementState = class({ | ||
1645 | invocations = 0 | ||
1646 | }) | ||
1647 | |||
1648 | function 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 | ||
1655 | end | ||
1656 | |||
1657 | function ElementState:error(fmt, ...) | ||
1658 | self.state:error(fmt, ...) | ||
1659 | end | ||
1660 | |||
1661 | function 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 | ||
1683 | end | ||
1684 | |||
1685 | function ElementState:default(mode) | ||
1686 | return self.element._defmode:find(mode) and self.element._default | ||
1687 | end | ||
1688 | |||
1689 | local 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") | ||
1698 | end | ||
1699 | |||
1700 | function ElementState:set_name(alias) | ||
1701 | self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) | ||
1702 | end | ||
1703 | |||
1704 | function 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 | ||
1726 | end | ||
1727 | |||
1728 | function 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 | ||
1739 | end | ||
1740 | |||
1741 | function 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 | ||
1751 | end | ||
1752 | |||
1753 | function ElementState:complete_invocation() | ||
1754 | while #self.args < self.element._minargs do | ||
1755 | self:pass(self.element._default) | ||
1756 | end | ||
1757 | end | ||
1758 | |||
1759 | function 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 | ||
1795 | end | ||
1796 | |||
1797 | local 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 | |||
1807 | function 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 | ||
1813 | end | ||
1814 | |||
1815 | function ParseState:error(fmt, ...) | ||
1816 | self.error_handler(self.parser, fmt:format(...)) | ||
1817 | end | ||
1818 | |||
1819 | function 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 | ||
1861 | end | ||
1862 | |||
1863 | function 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 | ||
1871 | end | ||
1872 | |||
1873 | function 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 | ||
1885 | end | ||
1886 | |||
1887 | function 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 | ||
1899 | end | ||
1900 | |||
1901 | function 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 | ||
1909 | end | ||
1910 | |||
1911 | function 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 | ||
1933 | end | ||
1934 | |||
1935 | function ParseState:close() | ||
1936 | if self.option then | ||
1937 | self.option:close() | ||
1938 | self.option = nil | ||
1939 | end | ||
1940 | end | ||
1941 | |||
1942 | function 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 | ||
1988 | end | ||
1989 | |||
1990 | function 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 | ||
2051 | end | ||
2052 | |||
2053 | function Parser:error(msg) | ||
2054 | io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) | ||
2055 | os.exit(1) | ||
2056 | end | ||
2057 | |||
2058 | -- Compatibility with strict.lua and other checkers: | ||
2059 | local default_cmdline = rawget(_G, "arg") or {} | ||
2060 | |||
2061 | function Parser:_parse(args, error_handler) | ||
2062 | return ParseState(self, error_handler):parse(args or default_cmdline) | ||
2063 | end | ||
2064 | |||
2065 | function Parser:parse(args) | ||
2066 | return self:_parse(args, self.error) | ||
2067 | end | ||
2068 | |||
2069 | local 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) | ||
2074 | end | ||
2075 | |||
2076 | function 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 | ||
2093 | end | ||
2094 | |||
2095 | local argparse = {} | ||
2096 | |||
2097 | argparse.version = "0.7.0" | ||
2098 | |||
2099 | setmetatable(argparse, {__call = function(_, ...) | ||
2100 | return Parser(default_cmdline[0]):add_help(true)(...) | ||
2101 | end}) | ||
2102 | |||
2103 | return argparse | ||