shellopts 2.0.0.pre.13 → 2.0.0.pre.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/TODO +15 -135
- data/lib/ext/algorithm.rb +14 -0
- data/lib/ext/ruby_env.rb +8 -0
- data/lib/shellopts.rb +90 -228
- data/lib/shellopts/args.rb +1 -1
- data/lib/shellopts/ast/command.rb +101 -30
- data/lib/shellopts/ast/dump.rb +28 -0
- data/lib/shellopts/ast/option.rb +8 -14
- data/lib/shellopts/ast/parser.rb +106 -0
- data/lib/shellopts/constants.rb +88 -0
- data/lib/shellopts/exceptions.rb +21 -0
- data/lib/shellopts/formatter.rb +125 -0
- data/lib/shellopts/grammar/analyzer.rb +76 -0
- data/lib/shellopts/grammar/command.rb +67 -60
- data/lib/shellopts/grammar/dump.rb +56 -0
- data/lib/shellopts/grammar/lexer.rb +56 -0
- data/lib/shellopts/grammar/option.rb +49 -60
- data/lib/shellopts/grammar/parser.rb +78 -0
- data/lib/shellopts/version.rb +2 -2
- data/shellopts.gemspec +1 -1
- metadata +13 -15
- data/lib/ext/array.rb +0 -9
- data/lib/main.rb +0 -1
- data/lib/shellopts/ast/node.rb +0 -37
- data/lib/shellopts/ast/program.rb +0 -14
- data/lib/shellopts/compiler.rb +0 -128
- data/lib/shellopts/generator.rb +0 -15
- data/lib/shellopts/grammar/node.rb +0 -33
- data/lib/shellopts/grammar/program.rb +0 -65
- data/lib/shellopts/idr.rb +0 -236
- data/lib/shellopts/main.rb +0 -10
- data/lib/shellopts/option_struct.rb +0 -148
- data/lib/shellopts/parser.rb +0 -106
- data/lib/shellopts/shellopts.rb +0 -123
data/lib/shellopts/args.rb
CHANGED
@@ -1,41 +1,112 @@
|
|
1
1
|
module ShellOpts
|
2
2
|
module Ast
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
attr_accessor :subcommand
|
11
|
-
|
12
|
-
def initialize(grammar, name)
|
13
|
-
super(grammar, name)
|
14
|
-
@options = []
|
3
|
+
# Note that Command is derived from BasicObject to minimize the number of
|
4
|
+
# reserved names
|
5
|
+
class Command < BasicObject
|
6
|
+
def initialize(grammar)
|
7
|
+
@grammar = grammar
|
8
|
+
@options_list = []
|
9
|
+
@options_hash = {}
|
15
10
|
@subcommand = nil
|
11
|
+
@subcommands_hash = {} # have at most one element
|
12
|
+
|
13
|
+
@grammar.opts.each { |opt|
|
14
|
+
if opt.argument?
|
15
|
+
self.instance_eval("def #{opt.ident}() @options_hash[:#{opt.ident}] end")
|
16
|
+
end
|
17
|
+
self.instance_eval("def #{opt.ident}?() @options_hash.key?(:#{opt.ident}) end")
|
18
|
+
}
|
19
|
+
|
20
|
+
@grammar.cmds.each { |cmd|
|
21
|
+
self.instance_eval("def #{cmd.ident}!() @subcommands_hash[:#{cmd.ident}] end")
|
22
|
+
}
|
16
23
|
end
|
17
24
|
|
18
|
-
#
|
19
|
-
def
|
20
|
-
|
25
|
+
# Return true if the option was used. Defined in #initialize for each option
|
26
|
+
# def <option>?() end
|
27
|
+
|
28
|
+
# Return the value of the option. Note that repeated options have their
|
29
|
+
# values aggregated into an array. Defined in #initialize for each option
|
30
|
+
# def <option>() end
|
31
|
+
|
32
|
+
# List of Ast::Option objects in the same order as on the command line
|
33
|
+
def options() @options_list end
|
34
|
+
|
35
|
+
# Hash from option identifier to option value. Note that repeated options
|
36
|
+
# have their values aggregated into an array
|
37
|
+
def [](ident) @options_hash[ident].argument end
|
38
|
+
|
39
|
+
# Return the sub-command Command object or nil if not present. Defined in
|
40
|
+
# #initialize for each sub-command
|
41
|
+
# def <command>!() end
|
42
|
+
|
43
|
+
# The sub-command identifier or nil if not present
|
44
|
+
def subcommand() @subcommand && Command.grammar(@subcommand).ident end
|
45
|
+
|
46
|
+
# The sub-command Command object or nil if not present
|
47
|
+
def subcommand!() @subcommand end
|
48
|
+
|
49
|
+
# Class-level accessor methods
|
50
|
+
def self.program?(command) command.__send__(:__is_program__) end
|
51
|
+
def self.grammar(command) command.__send__(:__get_grammar__) end
|
52
|
+
|
53
|
+
# Class-level mutating methods
|
54
|
+
def self.add_option(command, option) command.__send__(:__add_option__, option) end
|
55
|
+
def self.add_command(command, subcommand) command.__send__(:__add_command__, subcommand) end
|
56
|
+
|
57
|
+
private
|
58
|
+
# True if this is a Program object
|
59
|
+
def __is_program__() false end
|
60
|
+
|
61
|
+
# Get grammar
|
62
|
+
def __get_grammar__()
|
63
|
+
@grammar
|
21
64
|
end
|
22
65
|
|
23
|
-
#
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
66
|
+
# Add an option. Only used from the parser
|
67
|
+
def __add_option__(option)
|
68
|
+
@options_list << option
|
69
|
+
if option.grammar.repeatable?
|
70
|
+
(@options_hash[option.grammar.ident] ||= []) << option.argument
|
71
|
+
else
|
72
|
+
@options_hash[option.grammar.ident] = option.argument
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set sub-command. Only used from the parser
|
77
|
+
def __add_command__(command)
|
78
|
+
ident = Command.grammar(command).ident
|
79
|
+
@subcommand = command
|
80
|
+
@subcommands_hash[ident] = command
|
37
81
|
end
|
38
|
-
|
82
|
+
end
|
83
|
+
|
84
|
+
class Program < Command
|
85
|
+
def __is_program__() true end
|
39
86
|
end
|
40
87
|
end
|
41
88
|
end
|
89
|
+
|
90
|
+
# # TODO: Create class-level methods for access
|
91
|
+
# private
|
92
|
+
# # Return class of object. #class is not defined for BasicObjects so this
|
93
|
+
# # method provides an alternative way of getting the class
|
94
|
+
# def self.class_of(object)
|
95
|
+
# # https://stackoverflow.com/a/18621313/2130986
|
96
|
+
# ::Kernel.instance_method(:class).bind(object).call
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# # Class method implementation of ObjectStruct#instance_variable_set that is
|
100
|
+
# # not defined in a BasicObject
|
101
|
+
# def self.set_variable(this, var, value)
|
102
|
+
# # https://stackoverflow.com/a/18621313/2130986
|
103
|
+
# ::Kernel.instance_method(:instance_variable_set).bind(this).call(var, value)
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# # Class method implementation of ObjectStruct#instance_variable_get that is
|
107
|
+
# # not defined in a BasicObject
|
108
|
+
# def self.get_variable(this, var)
|
109
|
+
# # https://stackoverflow.com/a/18621313/2130986
|
110
|
+
# ::Kernel.instance_method(:instance_variable_get).bind(this).call(var)
|
111
|
+
# end
|
112
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ShellOpts
|
2
|
+
module Ast
|
3
|
+
class Command < BasicObject
|
4
|
+
def dump
|
5
|
+
klass = __is_program__ ? "Program" : "Command"
|
6
|
+
::Kernel.puts "#{@grammar.ident.inspect} (#{klass})"
|
7
|
+
::Kernel.indent {
|
8
|
+
if !options.empty?
|
9
|
+
options.map(&:dump)
|
10
|
+
end
|
11
|
+
if subcommand
|
12
|
+
subcommand!.dump
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Option
|
19
|
+
def dump
|
20
|
+
puts "#{grammar.ident.inspect} (Option)"
|
21
|
+
indent {
|
22
|
+
puts "name: #{name.inspect}"
|
23
|
+
puts "argument: #{argument.inspect}"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/shellopts/ast/option.rb
CHANGED
@@ -1,21 +1,15 @@
|
|
1
1
|
module ShellOpts
|
2
2
|
module Ast
|
3
|
-
class Option
|
4
|
-
|
5
|
-
attr_reader :
|
3
|
+
class Option
|
4
|
+
attr_reader :grammar
|
5
|
+
attr_reader :name # The actual name used on the command line
|
6
|
+
attr_reader :argument
|
6
7
|
|
7
|
-
def initialize(grammar, name,
|
8
|
-
|
9
|
-
@
|
8
|
+
def initialize(grammar, name, argument)
|
9
|
+
@grammar = grammar
|
10
|
+
@name = name
|
11
|
+
@argument = argument
|
10
12
|
end
|
11
|
-
|
12
|
-
def values() value end
|
13
|
-
|
14
|
-
# :nocov:
|
15
|
-
def dump
|
16
|
-
super { puts "values: #{values.inspect}" }
|
17
|
-
end
|
18
|
-
# :nocov:
|
19
13
|
end
|
20
14
|
end
|
21
15
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module ShellOpts
|
2
|
+
module Ast
|
3
|
+
# Parse a subcommand
|
4
|
+
class Parser
|
5
|
+
def initialize(grammar, argv)
|
6
|
+
@grammar, @argv = grammar, argv.dup
|
7
|
+
@seen_options = {} # Used to keep track of repeated options
|
8
|
+
@current = nil # Current command
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
@current = program = Program.new(@grammar)
|
13
|
+
parse_command(program)
|
14
|
+
cmd = Command.grammar(@current)
|
15
|
+
!cmd.virtual? or error("'%s' command requires a sub-command")
|
16
|
+
[program, Args.new(cmd, @argv)]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse(grammar, argv)
|
20
|
+
self.new(grammar, argv).call
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def error(message)
|
25
|
+
grammar = Command.grammar(@current)
|
26
|
+
raise Error.new(grammar), message % grammar.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_command(command)
|
30
|
+
@seen_options = {} # Every new command resets the seen options
|
31
|
+
while arg = @argv.first
|
32
|
+
if arg == "--"
|
33
|
+
@argv.shift
|
34
|
+
break
|
35
|
+
elsif arg.start_with?("-")
|
36
|
+
parse_option(command)
|
37
|
+
elsif cmd = Command.grammar(command).commands[arg]
|
38
|
+
@argv.shift
|
39
|
+
@current = subcommand = Ast::Command.new(cmd)
|
40
|
+
Command.add_command(command, subcommand)
|
41
|
+
parse_command(subcommand)
|
42
|
+
break
|
43
|
+
else
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_option(command)
|
50
|
+
# Split into name and argument
|
51
|
+
case @argv.first
|
52
|
+
when /^--(.+?)(?:=(.*))?$/
|
53
|
+
name, arg, short = $1, $2, false
|
54
|
+
opt_name = "--#{name}"
|
55
|
+
when /^-(.)(.+)?$/
|
56
|
+
name, arg, short = $1, $2, true
|
57
|
+
opt_name = "-#{name}"
|
58
|
+
end
|
59
|
+
@argv.shift
|
60
|
+
|
61
|
+
option = Command.grammar(command).options[name] or error "Unknown option '#{opt_name}'"
|
62
|
+
!@seen_options.key?(option.ident) || option.repeatable? or error "Duplicate option '#{opt_name}'"
|
63
|
+
@seen_options[option.ident] = true
|
64
|
+
|
65
|
+
# Parse (optional) argument
|
66
|
+
if option.argument?
|
67
|
+
if arg.nil? && !option.optional?
|
68
|
+
if !@argv.empty?
|
69
|
+
arg = @argv.shift
|
70
|
+
else
|
71
|
+
error "Missing argument for option '#{opt_name}'"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
arg &&= parse_option_arg(option, name, arg)
|
75
|
+
elsif arg && short
|
76
|
+
@argv.unshift("-#{arg}")
|
77
|
+
arg = nil
|
78
|
+
elsif !arg.nil?
|
79
|
+
error "No argument allowed for option '#{opt_name}'"
|
80
|
+
end
|
81
|
+
|
82
|
+
Command.add_option(command, Option.new(option, name, arg))
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_option_arg(option, name, arg)
|
86
|
+
if option.string?
|
87
|
+
arg
|
88
|
+
elsif arg == ""
|
89
|
+
nil
|
90
|
+
elsif option.integer?
|
91
|
+
arg =~ /^-?\d+$/ or error "Illegal integer in '#{name}' argument: '#{arg}'"
|
92
|
+
arg.to_i
|
93
|
+
else # option.float?
|
94
|
+
# https://stackoverflow.com/a/21891705/2130986
|
95
|
+
arg =~ /^[+-]?(?:0|[1-9]\d*)(?:\.(?:\d*[1-9]|0))?$/ or
|
96
|
+
error "Illegal float in '#{name}' argument: '#{arg}'"
|
97
|
+
arg.to_f
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
|
2
|
+
module ShellOpts
|
3
|
+
# FIXME: An option group is -abcd, an option list is a,b,c,d
|
4
|
+
module Constants
|
5
|
+
# Short and long option names
|
6
|
+
SHORT_OPTION_NAME_SUB_RE = /[a-zA-Z0-9]/
|
7
|
+
LONG_OPTION_NAME_SUB_RE = /[a-z](?:[\w-]*\w)/
|
8
|
+
OPTION_NAME_SUB_RE = /#{SHORT_OPTION_NAME_SUB_RE}|#{LONG_OPTION_NAME_SUB_RE}/
|
9
|
+
|
10
|
+
# Initial option in a group
|
11
|
+
INITIAL_SHORT_OPTION_SUB_RE = /[-+]#{SHORT_OPTION_NAME_SUB_RE}/
|
12
|
+
INITIAL_LONG_OPTION_SUB_RE = /(?:--|\+\+)#{LONG_OPTION_NAME_SUB_RE}/
|
13
|
+
INITIAL_OPTION_SUB_RE = /#{INITIAL_SHORT_OPTION_SUB_RE}|#{INITIAL_LONG_OPTION_SUB_RE}/
|
14
|
+
|
15
|
+
# A list of short and long options
|
16
|
+
OPTION_GROUP_SUB_RE = /#{INITIAL_OPTION_SUB_RE}(?:,#{OPTION_NAME_SUB_RE})*/
|
17
|
+
|
18
|
+
# Option argument
|
19
|
+
OPTION_ARG_SUB_RE = /[A-Z](?:[A-Z0-9_-]*[A-Z0-9])?/
|
20
|
+
|
21
|
+
# Matches option flags and argument. It defines the following captures
|
22
|
+
#
|
23
|
+
# $1 - Argument flag ('=')
|
24
|
+
# $2 - Type flag ('#' or '$')
|
25
|
+
# $3 - Argument name
|
26
|
+
# $4 - Optional flag ('?')
|
27
|
+
#
|
28
|
+
OPTION_FLAGS_SUB_RE = /(=)(#|\$)?(#{OPTION_ARG_SUB_RE})?(\?)?/
|
29
|
+
|
30
|
+
# Matches a declaration of an option. The RE defines the following captures:
|
31
|
+
#
|
32
|
+
# $1 - Option group
|
33
|
+
# $2 - Argument flag ('=')
|
34
|
+
# $3 - Type flag ('#' or '$')
|
35
|
+
# $4 - Argument name
|
36
|
+
# $5 - Optional flag ('?')
|
37
|
+
#
|
38
|
+
OPTION_SUB_RE = /(#{OPTION_GROUP_SUB_RE})#{OPTION_FLAGS_SUB_RE}?/
|
39
|
+
|
40
|
+
# Command and command paths
|
41
|
+
COMMAND_IDENT_SUB_RE = /[a-z](?:[a-z0-9_-]*[a-z0-9])?/
|
42
|
+
COMMAND_SUB_RE = /#{COMMAND_IDENT_SUB_RE}!/
|
43
|
+
COMMAND_PATH_SUB_RE = /#{COMMAND_IDENT_SUB_RE}(?:\.#{COMMAND_IDENT_SUB_RE})*!/
|
44
|
+
|
45
|
+
# Command argument
|
46
|
+
ARGUMENT_SUB_RE = /[A-Z][A-Z0-9_-]*[A-Z0-9](?:\.\.\.)?/
|
47
|
+
ARGUMENT_EXPR_SUB_RE = /\[?#{ARGUMENT_SUB_RE}(?:#{ARGUMENT_SUB_RE}|[\[\]\|\s])*/
|
48
|
+
|
49
|
+
# Matches a line starting with a command or an option
|
50
|
+
SCAN_RE = /^(?:#{COMMAND_PATH_SUB_RE}|#{OPTION_SUB_RE})(?:\s+.*)?$/
|
51
|
+
|
52
|
+
|
53
|
+
# Create anchored REs for all SUB_REs
|
54
|
+
self.constants.each { |c|
|
55
|
+
next if c.to_s !~ /_SUB_RE$/
|
56
|
+
sub_re = self.const_get(c)
|
57
|
+
next if !sub_re.is_a?(Regexp)
|
58
|
+
re = /^#{sub_re}$/
|
59
|
+
name = c.to_s.sub(/_SUB_RE$/, "_RE")
|
60
|
+
self.const_set(name, re)
|
61
|
+
}
|
62
|
+
|
63
|
+
# Method names reserved by the BasicObject class
|
64
|
+
BASIC_OBJECT_RESERVED_WORDS = %w(
|
65
|
+
! != == __id__ __send__ equal? instance_eval instance_exec method_missing
|
66
|
+
singleton_method_added singleton_method_removed singleton_method_undefined)
|
67
|
+
|
68
|
+
# Method names reserved by the Ast::Command class
|
69
|
+
AST_COMMAND_RESERVED_WORDS = %w(
|
70
|
+
initialize options subcommand __is_program__ __get_grammar__
|
71
|
+
__add_option__ __add_command__)
|
72
|
+
|
73
|
+
# Reserved option names
|
74
|
+
OPTION_RESERVED_WORDS =
|
75
|
+
(BASIC_OBJECT_RESERVED_WORDS + AST_COMMAND_RESERVED_WORDS).grep(OPTION_NAME_RE)
|
76
|
+
|
77
|
+
# Reserved command names
|
78
|
+
COMMAND_RESERVED_WORDS = %w(subcommand)
|
79
|
+
end
|
80
|
+
|
81
|
+
include Constants
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module ShellOpts
|
3
|
+
class CompileError < StandardError; end
|
4
|
+
|
5
|
+
class ShellOptsError < RuntimeError; end
|
6
|
+
|
7
|
+
class Error < ShellOptsError
|
8
|
+
attr_reader :subject
|
9
|
+
|
10
|
+
def initialize(subject = nil)
|
11
|
+
super()
|
12
|
+
@subject = subject
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Fail < ShellOptsError; end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NotYet < NotImplementedError; end
|
20
|
+
class NotThis < ScriptError; end
|
21
|
+
class NotHere < ScriptError; end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
|
2
|
+
require 'ext/algorithm'
|
3
|
+
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
module ShellOpts
|
7
|
+
class Formatter
|
8
|
+
# Return string describing usage of command
|
9
|
+
def self.usage_string(command, levels: 1, margin: "")
|
10
|
+
elements(command, levels: levels, help: false).map { |line|
|
11
|
+
"#{margin}#{line}"
|
12
|
+
}.join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return string with help for the given command
|
16
|
+
def self.help_string(command, levels: 10, margin: "", tab: " ")
|
17
|
+
io = StringIO.new
|
18
|
+
elements(command, levels: levels, help: true).each { |head, texts, options|
|
19
|
+
io.puts "#{margin}#{head}"
|
20
|
+
texts.each { |text| io.puts "#{margin}#{tab}#{text}" }
|
21
|
+
options.each { |opt_head, opt_texts|
|
22
|
+
io.puts
|
23
|
+
io.puts "#{margin}#{tab}#{opt_head}"
|
24
|
+
opt_texts.each { |text| io.puts "#{margin}#{tab*2}#{text}" }
|
25
|
+
}
|
26
|
+
io.puts
|
27
|
+
}
|
28
|
+
io.string[0..-2]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def self.elements(command, levels: 1, help: false)
|
33
|
+
result = []
|
34
|
+
recursive_elements(result, command, levels: levels, help: help)
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.recursive_elements(acc, command, levels: 1, help: false)
|
39
|
+
cmds = (command.virtual? ? command.cmds : [command])
|
40
|
+
cmds.each { |cmd|
|
41
|
+
if levels == 1 || cmd.cmds.empty?
|
42
|
+
usage = (
|
43
|
+
path_elements(cmd) +
|
44
|
+
option_elements(cmd) +
|
45
|
+
subcommand_element(cmd) +
|
46
|
+
argument_elements(cmd)
|
47
|
+
).compact.join(" ")
|
48
|
+
if help
|
49
|
+
opts = []
|
50
|
+
cmd.opts.each { |opt|
|
51
|
+
next if opt.text.empty?
|
52
|
+
opts << [option_help(opt), opt.text]
|
53
|
+
}
|
54
|
+
acc << [usage, cmd.text, opts]
|
55
|
+
else
|
56
|
+
acc << usage
|
57
|
+
end
|
58
|
+
else
|
59
|
+
cmd.cmds.each { |subcmd|
|
60
|
+
recursive_elements(acc, subcmd, levels: levels - 1, help: help)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return command line usage string
|
67
|
+
def self.command(cmd)
|
68
|
+
(path_elements(cmd) + option_elements(cmd) + argument_elements(cmd)).compact.join(" ")
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.path_elements(cmd)
|
72
|
+
Algorithm.follow(cmd, :parent).map { |parent| parent.name }.reverse
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.option_elements(cmd)
|
76
|
+
elements = []
|
77
|
+
collapsable_opts, other_opts = cmd.opts.partition { |opt| opt.shortname && !opt.argument? }
|
78
|
+
|
79
|
+
if !collapsable_opts.empty?
|
80
|
+
elements << "-" + collapsable_opts.map(&:shortname).join
|
81
|
+
end
|
82
|
+
|
83
|
+
elements + other_opts.map { |opt|
|
84
|
+
if opt.shortname
|
85
|
+
"-#{opt.shortname} #{opt.argument_name}" # We know opt has an argument
|
86
|
+
elsif opt.argument?
|
87
|
+
"--#{opt.longname}=#{opt.argument_name}"
|
88
|
+
else
|
89
|
+
"--#{opt.longname}"
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.option_help(opt)
|
95
|
+
result = opt.names.map { |name|
|
96
|
+
if name.size == 1
|
97
|
+
"-#{name}"
|
98
|
+
else
|
99
|
+
"--#{name}"
|
100
|
+
end
|
101
|
+
}.join(", ")
|
102
|
+
if opt.argument?
|
103
|
+
if opt.longname
|
104
|
+
result += "=#{opt.argument_name}"
|
105
|
+
else
|
106
|
+
result += " #{opt.argument_name}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
result
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.subcommand_element(cmd)
|
113
|
+
!cmd.cmds.empty? ? [cmd.cmds.map(&:name).join("|")] : []
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.argument_elements(cmd)
|
117
|
+
cmd.args
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.help_element(cmd)
|
121
|
+
text.map { |l| l.sub(/^\s*# /, "").rstrip }.join(" ")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|