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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43a4d96ef2927b72add380e17e0dad25b2004659491a5dc4adb36f3c9965c8c9
4
- data.tar.gz: 507780f5ea40771db0ffbc4d0247b9f8f5d08044953dc0c387e92432c42879df
3
+ metadata.gz: a2e9d4e1a3ad5eca9fd21a3bad085a252ff7b6921ec889e4da404c989304bf9c
4
+ data.tar.gz: fa9f67483fcfb303e76f8b6b2282948f117f2497b734b6fdd6ca6f91a29b3d96
5
5
  SHA512:
6
- metadata.gz: d57c4a7245f5c22b74c762d986b8a80ca331643a3f32f63421f08f412da01e3f6e21c76e0fe1921fa8337ad2cbc168e00bb9f722f3ebfc1fdb7468619169658d
7
- data.tar.gz: 4ab7b06dd787db06af9ca0d70f6d44a26e251707badef5beecdb3bb719a3fc1bebf5357f00df2702589f4ea6960ebc6af83329adadc3e65bd2746e3dbb536fca
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
@@ -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
- attr_reader :ident
75
+ attr_accessor :ident
76
76
 
77
77
  # Canonical name (String) of the object
78
78
  #
@@ -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 add_version_option
128
- option_token = Token.new(:option, 1, 1, "--version")
129
- brief_token = Token.new(:brief, 1, 1, "Write version number and exit")
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
- end
134
-
135
- def add_help_options
136
- option_token = Token.new(:option, 1, 1, "-h,help")
137
- brief_token = Token.new(:brief, 1, 1, "Write help text and exit")
138
- paragraph_token = Token.new(:text, 1, 1,
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
 
@@ -84,19 +84,27 @@ module ShellOpts
84
84
  }
85
85
  end
86
86
 
87
- # Returns a hash of the given options if defined. Returns all options if no
88
- # 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
+ #
89
94
  def to_h(*keys)
90
95
  keys = ::Kernel::Array(keys).flatten
91
- if keys.empty?
92
- self.to_h(@__grammar__.options.map(&:ident))
93
- else
94
- keys.map { |key|
95
- self.__send__("#{key}?".to_sym) ? [key, self.__send__(key)] : nil
96
- }.compact.to_h
97
- end
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.
@@ -1,3 +1,3 @@
1
1
  module ShellOpts
2
- VERSION = "2.0.18"
2
+ VERSION = "2.0.21"
3
3
  end
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
- def version
101
- return @version if @version
102
- exe = caller.find { |line| line =~ /`<top \(required\)>'$/ }&.sub(/:.*/, "")
103
- file = Dir.glob(File.dirname(exe) + "/../lib/*/version.rb").first
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
- # Add message options (TODO)
109
- attr_accessor :msgopts
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, help: true, version: true, msgopts: false, float: true, exception: false)
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
- @use_version = version ? true : false
129
- @version = @use_version && @version != true ? @version : nil
130
- @msgopts, @float, @exception = msgopts, float, exception
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
- ast.add_version_option if @use_version
143
- ast.add_help_options if @help
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
- if @program.version?
157
- puts version
158
- exit
159
- elsif @program.help?
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, msgopts: false, **opts)
326
- msgopts ||= Messages.is_included?
327
- ShellOpts.process(spec, argv, msgopts: msgopts, **opts)
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
- # The Include module brings the reporting methods into the namespace when
349
- # included
350
- module Messages
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.include(...)
354
- @is_included = true
355
- super
356
- end
439
+ def self.included(...) @is_included = true; super end
357
440
 
358
- def notice(message)
359
- $stderr.puts "#{name}: #{message}" if !quiet?
360
- end
441
+ def notice(message) ::ShellOpts.notice(message) end
442
+ def mesg(message) ::ShellOpts.mesg(message) end
443
+ end
361
444
 
362
- def mesg(message)
363
- $stdout.puts message if !quiet?
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 verb(level = 1, message)
367
- $stdout.puts message if level <= @verbose
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
- def debug(message)
371
- $stdout.puts message if debug?
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