shellopts 2.0.3 → 2.0.6
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/lib/shellopts/formatter.rb +5 -4
- data/lib/shellopts/parser.rb +24 -0
- data/lib/shellopts/program.rb +52 -63
- data/lib/shellopts/version.rb +1 -1
- data/lib/shellopts.rb +25 -6
- data/main +26 -10
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52971494b88f77b6b66a95e8409de7cbebcbf030588089c395424b7e86f5e372
|
4
|
+
data.tar.gz: e00e482a0db690c2b7bd0861589a5179a67d7cec9b285ebcc1d2b5a7b6f73ff7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2206c1e56c4239472bf5b697854fc4d7ba06bd9dedfe4eb2f86c2e115f4ddd52f66e9b4f47cbba41b1b4eafb08b8666b2115df7f70d769465dcb3e4a344d322a
|
7
|
+
data.tar.gz: f9bc90bdba87159a2821afb3c719ba4c4fbc7077601266d9e61c5f83273924bc23e9fa2208bf727855fc99b6acac09a97ec3dc58be179f649c9ed6dbb4fcc259
|
data/lib/shellopts/formatter.rb
CHANGED
@@ -38,16 +38,17 @@ module ShellOpts
|
|
38
38
|
using Ext::Array::Wrap
|
39
39
|
|
40
40
|
def puts_usage(bol: false)
|
41
|
+
width = [Formatter.rest, Formatter::USAGE_MAX_WIDTH].min
|
41
42
|
if descrs.size == 0
|
42
43
|
print (lead = Formatter.command_prefix || "")
|
43
44
|
indent(lead.size, ' ', bol: bol && lead == "") {
|
44
|
-
puts render(:multi,
|
45
|
+
puts render(:multi, width)
|
45
46
|
}
|
46
47
|
else
|
47
48
|
lead = Formatter.command_prefix || ""
|
48
49
|
descrs.each { |descr|
|
49
50
|
print lead
|
50
|
-
puts render(:single,
|
51
|
+
puts render(:single, width, args: [descr.text])
|
51
52
|
}
|
52
53
|
end
|
53
54
|
end
|
@@ -199,8 +200,8 @@ module ShellOpts
|
|
199
200
|
# Number of characters between columns in brief output
|
200
201
|
BRIEF_COL_SEP = 2
|
201
202
|
|
202
|
-
#
|
203
|
-
BRIEF_COL1_MIN_WIDTH =
|
203
|
+
# Minimum width of first column in brief option and command lists
|
204
|
+
BRIEF_COL1_MIN_WIDTH = 20
|
204
205
|
|
205
206
|
# Maximum width of first column in brief option and command lists
|
206
207
|
BRIEF_COL1_MAX_WIDTH = 40
|
data/lib/shellopts/parser.rb
CHANGED
@@ -122,6 +122,22 @@ module ShellOpts
|
|
122
122
|
def self.parse(token)
|
123
123
|
super(nil, token)
|
124
124
|
end
|
125
|
+
|
126
|
+
def add_stdopts
|
127
|
+
option_token = Token.new(:option, 1, 1, "--version")
|
128
|
+
brief_token = Token.new(:brief, 1, 1, "Write version number and exit")
|
129
|
+
group = OptionGroup.new(self, option_token)
|
130
|
+
option = Option.parse(group, option_token)
|
131
|
+
brief = Brief.parse(group, brief_token)
|
132
|
+
|
133
|
+
option_token = Token.new(:option, 1, 1, "-h,help")
|
134
|
+
brief_token = Token.new(:brief, 1, 1, "Write help text and exit")
|
135
|
+
paragraph_token = Token.new(:text, 1, 1, "-h prints a brief help text, --help prints a longer man-style description of the command")
|
136
|
+
group = OptionGroup.new(self, option_token)
|
137
|
+
option = Option.parse(group, option_token)
|
138
|
+
brief = Brief.parse(group, brief_token)
|
139
|
+
paragraph = Paragraph.parse(group, paragraph_token)
|
140
|
+
end
|
125
141
|
end
|
126
142
|
|
127
143
|
class ArgSpec
|
@@ -146,6 +162,14 @@ module ShellOpts
|
|
146
162
|
@nodes = {}
|
147
163
|
end
|
148
164
|
|
165
|
+
# def add_stdopts
|
166
|
+
# version_token = Token.new(:option, 1, 1, "--version")
|
167
|
+
# version_brief = Token.new(:brief, 1, 1, "Gryf gryf")
|
168
|
+
# group = Grammar::OptionGroup.new(@program, version_token)
|
169
|
+
# option = Grammar::Option.parse(group, version_token)
|
170
|
+
# brief = Grammr::Brief.parse(option, version_brief)
|
171
|
+
# end
|
172
|
+
|
149
173
|
def parse()
|
150
174
|
@program = Grammar::Program.parse(@tokens.shift)
|
151
175
|
oneline = @tokens.first.lineno == @tokens.last.lineno
|
data/lib/shellopts/program.rb
CHANGED
@@ -4,30 +4,33 @@
|
|
4
4
|
module ShellOpts
|
5
5
|
# Command represents a program or a subcommand. It is derived from
|
6
6
|
# BasicObject to have only a minimum of inherited member methods.
|
7
|
-
# Additional methods defined in Command use the '__<identifier>__' naming
|
8
|
-
# convention that doesn't collide with option or subcommand names but
|
9
|
-
# they're rarely used in application code
|
10
7
|
#
|
11
8
|
# The names of the inherited methods can't be used as options or
|
12
9
|
# command namess. They are: instance_eval, instance_exec method_missing,
|
13
10
|
# singleton_method_added, singleton_method_removed, and
|
14
|
-
# singleton_method_undefined
|
11
|
+
# singleton_method_undefined.
|
12
|
+
#
|
13
|
+
# Additional methods defined in Command use the '__<identifier>__' naming
|
14
|
+
# convention that doesn't collide with option or subcommand names but
|
15
|
+
# they're rarely used in application code
|
15
16
|
#
|
16
17
|
# Command also defines #subcommand and #subcommand! but they can be
|
17
18
|
# overshadowed by an option or command declaration. Their values can
|
18
19
|
# still be accessed using the dashed name, though
|
19
20
|
#
|
20
|
-
#
|
21
|
+
# Option and Command objects can be accessed using #[]. #key? is also defined
|
21
22
|
#
|
22
23
|
# The following methods are created dynamically for each declared option
|
23
24
|
# with an attribute name
|
24
25
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
26
|
+
# <identifier>(default = nil)
|
27
|
+
# <identifier>=(value)
|
28
|
+
# <identifier>?()
|
29
|
+
#
|
30
|
+
# The default value is used if the option or its value is missing
|
28
31
|
#
|
29
32
|
# Options without an an attribute can still be accessed using #[] or trough
|
30
|
-
# #
|
33
|
+
# #__option_values__, #__option_hash, or #__options_list__
|
31
34
|
#
|
32
35
|
# Each subcommand has a single method:
|
33
36
|
#
|
@@ -42,9 +45,9 @@ module ShellOpts
|
|
42
45
|
|
43
46
|
# These names can't be used as option or command names
|
44
47
|
RESERVED_OPTION_NAMES = %w(
|
45
|
-
is_a
|
46
|
-
|
47
|
-
|
48
|
+
is_a instance_eval instance_exec method_missing singleton_method_added
|
49
|
+
singleton_method_removed singleton_method_undefined
|
50
|
+
)
|
48
51
|
|
49
52
|
# These methods can be overridden by an option (the value is not used -
|
50
53
|
# this is just for informational purposes)
|
@@ -59,43 +62,25 @@ module ShellOpts
|
|
59
62
|
object
|
60
63
|
end
|
61
64
|
|
62
|
-
# Return command
|
65
|
+
# Return command or option object if present, otherwise nil. Returns a
|
66
|
+
# possibly empty array of option objects if the option is repeatable
|
63
67
|
#
|
64
68
|
# The key is the name or identifier of the object or any any option
|
65
69
|
# alias. Eg. :f, '-f', :file, or '--file' are all usable as option keys
|
66
70
|
# and :cmd! or 'cmd' as command keys
|
67
71
|
#
|
68
|
-
# For options, the returned value is the argument given by the user
|
69
|
-
# optionally converted to Integer or Float or nil if the option doesn't
|
70
|
-
# take arguments. If the option takes an argument and it is repeatable
|
71
|
-
# the value is an array of the arguments. Repeatable options without
|
72
|
-
# arguments have the number of occurences as the value
|
73
|
-
#
|
74
72
|
def [](key)
|
75
73
|
case object = __grammar__[key]
|
76
74
|
when ::ShellOpts::Grammar::Command
|
77
75
|
object.ident == __subcommand__!.__ident__ ? __subcommand__! : nil
|
78
76
|
when ::ShellOpts::Grammar::Option
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
# Assign a value to an existing option. This can be used to implement
|
86
|
-
# default values. #[]= doesn't currently check the type of the given
|
87
|
-
# value so take care. Note that the corresponding option(s) in
|
88
|
-
# #__option_list__ is not updated
|
89
|
-
def []=(key, value)
|
90
|
-
case object = __grammar__[key]
|
91
|
-
when ::ShellOpts::Grammar::Command
|
92
|
-
::Kernel.raise ArgumentError, "#{key.inspect} is not an option"
|
93
|
-
when ::ShellOpts::Grammar::Option
|
94
|
-
object.argument? || object.repeatable? or
|
95
|
-
::Kernel.raise ArgumentError, "#{key.inspect} is not assignable"
|
96
|
-
__options__[object.ident] = value
|
77
|
+
if object.repeatable?
|
78
|
+
__option_hash__[object.ident] || []
|
79
|
+
else
|
80
|
+
__option_hash__[object.ident]
|
81
|
+
end
|
97
82
|
else
|
98
|
-
::Kernel.raise ArgumentError, "Unknown
|
83
|
+
::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
|
99
84
|
end
|
100
85
|
end
|
101
86
|
|
@@ -103,11 +88,11 @@ module ShellOpts
|
|
103
88
|
def key?(key)
|
104
89
|
case object = __grammar__[key]
|
105
90
|
when ::ShellOpts::Grammar::Command
|
106
|
-
object.ident == __subcommand__
|
91
|
+
object.ident == __subcommand__
|
107
92
|
when ::ShellOpts::Grammar::Option
|
108
|
-
|
93
|
+
__option_hash__.key?(object.ident)
|
109
94
|
else
|
110
|
-
|
95
|
+
::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
|
111
96
|
end
|
112
97
|
end
|
113
98
|
|
@@ -153,13 +138,17 @@ module ShellOpts
|
|
153
138
|
# depending on the option's type. Repeated options options without
|
154
139
|
# arguments have the number of occurences as the value, with arguments
|
155
140
|
# the value is an array of the given values
|
156
|
-
attr_reader :
|
141
|
+
attr_reader :__option_values__
|
157
142
|
|
158
143
|
# List of Option objects for the subcommand in the same order as
|
159
144
|
# given by the user but note that options are reordered to come after
|
160
145
|
# their associated subcommand if float is true. Repeated options are not
|
161
146
|
# collapsed
|
162
147
|
attr_reader :__option_list__
|
148
|
+
|
149
|
+
# Map from identifier to option object or to a list of option objects if
|
150
|
+
# the option is repeatable
|
151
|
+
attr_reader :__option_hash__
|
163
152
|
|
164
153
|
# The subcommand identifier (a Symbol incl. the exclamation mark) or nil
|
165
154
|
# if not present. Use #subcommand!, or the dynamically generated
|
@@ -172,9 +161,10 @@ module ShellOpts
|
|
172
161
|
private
|
173
162
|
def __initialize__(grammar)
|
174
163
|
@__grammar__ = grammar
|
175
|
-
@
|
164
|
+
@__option_values__ = {}
|
176
165
|
@__option_list__ = []
|
177
|
-
@
|
166
|
+
@__option_hash__ = {}
|
167
|
+
@__option_values__ = {}
|
178
168
|
@__subcommand__ = nil
|
179
169
|
|
180
170
|
__define_option_methods__
|
@@ -182,36 +172,34 @@ module ShellOpts
|
|
182
172
|
|
183
173
|
def __define_option_methods__
|
184
174
|
@__grammar__.options.each { |opt|
|
185
|
-
next if opt.attr.nil?
|
186
175
|
if opt.argument? || opt.repeatable?
|
187
176
|
if opt.optional?
|
188
177
|
self.instance_eval %(
|
189
178
|
def #{opt.attr}(default = nil)
|
190
|
-
if @
|
191
|
-
@
|
179
|
+
if @__option_values__.key?(:#{opt.attr})
|
180
|
+
@__option_values__[:#{opt.attr}]
|
192
181
|
else
|
193
|
-
|
182
|
+
default
|
194
183
|
end
|
195
184
|
end
|
196
185
|
)
|
197
|
-
elsif !opt.argument?
|
186
|
+
elsif !opt.argument? # Repeatable w/o argument
|
198
187
|
self.instance_eval %(
|
199
|
-
def #{opt.attr}(default =
|
200
|
-
if @
|
201
|
-
|
202
|
-
value == 0 ? default : value
|
188
|
+
def #{opt.attr}(default = [])
|
189
|
+
if @__option_values__.key?(:#{opt.attr})
|
190
|
+
@__option_values__[:#{opt.attr}]
|
203
191
|
else
|
204
|
-
|
192
|
+
default
|
205
193
|
end
|
206
194
|
end
|
207
195
|
)
|
208
196
|
else
|
209
|
-
self.instance_eval("def #{opt.attr}() @
|
197
|
+
self.instance_eval("def #{opt.attr}() @__option_values__[:#{opt.attr}] end")
|
210
198
|
end
|
211
|
-
self.instance_eval("def #{opt.attr}=(value) @
|
212
|
-
@
|
199
|
+
self.instance_eval("def #{opt.attr}=(value) @__option_values__[:#{opt.attr}] = value end")
|
200
|
+
@__option_values__[opt.attr] = 0 if !opt.argument?
|
213
201
|
end
|
214
|
-
self.instance_eval("def #{opt.attr}?() @
|
202
|
+
self.instance_eval("def #{opt.attr}?() @__option_values__.key?(:#{opt.attr}) end")
|
215
203
|
}
|
216
204
|
|
217
205
|
@__grammar__.commands.each { |cmd|
|
@@ -228,14 +216,15 @@ module ShellOpts
|
|
228
216
|
ident = option.grammar.ident
|
229
217
|
@__option_list__ << option
|
230
218
|
if option.repeatable?
|
219
|
+
(@__option_hash__[ident] ||= []) << option
|
231
220
|
if option.argument?
|
232
|
-
(@
|
221
|
+
(@__option_values__[ident] ||= []) << option.argument
|
233
222
|
else
|
234
|
-
@
|
235
|
-
@__options__[ident] += 1
|
223
|
+
@__option_values__[ident] = (@__option_values__[ident] || 0) + 1
|
236
224
|
end
|
237
225
|
else
|
238
|
-
@
|
226
|
+
@__option_hash__[ident] = option
|
227
|
+
@__option_values__[ident] = option.argument
|
239
228
|
end
|
240
229
|
end
|
241
230
|
|
@@ -254,7 +243,7 @@ module ShellOpts
|
|
254
243
|
|
255
244
|
# Option models an option as given by the user on the subcommand line.
|
256
245
|
# Compiled options (and possibly aggregated) options are stored in the
|
257
|
-
# Command#
|
246
|
+
# Command#__option_values__ array
|
258
247
|
class Option
|
259
248
|
# Associated Grammar::Option object
|
260
249
|
attr_reader :grammar
|
data/lib/shellopts/version.rb
CHANGED
data/lib/shellopts.rb
CHANGED
@@ -102,6 +102,9 @@ module ShellOpts
|
|
102
102
|
attr_accessor :stdopts
|
103
103
|
attr_accessor :msgopts
|
104
104
|
|
105
|
+
# Version of client program. This is only used if +stdopts+ is true
|
106
|
+
attr_reader :version
|
107
|
+
|
105
108
|
# Interpreter flags
|
106
109
|
attr_accessor :float
|
107
110
|
|
@@ -116,9 +119,9 @@ module ShellOpts
|
|
116
119
|
attr_reader :tokens
|
117
120
|
alias_method :ast, :grammar # Oops - defined earlier FIXME
|
118
121
|
|
119
|
-
def initialize(name: nil, stdopts: true, msgopts: false, float: true, exception: false)
|
122
|
+
def initialize(name: nil, stdopts: true, version: nil, msgopts: false, float: true, exception: false)
|
120
123
|
@name = name || File.basename($PROGRAM_NAME)
|
121
|
-
@stdopts, @msgopts, @float, @exception = stdopts, msgopts, float, exception
|
124
|
+
@stdopts, @version, @msgopts, @float, @exception = stdopts, version, msgopts, float, exception
|
122
125
|
end
|
123
126
|
|
124
127
|
# Compile source and return grammar object. Also sets #spec and #grammar.
|
@@ -130,7 +133,7 @@ module ShellOpts
|
|
130
133
|
@file = find_caller_file
|
131
134
|
@tokens = Lexer.lex(name, @spec, @oneline)
|
132
135
|
ast = Parser.parse(tokens)
|
133
|
-
|
136
|
+
ast.add_stdopts if stdopts
|
134
137
|
@grammar = Analyzer.analyze(ast)
|
135
138
|
}
|
136
139
|
self
|
@@ -143,6 +146,20 @@ module ShellOpts
|
|
143
146
|
handle_exceptions {
|
144
147
|
@argv = argv.dup
|
145
148
|
@program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)
|
149
|
+
if stdopts
|
150
|
+
if @program.version?
|
151
|
+
version or raise ArgumentError, "Version not specified"
|
152
|
+
puts version
|
153
|
+
exit
|
154
|
+
elsif @program.help?
|
155
|
+
if @program[:help].name == "-h"
|
156
|
+
ShellOpts.brief
|
157
|
+
else
|
158
|
+
ShellOpts.help
|
159
|
+
end
|
160
|
+
exit
|
161
|
+
end
|
162
|
+
end
|
146
163
|
}
|
147
164
|
self
|
148
165
|
end
|
@@ -177,7 +194,7 @@ module ShellOpts
|
|
177
194
|
saved = $stdout
|
178
195
|
$stdout = $stderr
|
179
196
|
$stderr.puts "#{name}: #{message}"
|
180
|
-
Formatter.usage(
|
197
|
+
Formatter.usage(grammar)
|
181
198
|
exit 1
|
182
199
|
ensure
|
183
200
|
$stdout = saved
|
@@ -201,11 +218,12 @@ module ShellOpts
|
|
201
218
|
def brief() Formatter.brief(@grammar) end
|
202
219
|
|
203
220
|
# Print help for the given subject or the full documentation if +subject+
|
204
|
-
# is nil
|
221
|
+
# is nil. Clears the screen beforehand if :clear is true
|
205
222
|
#
|
206
|
-
def help(subject = nil)
|
223
|
+
def help(subject = nil, clear: true)
|
207
224
|
node = (subject ? @grammar[subject] : @grammar) or
|
208
225
|
raise ArgumentError, "No such command: '#{subject&.sub(".", " ")}'"
|
226
|
+
print '[H[2J' if clear
|
209
227
|
Formatter.help(node)
|
210
228
|
end
|
211
229
|
|
@@ -310,6 +328,7 @@ module ShellOpts
|
|
310
328
|
def self.instance?() !@instance.nil? end
|
311
329
|
def self.instance() @instance or raise Error, "ShellOpts is not initialized" end
|
312
330
|
def self.instance=(instance) @instance = instance end
|
331
|
+
def self.shellopts() instance end
|
313
332
|
|
314
333
|
forward_self_to :instance, :error, :failure
|
315
334
|
|
data/main
CHANGED
@@ -7,18 +7,33 @@ require 'shellopts'
|
|
7
7
|
|
8
8
|
include ShellOpts
|
9
9
|
|
10
|
+
VERSION = "1.2.3"
|
10
11
|
|
11
12
|
SPEC = %(
|
12
|
-
-a
|
13
|
-
|
13
|
+
-a @ An option
|
14
|
+
)
|
15
|
+
opts, args = ShellOpts::process(SPEC, ARGV, version: VERSION)
|
16
|
+
#ShellOpts::ShellOpts.help
|
14
17
|
|
15
|
-
-b,beta=ARG
|
16
|
-
@ Alternative style of brief comment
|
17
18
|
|
18
|
-
Longer and more elaborate description of the --beta option
|
19
|
-
)
|
20
19
|
|
21
|
-
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
__END__
|
24
|
+
|
25
|
+
|
26
|
+
#SPEC = %(
|
27
|
+
# -a,alpha @ Brief comment for -a and --alpha options
|
28
|
+
# Longer and more elaborate description of the --alpha option
|
29
|
+
#
|
30
|
+
# -b,beta=ARG
|
31
|
+
# @ Alternative style of brief comment
|
32
|
+
#
|
33
|
+
# Longer and more elaborate description of the --beta option
|
34
|
+
#)
|
35
|
+
#
|
36
|
+
#opts, args = ShellOpts.process(SPEC, ARGV)
|
22
37
|
#puts "opts.alpha?: #{opts.alpha?.inspect}"
|
23
38
|
#puts "opts.alpha: #{opts.alpha.inspect}"
|
24
39
|
#puts "opts.beta?: #{opts.beta?.inspect}"
|
@@ -266,8 +281,9 @@ SPEC4 = %(
|
|
266
281
|
|
267
282
|
SPEC5 = "cmd! cmd!"
|
268
283
|
|
269
|
-
shellopts = ShellOpts::ShellOpts.new(exception:
|
284
|
+
shellopts = ShellOpts::ShellOpts.new(exception: true)
|
270
285
|
#shellopts.compile("cmd! cmd!")
|
286
|
+
|
271
287
|
shellopts.compile(SPEC)
|
272
288
|
#shellopts.compile(SPEC2)
|
273
289
|
#shellopts.compile(SPEC3)
|
@@ -277,9 +293,9 @@ shellopts.compile(SPEC)
|
|
277
293
|
#shellopts.tokens.each(&:dump)
|
278
294
|
#exit
|
279
295
|
|
280
|
-
shellopts.usage
|
296
|
+
#shellopts.usage
|
281
297
|
#shellopts.brief
|
282
|
-
|
298
|
+
shellopts.help
|
283
299
|
#shellopts.help("cmd")
|
284
300
|
#shellopts.help(ARGV.first)
|
285
301
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shellopts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claus Rasmussen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: forward_to
|
@@ -187,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
187
|
- !ruby/object:Gem::Version
|
188
188
|
version: '0'
|
189
189
|
requirements: []
|
190
|
-
rubygems_version: 3.
|
190
|
+
rubygems_version: 3.1.4
|
191
191
|
signing_key:
|
192
192
|
specification_version: 4
|
193
193
|
summary: Parse command line options and arguments
|