shellopts 2.0.0.pre.1 → 2.0.0.pre.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/TODO +6 -0
- data/lib/main.rb +1 -0
- data/lib/shellopts.rb +140 -44
- data/lib/shellopts/args.rb +48 -0
- data/lib/shellopts/ast/command.rb +6 -6
- data/lib/shellopts/compiler.rb +19 -21
- data/lib/shellopts/generator.rb +3 -3
- data/lib/shellopts/grammar/command.rb +12 -13
- data/lib/shellopts/grammar/node.rb +11 -3
- data/lib/shellopts/grammar/option.rb +2 -1
- data/lib/shellopts/grammar/program.rb +4 -4
- data/lib/shellopts/idr.rb +71 -44
- data/lib/shellopts/main.rb +10 -0
- data/lib/shellopts/option_struct.rb +62 -159
- data/lib/shellopts/parser.rb +11 -11
- data/lib/shellopts/shellopts.rb +53 -35
- data/lib/shellopts/version.rb +1 -1
- metadata +5 -5
- data/lib/shellopts/messenger.rb +0 -71
- data/lib/shellopts/utils.rb +0 -16
- data/rs +0 -40
data/lib/shellopts/generator.rb
CHANGED
@@ -3,9 +3,9 @@ require 'shellopts/idr.rb'
|
|
3
3
|
|
4
4
|
module ShellOpts
|
5
5
|
module Idr
|
6
|
-
# Generates an Idr::Program from
|
7
|
-
def self.generate(
|
8
|
-
Idr::Program.new(
|
6
|
+
# Generates an Idr::Program from a ShellOpts object
|
7
|
+
def self.generate(shellopts)
|
8
|
+
Idr::Program.new(shellopts)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
@@ -18,7 +18,7 @@ module ShellOpts
|
|
18
18
|
attr_reader :option_list
|
19
19
|
|
20
20
|
# List of commands in declaration order
|
21
|
-
attr_reader :
|
21
|
+
attr_reader :subcommand_list
|
22
22
|
|
23
23
|
# Multihash from option key or names (both short and long names) to option. This
|
24
24
|
# means an option can occur more than once as the hash value
|
@@ -31,9 +31,9 @@ module ShellOpts
|
|
31
31
|
# Sub-commands of this command. Is a multihash from sub-command key or
|
32
32
|
# name to command object. Lazily constructed because subcommands are added
|
33
33
|
# after initialization
|
34
|
-
def
|
35
|
-
@
|
36
|
-
|
34
|
+
def subcommands()
|
35
|
+
@subcommand_multihash ||= @subcommand_list.flat_map { |subcommand|
|
36
|
+
subcommand.identifiers.map { |name| [name, subcommand] }
|
37
37
|
}.to_h
|
38
38
|
end
|
39
39
|
|
@@ -41,16 +41,15 @@ module ShellOpts
|
|
41
41
|
# if this is the root object. name is the name of the command (without
|
42
42
|
# the exclamation mark), and option_list a list of Option objects
|
43
43
|
def initialize(parent, name, option_list)
|
44
|
-
super("#{name}!".to_sym)
|
45
|
-
@name = name
|
44
|
+
super("#{name}!".to_sym, name)
|
46
45
|
parent.attach(self) if parent
|
47
46
|
@option_list = option_list
|
48
|
-
@
|
47
|
+
@subcommand_list = []
|
49
48
|
end
|
50
49
|
|
51
50
|
# Return key for the identifier
|
52
51
|
def identifier2key(ident)
|
53
|
-
options[ident]&.key ||
|
52
|
+
options[ident]&.key || subcommands[ident]&.key
|
54
53
|
end
|
55
54
|
|
56
55
|
# Return list of identifiers for the command
|
@@ -65,16 +64,16 @@ module ShellOpts
|
|
65
64
|
yield if block_given?
|
66
65
|
puts "options:"
|
67
66
|
indent { option_list.each { |opt| opt.dump } }
|
68
|
-
puts "
|
69
|
-
indent {
|
67
|
+
puts "subcommands: "
|
68
|
+
indent { subcommand_list.each { |cmd| cmd.dump } }
|
70
69
|
}
|
71
70
|
end
|
72
71
|
# :nocov:
|
73
72
|
|
74
73
|
protected
|
75
|
-
def attach(
|
76
|
-
|
77
|
-
@
|
74
|
+
def attach(subcommand)
|
75
|
+
subcommand.instance_variable_set(:@parent, self)
|
76
|
+
@subcommand_list << subcommand
|
78
77
|
end
|
79
78
|
end
|
80
79
|
end
|
@@ -10,14 +10,22 @@ module ShellOpts
|
|
10
10
|
# Key (Symbol) of node. Unique within the enclosing command
|
11
11
|
attr_reader :key
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
# Name of node. The name of an option is without the prefixed '-' or
|
14
|
+
# '--', the name of a command is without the suffixed '!'. Note that name
|
15
|
+
# collisions can happen between options and commands names
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
def initialize(key, name)
|
19
|
+
@key, @name = key, name
|
15
20
|
end
|
16
21
|
|
17
22
|
# :nocov:
|
18
23
|
def dump(&block)
|
19
24
|
puts key.inspect
|
20
|
-
indent {
|
25
|
+
indent {
|
26
|
+
puts "name: #{name.inspect}"
|
27
|
+
yield if block_given?
|
28
|
+
}
|
21
29
|
end
|
22
30
|
# :nocov:
|
23
31
|
end
|
@@ -27,7 +27,8 @@ module ShellOpts
|
|
27
27
|
# informal name of the option argument (eg. 'FILE') or nil if not present
|
28
28
|
def initialize(short_names, long_names, flags, label = nil)
|
29
29
|
@key_name = long_names.first || short_names.first
|
30
|
-
|
30
|
+
name = @key_name.sub(/^-+/, "")
|
31
|
+
super(name.to_sym, name)
|
31
32
|
@short_names, @long_names = short_names, long_names
|
32
33
|
@flags = flags.map { |flag| [flag, true] }.to_h
|
33
34
|
@label = label
|
@@ -17,7 +17,7 @@ module ShellOpts
|
|
17
17
|
def usage
|
18
18
|
(
|
19
19
|
render_options(option_list) +
|
20
|
-
|
20
|
+
subcommand_list.map { |cmd| render_subcommand(cmd) } +
|
21
21
|
args
|
22
22
|
).flatten.join(" ")
|
23
23
|
end
|
@@ -32,9 +32,9 @@ module ShellOpts
|
|
32
32
|
# :nocov:
|
33
33
|
|
34
34
|
private
|
35
|
-
def
|
36
|
-
[
|
37
|
-
|
35
|
+
def render_subcommand(subcommand)
|
36
|
+
[subcommand.name] + render_options(subcommand.option_list) +
|
37
|
+
subcommand.subcommand_list.map { |cmd| render_subcommand(cmd) }.flatten
|
38
38
|
end
|
39
39
|
|
40
40
|
def render_options(options)
|
data/lib/shellopts/idr.rb
CHANGED
@@ -13,15 +13,14 @@ module ShellOpts
|
|
13
13
|
# and a value. Options have their (optional) argument as value while
|
14
14
|
# commands use +self+ as value
|
15
15
|
class Node
|
16
|
+
# Parent node. nil for the top-level Program object
|
17
|
+
attr_reader :parent
|
18
|
+
|
16
19
|
# Unique key (within context) for the option or command. nil for the
|
17
20
|
# top-level Program object
|
18
|
-
#
|
19
|
-
# It is usually the first long option if present and else the first short
|
20
|
-
# option turned into a Symbol by first removing prefixed dashed, eg.
|
21
|
-
# '--all' becomes :all
|
22
21
|
attr_reader :key
|
23
22
|
|
24
|
-
# Name of command
|
23
|
+
# Name of command or option as used on the command line
|
25
24
|
attr_reader :name
|
26
25
|
|
27
26
|
# Value of node. This can be a simple value (String, Integer, or Float),
|
@@ -33,10 +32,13 @@ module ShellOpts
|
|
33
32
|
# arguments or if an optional argument is missing.
|
34
33
|
attr_reader :value
|
35
34
|
|
35
|
+
# The top-level Program object
|
36
|
+
def program() @program ||= (parent&.program || self) end
|
37
|
+
|
36
38
|
protected
|
37
39
|
# Copy arguments into instance variables
|
38
|
-
def initialize(ast, key, name, value)
|
39
|
-
@ast, @key, @name, @value = ast, key, name, value
|
40
|
+
def initialize(parent, ast, key, name, value)
|
41
|
+
@parent, @ast, @key, @name, @value = parent, ast, key, name, value
|
40
42
|
end
|
41
43
|
|
42
44
|
# The AST node for this Idr object
|
@@ -54,9 +56,9 @@ module ShellOpts
|
|
54
56
|
protected
|
55
57
|
# Initialize with defauls from the Ast. +value+ is set to true if option
|
56
58
|
# doesn't take an argument
|
57
|
-
def initialize(ast)
|
59
|
+
def initialize(parent, ast)
|
58
60
|
value = ast.grammar.argument? ? ast.value : true
|
59
|
-
super(ast, ast.key, ast.name, value)
|
61
|
+
super(parent, ast, ast.key, ast.name, value)
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
@@ -71,9 +73,9 @@ module ShellOpts
|
|
71
73
|
alias :values :value
|
72
74
|
|
73
75
|
# Name is set to the key name and value to an array of option values
|
74
|
-
def initialize(key, name, options)
|
76
|
+
def initialize(parent, key, name, options)
|
75
77
|
@names = options.map(&:name)
|
76
|
-
super(nil, key, name, options.map(&:value))
|
78
|
+
super(parent, nil, key, name, options.map(&:value))
|
77
79
|
end
|
78
80
|
end
|
79
81
|
|
@@ -91,13 +93,13 @@ module ShellOpts
|
|
91
93
|
attr_reader :subcommand
|
92
94
|
|
93
95
|
# True if ident is declared
|
94
|
-
def declared?(ident) option?(ident) ||
|
96
|
+
def declared?(ident) option?(ident) || subcommand?(ident) end
|
95
97
|
|
96
98
|
# True if ident is declared as an option
|
97
99
|
def option?(ident) grammar.options.key?(ident) end
|
98
100
|
|
99
101
|
# True if ident is declared as a command
|
100
|
-
def
|
102
|
+
def subcommand?(ident) grammar.subcommands.key?(ident) end
|
101
103
|
|
102
104
|
# True if ident is present
|
103
105
|
def key?(ident)
|
@@ -120,8 +122,8 @@ module ShellOpts
|
|
120
122
|
end
|
121
123
|
|
122
124
|
# Apply defaults recursively. Values can be lambdas that will be evaluated to
|
123
|
-
# get the default value
|
124
|
-
def apply(defaults = {}) end
|
125
|
+
# get the default value. TODO
|
126
|
+
def apply(defaults = {}) raise InternalError, "Not implemented" end
|
125
127
|
|
126
128
|
# Return options and command as an array
|
127
129
|
def to_a() @ast.values end
|
@@ -129,49 +131,52 @@ module ShellOpts
|
|
129
131
|
# Return options and command as a hash. The hash also define the
|
130
132
|
# singleton method #subcommand that returns the key of the subcommand
|
131
133
|
#
|
132
|
-
# +
|
133
|
-
# symbolic key, +:name+ use
|
134
|
-
# option and command names
|
134
|
+
# +key_type+ controls the type of keys used: +:key+ (the default) use the
|
135
|
+
# symbolic key, +:name+ use #name. Note that using +:name+ can cause
|
136
|
+
# name collisions between option and command names
|
135
137
|
#
|
136
138
|
# +aliases+ maps from key to replacement key (which could be any object).
|
137
139
|
# +aliases+ can be used to avoid name collisions between options and
|
138
|
-
# commands
|
140
|
+
# commands when using key_format: :name
|
139
141
|
#
|
140
|
-
# IDEA:
|
141
|
-
# IDEA: Add a singleton method #subcommand to the hash
|
142
|
+
# IDEA: Add a singleton methods to the hash with #name, #usage, etc.
|
142
143
|
#
|
143
|
-
def to_h(
|
144
|
+
def to_h(key_type: ::ShellOpts.default_key_type, aliases: {})
|
145
|
+
keys = map_keys(key_type, aliases)
|
144
146
|
value = {}
|
145
147
|
value.define_singleton_method(:subcommand) { nil }
|
146
|
-
options.values.each { |opt|
|
147
|
-
|
148
|
-
!value.key?(ident) or raise ConversionError, "Duplicate key: #{ident.inspect}"
|
148
|
+
options.values.each { |opt| # includes subcommand
|
149
|
+
key = keys[opt.key]
|
149
150
|
case opt
|
150
151
|
when Option
|
151
|
-
value[
|
152
|
+
value[key] = opt.value
|
152
153
|
when Command
|
153
|
-
value[
|
154
|
-
value.define_singleton_method(:subcommand) {
|
154
|
+
value[key] = opt.value.to_h(key_type: key_type, aliases: aliases[opt.key] || {})
|
155
|
+
value.define_singleton_method(:subcommand) { key } # Redefine
|
155
156
|
else
|
157
|
+
# :nocov:
|
156
158
|
raise InternalError, "Oops"
|
159
|
+
# :nocov:
|
157
160
|
end
|
158
161
|
}
|
159
162
|
value
|
160
163
|
end
|
161
164
|
|
162
165
|
# Return options and command as a struct
|
163
|
-
def to_struct(
|
166
|
+
def to_struct(key_type: ::ShellOpts.default_key_type, aliases: {})
|
167
|
+
OptionStruct.new(self, key_type, aliases)
|
168
|
+
end
|
164
169
|
|
165
170
|
protected
|
166
171
|
# Initialize an Idr::Command object and all dependent objects
|
167
|
-
def initialize(ast)
|
168
|
-
super(ast, ast.key, ast.name, self)
|
169
|
-
@option_list = ast.options.map { |node| SimpleOption.new(node) }
|
170
|
-
@subcommand = Command.new(ast.
|
172
|
+
def initialize(parent, ast)
|
173
|
+
super(parent, ast, ast.key, ast.name, self)
|
174
|
+
@option_list = ast.options.map { |node| SimpleOption.new(self, node) }
|
175
|
+
@subcommand = Command.new(self, ast.subcommand) if ast.subcommand
|
171
176
|
@options = @option_list.group_by { |option| option.key }.map { |key, option_list|
|
172
177
|
option =
|
173
178
|
if ast.grammar.options[key].repeated?
|
174
|
-
OptionGroup.new(key, ast.grammar.options[key].key_name, option_list)
|
179
|
+
OptionGroup.new(self, key, ast.grammar.options[key].key_name, option_list)
|
175
180
|
else
|
176
181
|
option_list.first
|
177
182
|
end
|
@@ -179,30 +184,52 @@ module ShellOpts
|
|
179
184
|
}.to_h
|
180
185
|
@options[subcommand.key] = @subcommand if @subcommand
|
181
186
|
end
|
187
|
+
|
188
|
+
# Internal-key to used-key map. Checks for reserved words and
|
189
|
+
# name-collisions
|
190
|
+
def map_keys(key_type, aliases, reserved_words = [])
|
191
|
+
keys = {}
|
192
|
+
used_keys = {}
|
193
|
+
(grammar.option_list + grammar.subcommand_list).each { |node|
|
194
|
+
internal_key = node.key
|
195
|
+
key = aliases[internal_key] || (key_type == :name ? node.name.to_sym : internal_key)
|
196
|
+
!reserved_words.include?(key) or
|
197
|
+
raise ::ShellOpts::ConversionError, "'#{key}' is a reserved word"
|
198
|
+
!used_keys.key?(key) or
|
199
|
+
raise ::ShellOpts::ConversionError, "Name collision between '--#{key}' and '#{key}!'"
|
200
|
+
keys[internal_key] = key
|
201
|
+
used_keys[key] = true
|
202
|
+
}
|
203
|
+
keys
|
204
|
+
end
|
182
205
|
end
|
183
206
|
|
184
207
|
class Program < Command
|
208
|
+
# Name of program
|
209
|
+
def name() @shellopts.name end
|
210
|
+
def name=(name) @shellopts.name = name end
|
211
|
+
|
212
|
+
# Usage string
|
213
|
+
def usage() @shellopts.usage end
|
214
|
+
def usage=(usage) @shellopts.usage = usage end
|
215
|
+
|
185
216
|
# #key is nil for the top-level Program object
|
186
217
|
def key() nil end
|
187
218
|
|
188
219
|
# Remaining command line arguments
|
189
|
-
def args() @
|
190
|
-
|
191
|
-
# Messenger object that is used to emit error messages. It should
|
192
|
-
# implement #error(*args) and #fail(*args)
|
193
|
-
attr_reader :messenger
|
220
|
+
def args() @shellopts.args end
|
194
221
|
|
195
222
|
# Initialize the top-level Idr::Program object
|
196
|
-
def initialize(
|
197
|
-
@
|
198
|
-
super(ast)
|
223
|
+
def initialize(shellopts)
|
224
|
+
@shellopts = shellopts
|
225
|
+
super(nil, shellopts.ast)
|
199
226
|
end
|
200
227
|
|
201
228
|
# Emit error message and a usage description before exiting with status 1
|
202
|
-
def error(*args)
|
229
|
+
def error(*args) @shellopts.error(*error_messages) end
|
203
230
|
|
204
231
|
# Emit error message before exiting with status 1
|
205
|
-
def fail(*args)
|
232
|
+
def fail(*args) @shellopts.fail(*error_messages) end
|
206
233
|
end
|
207
234
|
end
|
208
235
|
end
|
@@ -3,88 +3,8 @@ require 'shellopts/shellopts.rb'
|
|
3
3
|
require 'shellopts/idr'
|
4
4
|
|
5
5
|
module ShellOpts
|
6
|
-
|
7
|
-
|
8
|
-
# mark. It doesn't change how options are named
|
9
|
-
def self.new(idr, key = :key, aliases = {})
|
10
|
-
ast = idr.instance_variable_get("@ast")
|
11
|
-
grammar = ast.grammar
|
12
|
-
instance = allocate
|
13
|
-
|
14
|
-
# Generate option accessor methods
|
15
|
-
grammar.option_list.each { |option|
|
16
|
-
key = alias_key(option.key, aliases)
|
17
|
-
instance.instance_eval("def #{key}() @#{key} end")
|
18
|
-
present = set_variable(instance, "@#{key}", idr[option.key])
|
19
|
-
instance.instance_eval("def #{key}?() #{present} end")
|
20
|
-
}
|
21
|
-
|
22
|
-
# Generate #subcommand default methods
|
23
|
-
if !idr.subcommand
|
24
|
-
instance.instance_eval("def subcommand() nil end")
|
25
|
-
instance.instance_eval("def subcommand?() false end")
|
26
|
-
instance.instance_eval("def subcommand!() nil end")
|
27
|
-
end
|
28
|
-
|
29
|
-
# Generate subcommand methods
|
30
|
-
grammar.command_list.each { |command|
|
31
|
-
key = alias_key(command.key, aliases)
|
32
|
-
if command.key == idr.subcommand&.key
|
33
|
-
struct = OptionStruct.new(idr.subcommand, aliases[idr.subcommand.key] || {})
|
34
|
-
set_variable(instance, "@subcommand", struct)
|
35
|
-
instance.instance_eval("def #{key}() @subcommand end")
|
36
|
-
instance.instance_eval("def subcommand() :#{key} end")
|
37
|
-
instance.instance_eval("def subcommand?() true end")
|
38
|
-
instance.instance_eval("def subcommand!() @subcommand end")
|
39
|
-
else
|
40
|
-
instance.instance_eval("def #{key}() nil end")
|
41
|
-
end
|
42
|
-
}
|
43
|
-
|
44
|
-
instance
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
# Return class of object. #class is not defined for BasicObjects so this
|
49
|
-
# method provides an alternative way of getting the class
|
50
|
-
def self.class_of(object)
|
51
|
-
# https://stackoverflow.com/a/18621313/2130986
|
52
|
-
::Kernel.instance_method(:class).bind(object).call
|
53
|
-
end
|
54
|
-
|
55
|
-
# Replace key with alias and check against the list of reserved words
|
56
|
-
def self.alias_key(internal_key, aliases)
|
57
|
-
key = aliases[internal_key] || internal_key
|
58
|
-
!RESERVED_WORDS.include?(key.to_s) or
|
59
|
-
raise ::ShellOpts::ConversionError, "Can't create struct: '#{key}' is a reserved word"
|
60
|
-
key
|
61
|
-
end
|
62
|
-
|
63
|
-
# Shorthand helper method. Substitutes the undefined ObjectStruct#instance_variable_set
|
64
|
-
def self.set_variable(this, var, value)
|
65
|
-
# https://stackoverflow.com/a/18621313/2130986
|
66
|
-
::Kernel.instance_method(:instance_variable_set).bind(this).call(var, value)
|
67
|
-
end
|
68
|
-
|
69
|
-
BASIC_OBJECT_RESERVED_WORDS = %w(
|
70
|
-
__id__ __send__ instance_eval instance_exec method_missing
|
71
|
-
singleton_method_added singleton_method_removed
|
72
|
-
singleton_method_undefined)
|
73
|
-
OPTIONS_STRUCT_RESERVED_WORDS = %w(subcommand)
|
74
|
-
RESERVED_WORDS = BASIC_OBJECT_RESERVED_WORDS + OPTIONS_STRUCT_RESERVED_WORDS
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
__END__
|
86
|
-
|
87
|
-
module ShellOpts
|
6
|
+
# FIXME: Outdated
|
7
|
+
#
|
88
8
|
# Struct representation of options. Usually created by ShellOpts::to_struct
|
89
9
|
#
|
90
10
|
# OptionStruct objects give easy access to configuration option values but
|
@@ -136,110 +56,93 @@ module ShellOpts
|
|
136
56
|
# reserved words or with the special #command method
|
137
57
|
#
|
138
58
|
class OptionStruct < BasicObject
|
139
|
-
# Create a
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
59
|
+
# Create a OptionStruct object recursively from an Idr::Command object
|
60
|
+
def self.new(idr, key_type, aliases = {})
|
61
|
+
# Shorthands
|
62
|
+
ast = idr.instance_variable_get("@ast")
|
63
|
+
grammar = ast.grammar
|
64
|
+
|
65
|
+
# Get key map
|
66
|
+
keys = idr.send(:map_keys, key_type, aliases, RESERVED_WORDS)
|
67
|
+
|
68
|
+
# Allocate OptionStruct instance
|
145
69
|
instance = allocate
|
146
|
-
set_variable(instance, "@__options_hash__", options_hash)
|
147
70
|
|
148
|
-
#
|
149
|
-
|
150
|
-
!RESERVED_WORDS.include?(key.to_s) or
|
151
|
-
raise ::ShellOpts::ConversionError, "Can't create struct: '#{key}' is a reserved word"
|
152
|
-
key != command_alias or
|
153
|
-
raise ::ShellOpts::ConversionError, "Can't create struct: '#{key}' is the command alias"
|
154
|
-
}
|
71
|
+
# Set reference to Idr object. Is currently unused
|
72
|
+
set_variable(instance, "@__idr__", idr)
|
155
73
|
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
instance.instance_eval("def #{
|
74
|
+
# Generate general option accessor methods
|
75
|
+
grammar.option_list.each { |option|
|
76
|
+
key = keys[option.key]
|
77
|
+
instance.instance_eval("def #{key}() @#{key} end")
|
78
|
+
instance.instance_eval("def #{key}?() false end")
|
160
79
|
}
|
161
|
-
|
162
|
-
|
80
|
+
|
81
|
+
# Generate accessor method for present options
|
82
|
+
idr.option_list.each { |option|
|
83
|
+
key = keys[option.key]
|
84
|
+
set_variable(instance, "@#{key}", idr[option.key])
|
85
|
+
instance.instance_eval("def #{key}?() true end")
|
163
86
|
}
|
164
87
|
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
88
|
+
# Generate general #subcommand methods
|
89
|
+
if !idr.subcommand
|
90
|
+
instance.instance_eval("def subcommand() nil end")
|
91
|
+
instance.instance_eval("def subcommand?() false end")
|
92
|
+
instance.instance_eval %(
|
93
|
+
def subcommand!(*msgs)
|
94
|
+
$stderr.puts "in subcommand!"
|
95
|
+
::Kernel.raise ::ShellOpts::UserError, (msgs.empty? ? 'No command' : msgs.join)
|
96
|
+
end
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Generate individual subcommand methods
|
101
|
+
grammar.subcommand_list.each { |subcommand|
|
102
|
+
key = keys[subcommand.key]
|
103
|
+
if subcommand.key == idr.subcommand&.key
|
104
|
+
struct = OptionStruct.new(idr.subcommand, key_type, aliases[idr.subcommand.key] || {})
|
105
|
+
set_variable(instance, "@subcommand", struct)
|
106
|
+
instance.instance_eval("def #{key}() @subcommand end")
|
107
|
+
instance.instance_eval("def subcommand() :#{key} end")
|
108
|
+
instance.instance_eval("def subcommand?() true end")
|
109
|
+
instance.instance_eval("def subcommand!(*msgs) :#{key} end")
|
170
110
|
else
|
171
|
-
|
172
|
-
instance.instance_eval("def #{key}?() true end")
|
111
|
+
instance.instance_eval("def #{key}() nil end")
|
173
112
|
end
|
174
113
|
}
|
175
114
|
|
176
|
-
# Command accessor method
|
177
|
-
instance.instance_eval("def #{command_alias}() @__options_hash__.command end")
|
178
|
-
|
179
115
|
instance
|
180
116
|
end
|
181
117
|
|
182
|
-
|
183
|
-
def self.options_hash(instance)
|
184
|
-
get_variable(instance, "@__options_hash__")
|
185
|
-
end
|
186
|
-
|
118
|
+
private
|
187
119
|
# Return class of object. #class is not defined for BasicObjects so this
|
188
|
-
# method provides an alternative way of getting the class
|
120
|
+
# method provides an alternative way of getting the class
|
189
121
|
def self.class_of(object)
|
190
122
|
# https://stackoverflow.com/a/18621313/2130986
|
191
123
|
::Kernel.instance_method(:class).bind(object).call
|
192
124
|
end
|
193
125
|
|
194
|
-
#
|
195
|
-
|
196
|
-
options_hash(instance).size
|
197
|
-
end
|
198
|
-
|
199
|
-
# Return the option and command keys. The keys are in order of occurrence
|
200
|
-
# on the command line. A subcommand will always be the last element
|
201
|
-
def self.keys(instance)
|
202
|
-
options_hash(instance).keys
|
203
|
-
end
|
204
|
-
|
205
|
-
# Return the actual option name used on the command line for +name+. Use
|
206
|
-
# +index+ to select between repeated options. Return the name of the
|
207
|
-
# program/subcommand if key is nil
|
208
|
-
def self.name(struct, key = nil, index = nil)
|
209
|
-
options_hash(struct).name(key, index)
|
210
|
-
end
|
211
|
-
|
212
|
-
# Return the AST node for the option key or the AST node for the
|
213
|
-
# OptionStruct if key is nil. Use +index+ to select between repeated
|
214
|
-
# options. Raise InternalError if key doesn't exists
|
215
|
-
def self.node(struct, key = nil, index = nil)
|
216
|
-
options_hash(struct).node(key, index)
|
217
|
-
end
|
218
|
-
|
219
|
-
# Return key of the command of the struct (possibly nil)
|
220
|
-
def self.command(struct)
|
221
|
-
options_hash(struct).command
|
222
|
-
end
|
223
|
-
|
224
|
-
private
|
225
|
-
BASIC_OBJECT_RESERVED_WORDS = %w(
|
226
|
-
__id__ __send__ instance_eval instance_exec method_missing
|
227
|
-
singleton_method_added singleton_method_removed
|
228
|
-
singleton_method_undefined)
|
229
|
-
OPTIONS_STRUCT_RESERVED_WORDS = %w(__options_hash__ __command__)
|
230
|
-
RESERVED_WORDS = BASIC_OBJECT_RESERVED_WORDS + OPTIONS_STRUCT_RESERVED_WORDS
|
231
|
-
|
232
|
-
# Shorthand helper method. Substitutes the undefined ObjectStruct#instance_variable_set
|
126
|
+
# Class method implementation of ObjectStruct#instance_variable_set that is
|
127
|
+
# not defined in a BasicObject
|
233
128
|
def self.set_variable(this, var, value)
|
234
129
|
# https://stackoverflow.com/a/18621313/2130986
|
235
130
|
::Kernel.instance_method(:instance_variable_set).bind(this).call(var, value)
|
236
131
|
end
|
237
132
|
|
238
|
-
#
|
133
|
+
# Class method implementation of ObjectStruct#instance_variable_get that is
|
134
|
+
# not defined in a BasicObject
|
239
135
|
def self.get_variable(this, var)
|
240
136
|
# https://stackoverflow.com/a/18621313/2130986
|
241
137
|
::Kernel.instance_method(:instance_variable_get).bind(this).call(var)
|
242
138
|
end
|
139
|
+
|
140
|
+
BASIC_OBJECT_RESERVED_WORDS = %w(
|
141
|
+
__id__ __send__ instance_eval instance_exec method_missing
|
142
|
+
singleton_method_added singleton_method_removed
|
143
|
+
singleton_method_undefined).map(&:to_sym)
|
144
|
+
OPTIONS_STRUCT_RESERVED_WORDS = %w(__idr__ subcommand).map(&:to_sym)
|
145
|
+
RESERVED_WORDS = BASIC_OBJECT_RESERVED_WORDS + OPTIONS_STRUCT_RESERVED_WORDS
|
243
146
|
end
|
244
|
-
end
|
147
|
+
end
|
245
148
|
|