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