shellopts 2.0.18 → 2.0.21
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/TODO +16 -0
- data/lib/shellopts/grammar.rb +1 -1
- data/lib/shellopts/parser.rb +10 -15
- data/lib/shellopts/program.rb +29 -9
- data/lib/shellopts/version.rb +1 -1
- data/lib/shellopts.rb +133 -45
- data/main +15 -1185
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2e9d4e1a3ad5eca9fd21a3bad085a252ff7b6921ec889e4da404c989304bf9c
|
|
4
|
+
data.tar.gz: fa9f67483fcfb303e76f8b6b2282948f117f2497b734b6fdd6ca6f91a29b3d96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29c8d07d8fdf63c8b4e04d031ec1c7b779ed3410265103514ade6f8e5fd2b45d3f5b804de934722e0f49d11119e14e463507b7d63c0c6d2059018b75fec281a6
|
|
7
|
+
data.tar.gz: 2f7cad783bd109dc264574cb6ed76ec1f947d1591e15267bf071ebd243b5b646039bdec9fc2dac7983db38463fb6eddb328d920beaa35052647e647f9020ed49
|
data/TODO
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
o Make opts[] point at the main object
|
|
3
|
+
opts[].subcommand is the direct subcommand of main
|
|
4
|
+
o Make opts be the aggrated set of options in a subcommand
|
|
5
|
+
opts.subcommand is the concatenated path of subcommands
|
|
6
|
+
o Make opts[:some_command] be the set of options for that subcommand (already done)
|
|
7
|
+
o Sub-command options can overshadow options of outer commands but it is an
|
|
8
|
+
error to access them using opts. Use opts[] or opts[:some_command] instead
|
|
9
|
+
|
|
10
|
+
o Add a default argument to extract:
|
|
11
|
+
def extract(range, *defaults)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
extract(3..4, 'dbo')
|
|
15
|
+
|
|
16
|
+
o More strict parsing of option and command lines
|
|
1
17
|
o --option can't be escaped if on the start of a block line?
|
|
2
18
|
o Use require_relative
|
|
3
19
|
o In the following list is a command with a mandatory sub-command
|
data/lib/shellopts/grammar.rb
CHANGED
|
@@ -72,7 +72,7 @@ module ShellOpts
|
|
|
72
72
|
# Note that the analyzer fails with an an error if both --with-separator
|
|
73
73
|
# and --with_separator are used because they would both map to
|
|
74
74
|
# :with_separator
|
|
75
|
-
|
|
75
|
+
attr_accessor :ident
|
|
76
76
|
|
|
77
77
|
# Canonical name (String) of the object
|
|
78
78
|
#
|
data/lib/shellopts/parser.rb
CHANGED
|
@@ -23,7 +23,7 @@ module ShellOpts
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
class Option
|
|
26
|
-
SHORT_NAME_RE = /[a-zA-Z0-9]/
|
|
26
|
+
SHORT_NAME_RE = /[a-zA-Z0-9?]/
|
|
27
27
|
LONG_NAME_RE = /[a-zA-Z0-9][a-zA-Z0-9_-]*/
|
|
28
28
|
NAME_RE = /(?:#{SHORT_NAME_RE}|#{LONG_NAME_RE})(?:,#{LONG_NAME_RE})*/
|
|
29
29
|
|
|
@@ -124,23 +124,18 @@ module ShellOpts
|
|
|
124
124
|
super(nil, token)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
def
|
|
128
|
-
option_token = Token.new(:option, 1, 1,
|
|
129
|
-
brief_token = Token.new(:brief, 1, 1,
|
|
127
|
+
def inject_option(decl, brief, paragraph = nil, &block)
|
|
128
|
+
option_token = Token.new(:option, 1, 1, decl)
|
|
129
|
+
brief_token = Token.new(:brief, 1, 1, brief)
|
|
130
130
|
group = OptionGroup.new(self, option_token)
|
|
131
131
|
option = Option.parse(group, option_token)
|
|
132
132
|
brief = Brief.parse(group, brief_token)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"-h prints a brief help text, --help prints a longer man-style description of the command")
|
|
140
|
-
group = OptionGroup.new(self, option_token)
|
|
141
|
-
option = Option.parse(group, option_token)
|
|
142
|
-
brief = Brief.parse(group, brief_token)
|
|
143
|
-
paragraph = Paragraph.parse(group, paragraph_token)
|
|
133
|
+
paragraph ||= yield(option) if block_given?
|
|
134
|
+
if paragraph
|
|
135
|
+
paragraph_token = Token.new(:text, 1, 1, paragraph)
|
|
136
|
+
paragraph = Paragraph.parse(group, paragraph_token)
|
|
137
|
+
end
|
|
138
|
+
option
|
|
144
139
|
end
|
|
145
140
|
end
|
|
146
141
|
|
data/lib/shellopts/program.rb
CHANGED
|
@@ -84,19 +84,27 @@ module ShellOpts
|
|
|
84
84
|
}
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
# Returns a hash
|
|
88
|
-
#
|
|
87
|
+
# Returns a hash from option ident to value
|
|
88
|
+
#
|
|
89
|
+
# The value depends on the option type: If it is not repeatable, the value
|
|
90
|
+
# is the argument or nil if not present (or allowed). If the option is
|
|
91
|
+
# repeatable and has an argument, the value is an array of the arguments,
|
|
92
|
+
# if it doesn't have an argument, the value is the number of occurrences
|
|
93
|
+
#
|
|
89
94
|
def to_h(*keys)
|
|
90
95
|
keys = ::Kernel::Array(keys).flatten
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
__option_values__.select { |key,_| keys.empty? || keys.include?(key) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Like #to_h but present options without arguments have a true value
|
|
100
|
+
#
|
|
101
|
+
def to_h?(*keys)
|
|
102
|
+
keys = ::Kernel::Array(keys).flatten
|
|
103
|
+
keys = keys.empty? ? __option_values__.keys : keys
|
|
104
|
+
keys.filter_map { |key| __option_values__.key?(key) && [key, self.__send__(key)] }.to_h
|
|
98
105
|
end
|
|
99
106
|
|
|
107
|
+
|
|
100
108
|
# Subcommand identifier or nil if not present. #subcommand is often used in
|
|
101
109
|
# case statement to branch out to code that handles the given subcommand:
|
|
102
110
|
#
|
|
@@ -297,6 +305,18 @@ module ShellOpts
|
|
|
297
305
|
|
|
298
306
|
# The top-level command
|
|
299
307
|
class Program < Command
|
|
308
|
+
# Accessors for standard options values that are not affected if the option
|
|
309
|
+
# is renamed
|
|
310
|
+
attr_accessor :__quiet__
|
|
311
|
+
attr_accessor :__verbose__
|
|
312
|
+
attr_accessor :__debug__
|
|
313
|
+
|
|
314
|
+
def initialize
|
|
315
|
+
super
|
|
316
|
+
@__quiet__ = false
|
|
317
|
+
@__verbose__ = 0
|
|
318
|
+
@__debug__ = false
|
|
319
|
+
end
|
|
300
320
|
end
|
|
301
321
|
|
|
302
322
|
# Option models an option as given by the user on the subcommand line.
|
data/lib/shellopts/version.rb
CHANGED
data/lib/shellopts.rb
CHANGED
|
@@ -96,17 +96,20 @@ module ShellOpts
|
|
|
96
96
|
# Automatically add a -h and a --help option if true
|
|
97
97
|
attr_reader :help
|
|
98
98
|
|
|
99
|
-
# Version of client program. If not nil a --version option is added to the program
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
@version = IO.read(file).sub(/^.*VERSION\s*=\s*"(.*?)".*$/m, '\1') or
|
|
105
|
-
raise ArgumentError, "ShellOpts needs an explicit version"
|
|
106
|
-
end
|
|
99
|
+
# Version of client program. If not nil, a --version option is added to the program
|
|
100
|
+
attr_reader :version
|
|
101
|
+
|
|
102
|
+
# Automatically add a -q and a --quiet option if true
|
|
103
|
+
attr_reader :quiet
|
|
107
104
|
|
|
108
|
-
#
|
|
109
|
-
|
|
105
|
+
# Automatically add a -v and a --verbose option if true
|
|
106
|
+
attr_reader :verbose
|
|
107
|
+
|
|
108
|
+
# Automatically add a --debug option if true
|
|
109
|
+
attr_reader :debug
|
|
110
|
+
|
|
111
|
+
# Version number (this is usually detected dynamically)
|
|
112
|
+
attr_reader :version_number
|
|
110
113
|
|
|
111
114
|
# Floating options
|
|
112
115
|
attr_accessor :float
|
|
@@ -122,12 +125,33 @@ module ShellOpts
|
|
|
122
125
|
attr_reader :tokens
|
|
123
126
|
alias_method :ast, :grammar
|
|
124
127
|
|
|
125
|
-
def initialize(name: nil,
|
|
128
|
+
def initialize(name: nil,
|
|
129
|
+
# Options
|
|
130
|
+
help: true,
|
|
131
|
+
version: true,
|
|
132
|
+
quiet: nil,
|
|
133
|
+
verbose: nil,
|
|
134
|
+
debug: nil,
|
|
135
|
+
|
|
136
|
+
# Version number (usually detected)
|
|
137
|
+
version_number: nil,
|
|
138
|
+
|
|
139
|
+
# Floating options
|
|
140
|
+
float: true,
|
|
141
|
+
|
|
142
|
+
# Let exceptions through
|
|
143
|
+
exceptions: false
|
|
144
|
+
)
|
|
145
|
+
|
|
126
146
|
@name = name || File.basename($PROGRAM_NAME)
|
|
127
147
|
@help = help
|
|
128
|
-
@
|
|
129
|
-
@
|
|
130
|
-
@
|
|
148
|
+
@version = version || (version.nil? && !version_number.nil?)
|
|
149
|
+
@quiet = quiet
|
|
150
|
+
@verbose = verbose
|
|
151
|
+
@debug = debug
|
|
152
|
+
@version_number = version_number || find_version_number
|
|
153
|
+
@float = float
|
|
154
|
+
@exception = exception
|
|
131
155
|
end
|
|
132
156
|
|
|
133
157
|
# Compile source and return grammar object. Also sets #spec and #grammar.
|
|
@@ -139,8 +163,31 @@ module ShellOpts
|
|
|
139
163
|
@file = find_caller_file
|
|
140
164
|
@tokens = Lexer.lex(name, @spec, @oneline)
|
|
141
165
|
ast = Parser.parse(tokens)
|
|
142
|
-
|
|
143
|
-
|
|
166
|
+
|
|
167
|
+
help_spec = (@help == true ? "-h,help" : @help)
|
|
168
|
+
version_spec = (@version == true ? "--version" : @version)
|
|
169
|
+
quiet_spec = (@quiet == true ? "-q,quiet" : @quiet)
|
|
170
|
+
verbose_spec = (@verbose == true ? "+v,verbose" : @verbose)
|
|
171
|
+
debug_spec = (@debug == true ? "--debug" : @debug)
|
|
172
|
+
|
|
173
|
+
@quiet_option =
|
|
174
|
+
ast.inject_option(quiet_spec, "Quiet", "Do not write anything to standard output") if @quiet
|
|
175
|
+
@verbose_option =
|
|
176
|
+
ast.inject_option(verbose_spec, "Increase verbosity", "Write verbose output") if @verbose
|
|
177
|
+
@debug_option =
|
|
178
|
+
ast.inject_option(debug_spec, "Write debug information") if @debug
|
|
179
|
+
@help_option =
|
|
180
|
+
ast.inject_option(help_spec, "Write short or long help") { |option|
|
|
181
|
+
short_option = option.short_names.first
|
|
182
|
+
long_option = option.long_names.first
|
|
183
|
+
[
|
|
184
|
+
short_option && "#{short_option} prints a brief help text",
|
|
185
|
+
long_option && "#{long_option} prints a longer man-style description of the command"
|
|
186
|
+
].compact.join(", ")
|
|
187
|
+
} if @help
|
|
188
|
+
@version_option =
|
|
189
|
+
ast.inject_option(version_spec, "Write version number and exit") if @version
|
|
190
|
+
|
|
144
191
|
@grammar = Analyzer.analyze(ast)
|
|
145
192
|
}
|
|
146
193
|
self
|
|
@@ -153,16 +200,22 @@ module ShellOpts
|
|
|
153
200
|
handle_exceptions {
|
|
154
201
|
@argv = argv.dup
|
|
155
202
|
@program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if @program[:help].name == "-h"
|
|
161
|
-
ShellOpts.brief
|
|
162
|
-
else
|
|
203
|
+
|
|
204
|
+
# Process standard options (that may have been renamed)
|
|
205
|
+
if @program.__send__(:"#{@help_option.ident}?")
|
|
206
|
+
if @program[:help].name =~ /^--/
|
|
163
207
|
ShellOpts.help
|
|
208
|
+
else
|
|
209
|
+
ShellOpts.brief
|
|
164
210
|
end
|
|
165
211
|
exit
|
|
212
|
+
elsif @program.__send__(:"#{@version_option.ident}?")
|
|
213
|
+
puts version_number
|
|
214
|
+
exit
|
|
215
|
+
else
|
|
216
|
+
@program.__quiet__ = @program.__send__(:"#{@quiet_option.ident}?") if @quiet
|
|
217
|
+
@program.__verbose__ = @program.__send__(:"#{@verbose_option.ident}") if @verbose
|
|
218
|
+
@program.__debug__ = @program.__send__(:"#{@debug_option.ident}?") if @debug
|
|
166
219
|
end
|
|
167
220
|
}
|
|
168
221
|
self
|
|
@@ -235,6 +288,13 @@ module ShellOpts
|
|
|
235
288
|
def self.help(subject = nil) ::ShellOpts.instance.help(subject) end
|
|
236
289
|
|
|
237
290
|
private
|
|
291
|
+
def find_version_number
|
|
292
|
+
exe = caller.find { |line| line =~ /`<top \(required\)>'$/ }&.sub(/:.*/, "") or return nil
|
|
293
|
+
file = Dir.glob(File.dirname(exe) + "/../lib/*/version.rb").first or return nil
|
|
294
|
+
IO.read(file).sub(/^.*VERSION\s*=\s*"(.*?)".*$/m, '\1') or
|
|
295
|
+
raise ArgumentError, "ShellOpts needs an explicit version"
|
|
296
|
+
end
|
|
297
|
+
|
|
238
298
|
def handle_exceptions(&block)
|
|
239
299
|
return yield if exception
|
|
240
300
|
begin
|
|
@@ -322,9 +382,12 @@ module ShellOpts
|
|
|
322
382
|
end
|
|
323
383
|
end
|
|
324
384
|
|
|
325
|
-
def self.process(spec, argv,
|
|
326
|
-
|
|
327
|
-
|
|
385
|
+
def self.process(spec, argv, quiet: nil, verbose: nil, debug: nil, **opts)
|
|
386
|
+
constrain quiet, String, true, false, nil
|
|
387
|
+
quiet = quiet.nil? ? Message.is_included? || Verbose.is_included? : quiet
|
|
388
|
+
verbose = verbose.nil? ? ::ShellOpts::Verbose.is_included? : verbose
|
|
389
|
+
debug = debug.nil? ? Debug.is_included? : debug
|
|
390
|
+
ShellOpts.process(spec, argv, quiet: quiet, verbose: verbose, debug: debug, **opts)
|
|
328
391
|
end
|
|
329
392
|
|
|
330
393
|
@instance = nil
|
|
@@ -345,31 +408,56 @@ module ShellOpts
|
|
|
345
408
|
exit 1
|
|
346
409
|
end
|
|
347
410
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
411
|
+
def self.notice(message)
|
|
412
|
+
$stderr.puts message if !instance.quiet || !instance.program.quiet?
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def self.mesg(message)
|
|
416
|
+
$stdout.puts message if !instance.quiet || !instance.program.__quiet__
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def self.verb(level = 1, message)
|
|
420
|
+
$stdout.puts message if instance.verbose && level <= instance.program.__verbose__
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def self.debug(message)
|
|
424
|
+
$stdout.puts message if instance.debug && instance.program.__debug__
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def self.quiet_flag
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def self.verbose_flag
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def self.debug_flag
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
module Message
|
|
351
437
|
@is_included = false
|
|
352
438
|
def self.is_included?() @is_included end
|
|
353
|
-
def self.
|
|
354
|
-
@is_included = true
|
|
355
|
-
super
|
|
356
|
-
end
|
|
439
|
+
def self.included(...) @is_included = true; super end
|
|
357
440
|
|
|
358
|
-
def notice(message)
|
|
359
|
-
|
|
360
|
-
|
|
441
|
+
def notice(message) ::ShellOpts.notice(message) end
|
|
442
|
+
def mesg(message) ::ShellOpts.mesg(message) end
|
|
443
|
+
end
|
|
361
444
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
end
|
|
445
|
+
module Verbose
|
|
446
|
+
@is_included = false
|
|
447
|
+
def self.is_included?() @is_included end
|
|
448
|
+
def self.included(...) @is_included = true; super end
|
|
365
449
|
|
|
366
|
-
def
|
|
367
|
-
|
|
368
|
-
end
|
|
450
|
+
def notice(message) ::ShellOpts.notice(message) end
|
|
451
|
+
def mesg(message) ::ShellOpts.mesg(message) end
|
|
452
|
+
def verb(level = 1, message) ::ShellOpts.verb(level, message) end
|
|
453
|
+
end
|
|
369
454
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
end
|
|
455
|
+
module Debug
|
|
456
|
+
@is_included = false
|
|
457
|
+
def self.is_included?() @is_included end
|
|
458
|
+
def self.included(...) @is_included = true; super end
|
|
459
|
+
|
|
460
|
+
def debug(message) ::ShellOpts.debug(message) end
|
|
373
461
|
end
|
|
374
462
|
|
|
375
463
|
module ErrorHandling
|