shellopts 2.0.17 → 2.0.20
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/PROBLEMS +11 -0
- data/TODO +16 -0
- data/lib/shellopts/grammar.rb +3 -3
- data/lib/shellopts/parser.rb +10 -15
- data/lib/shellopts/program.rb +47 -39
- data/lib/shellopts/version.rb +1 -1
- data/lib/shellopts.rb +134 -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: 83e0ce72f27c72eec2087bee9296378fc0d84fc1124f830af27f21340cefffcc
|
4
|
+
data.tar.gz: 6c3b2363c1ca392b0950c792c48e353181f352ca22bfe16e7c42490248511b29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcc6e2271a451f494e007c78e9054c49bbb6f65f0f0fcf5be07158488157782c8216fc0f6fce7e4c25dda6bf0e39ac8ae2096f733cb4af7c4b1360b28a978129
|
7
|
+
data.tar.gz: e1d60f792447e5884f2ad939fbf408d5c053ef413328e2e2a42a082e34e9672537d8d64916fbc136f11797ac2c87894c148939d4feac9cc43489d3c66faa538d
|
data/PROBLEMS
CHANGED
@@ -39,3 +39,14 @@ SPEC = %(
|
|
39
39
|
mapping and grants are
|
40
40
|
)
|
41
41
|
|
42
|
+
###########################
|
43
|
+
|
44
|
+
# If option is only defined on a subcommand, it doesn't flow before the subcommand. This should be legal:
|
45
|
+
|
46
|
+
cmd -c sub
|
47
|
+
|
48
|
+
# Only this is legal today
|
49
|
+
|
50
|
+
cmd sub -c
|
51
|
+
|
52
|
+
|
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
@@ -30,7 +30,7 @@ module ShellOpts
|
|
30
30
|
def ancestors() parents.reverse end
|
31
31
|
|
32
32
|
def inspect
|
33
|
-
|
33
|
+
self.class.to_s
|
34
34
|
end
|
35
35
|
|
36
36
|
protected
|
@@ -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
|
#
|
@@ -241,7 +241,7 @@ module ShellOpts
|
|
241
241
|
def [](key)
|
242
242
|
case key
|
243
243
|
when String; lookup(key.split("."))
|
244
|
-
when Symbol; lookup(key.to_s.
|
244
|
+
when Symbol; lookup(key.to_s.gsub(".", "!.").split(".").map(&:to_sym))
|
245
245
|
when Array; lookup(key)
|
246
246
|
else
|
247
247
|
nil
|
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
@@ -62,53 +62,49 @@ module ShellOpts
|
|
62
62
|
object
|
63
63
|
end
|
64
64
|
|
65
|
-
#
|
66
|
-
# possibly empty array of option objects if the
|
65
|
+
# Returns the command or option object identified by the UID if present and
|
66
|
+
# otherwise nil. Returns a possibly empty array of option objects if the
|
67
|
+
# option is repeatable. Raise an ArgumentError if the key doesn't exists
|
67
68
|
#
|
68
|
-
# The key is the
|
69
|
-
#
|
70
|
-
# and :cmd! or 'cmd' as command keys
|
69
|
+
# The +key+ is the symbolic UID of the object. Eg. :command.option or
|
70
|
+
# :command.subcommand!
|
71
71
|
#
|
72
|
-
def [](
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
def [](uid)
|
73
|
+
__grammar__.key?(uid) or ::Kernel.raise ::ArgumentError, "'#{uid}' is not a valid UID"
|
74
|
+
idents = uid.to_s.gsub(/\./, "!.").split(/\./).map(&:to_sym)
|
75
|
+
idents.inject(self) { |cmd, ident|
|
76
|
+
case ident.to_s
|
77
|
+
when /!$/
|
78
|
+
return nil if cmd.__subcommand__ != ident
|
79
|
+
cmd = cmd.__subcommand__!
|
79
80
|
else
|
80
|
-
__option_hash__[
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
81
|
+
opt = cmd.__option_hash__[ident]
|
82
|
+
opt.nil? && cmd.__grammar__[ident].repeatable? ? [] : opt
|
83
|
+
end
|
84
|
+
}
|
85
85
|
end
|
86
86
|
|
87
|
-
#
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
else
|
95
|
-
::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Returns a hash of the given options if defined. Returns all options if no
|
100
|
-
# options are given
|
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
|
+
#
|
101
94
|
def to_h(*keys)
|
102
95
|
keys = ::Kernel::Array(keys).flatten
|
103
|
-
|
104
|
-
self.to_h(@__grammar__.options.map(&:ident))
|
105
|
-
else
|
106
|
-
keys.map { |key|
|
107
|
-
self.__send__("#{key}?".to_sym) ? [key, self.__send__(key)] : nil
|
108
|
-
}.compact.to_h
|
109
|
-
end
|
96
|
+
__option_values__.select { |key,_| keys.empty? || keys.include?(key) }
|
110
97
|
end
|
111
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
|
105
|
+
end
|
106
|
+
|
107
|
+
|
112
108
|
# Subcommand identifier or nil if not present. #subcommand is often used in
|
113
109
|
# case statement to branch out to code that handles the given subcommand:
|
114
110
|
#
|
@@ -148,7 +144,7 @@ module ShellOpts
|
|
148
144
|
#
|
149
145
|
def supercommand!() __supercommand__ end
|
150
146
|
|
151
|
-
# UID of command/program
|
147
|
+
# UID of command/program (String)
|
152
148
|
def __uid__() @__grammar__.uid end
|
153
149
|
|
154
150
|
# Identfier including the exclamation mark (Symbol)
|
@@ -309,6 +305,18 @@ module ShellOpts
|
|
309
305
|
|
310
306
|
# The top-level command
|
311
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
|
312
320
|
end
|
313
321
|
|
314
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,57 @@ module ShellOpts
|
|
345
408
|
exit 1
|
346
409
|
end
|
347
410
|
|
348
|
-
|
349
|
-
|
350
|
-
|
411
|
+
def self.notice(message)
|
412
|
+
$stderr.puts "#{instance.program.__grammar__.name}: #{message}" \
|
413
|
+
if !instance.quiet || !instance.program.quiet?
|
414
|
+
end
|
415
|
+
|
416
|
+
def self.mesg(message)
|
417
|
+
$stdout.puts message if !instance.quiet || !instance.program.__quiet__
|
418
|
+
end
|
419
|
+
|
420
|
+
def self.verb(level = 1, message)
|
421
|
+
$stdout.puts message if instance.verbose && level <= instance.program.__verbose__
|
422
|
+
end
|
423
|
+
|
424
|
+
def self.debug(message)
|
425
|
+
$stdout.puts message if instance.debug && instance.program.__debug__
|
426
|
+
end
|
427
|
+
|
428
|
+
def self.quiet_flag
|
429
|
+
end
|
430
|
+
|
431
|
+
def self.verbose_flag
|
432
|
+
end
|
433
|
+
|
434
|
+
def self.debug_flag
|
435
|
+
end
|
436
|
+
|
437
|
+
module Message
|
351
438
|
@is_included = false
|
352
439
|
def self.is_included?() @is_included end
|
353
|
-
def self.
|
354
|
-
@is_included = true
|
355
|
-
super
|
356
|
-
end
|
440
|
+
def self.included(...) @is_included = true; super end
|
357
441
|
|
358
|
-
def notice(message)
|
359
|
-
|
360
|
-
|
442
|
+
def notice(message) ::ShellOpts.notice(message) end
|
443
|
+
def mesg(message) ::ShellOpts.mesg(message) end
|
444
|
+
end
|
361
445
|
|
362
|
-
|
363
|
-
|
364
|
-
end
|
446
|
+
module Verbose
|
447
|
+
@is_included = false
|
448
|
+
def self.is_included?() @is_included end
|
449
|
+
def self.included(...) @is_included = true; super end
|
365
450
|
|
366
|
-
def
|
367
|
-
|
368
|
-
end
|
451
|
+
def notice(message) ::ShellOpts.notice(message) end
|
452
|
+
def mesg(message) ::ShellOpts.mesg(message) end
|
453
|
+
def verb(level = 1, message) ::ShellOpts.verb(level, message) end
|
454
|
+
end
|
369
455
|
|
370
|
-
|
371
|
-
|
372
|
-
end
|
456
|
+
module Debug
|
457
|
+
@is_included = false
|
458
|
+
def self.is_included?() @is_included end
|
459
|
+
def self.included(...) @is_included = true; super end
|
460
|
+
|
461
|
+
def debug(message) ::ShellOpts.debug(message) end
|
373
462
|
end
|
374
463
|
|
375
464
|
module ErrorHandling
|