shellopts 2.0.16 → 2.0.19

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: 256d4f5a28f8f4028030ac17631d4e93a36584b77c96cd8090ecab8dc393f36f
4
- data.tar.gz: 381c0515800feb0a4d2327d336ed35c43e5441915cf6aef5cb4ebd365cbe8d39
3
+ metadata.gz: 35b9e7b5699cc1a5637d4c00dd95c74047074f1227b0d05f9a9c8b04e1e2cc8b
4
+ data.tar.gz: c8deed23c8e6f0b26d080e15d2830edb5e65d1b5f2f821ccd83db7db27d7aefd
5
5
  SHA512:
6
- metadata.gz: ac3af229c984160970034b5bbef21584bb5d78e6581f3efb2d4d9c10e9217ebde1e3cc248136224c4859d9a73ded08fc1a6782ebddb55a2d8eff1b5d480c42f9
7
- data.tar.gz: 855d30fcd66f441a09ab53d50284a11e2a24bcd8c24b9c5573e1783c571005ab6b7a383aefbd0d9883893207550fe9bd880def44e361f000c87289a3476fe48f
6
+ metadata.gz: 5d3e96ec825ca58c0e653613ab84e2ede3e661737f5699571c81aaf9b2cb1252ea802581e5b981a1757d8a5a750ba998c43730c6d2da7d8228b721b06a45640e
7
+ data.tar.gz: e64eeac4c3bf784f8ef3a5e3a61c596e262bd23c4ee05857f76138f82ff2180afb72ccd5c2b7ffedc4e6aa2887b10c76e3a80c33e24ceadd20e71513549b5bf0
data/PROBLEMS ADDED
@@ -0,0 +1,52 @@
1
+
2
+ # Subcommand options are not recognized unless there exists an option on the
3
+ # program level (try to remove -a below)
4
+
5
+ SPEC = %(
6
+ Foreign data wrapper maintenance tool
7
+
8
+ -a,an-option
9
+ Gryf
10
+
11
+ list.servers! -- DATABASE
12
+ List foreign servers
13
+
14
+ create.server! -- DATABASE FDW-SERVER MSSQL-DATABASE [MSSQL-HOST [MSSQL-PORT]]
15
+ Create a new FDW server
16
+
17
+ -c,credentials=EFILE
18
+ Credentials file. The credentials file is a YAML formatted file that can
19
+ define the fields 'user', 'password', 'host', and 'port'
20
+
21
+ drop.server! -- DATABASE FDW-SERVER
22
+ Drop a FDW server. This cascades to FDW users too
23
+
24
+ list.users! -- DATABASE
25
+ List FDW users. 'users' in this context is postgres users that have an
26
+ associated FDW user mapping
27
+
28
+ --servers @ Also list the user's FDW servers
29
+
30
+ create.user! -- DATABASE FDW-SERVER [FDW-USER [FDW-PASSWORD]]
31
+ Create a FDW user. The user has to exist beforehand, this command only adds
32
+ a user mapping to the user and grants the usage privilege
33
+
34
+ -c,credentials=EFILE
35
+ Credentials file
36
+
37
+ drop.user! -- DATABASE FDW-USER
38
+ Drops a FDW user. The postgres user is not dropped but the user's user
39
+ mapping and grants are
40
+ )
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/lib/ext/follow.rb ADDED
@@ -0,0 +1,26 @@
1
+
2
+ module Algorithm
3
+ class FollowEnumerator < Enumerator
4
+ def initialize(object, method = nil, &block)
5
+ closure = method ? lambda { |object| object.__send__(method) } : block
6
+ super() { |yielder|
7
+ while object
8
+ yielder << object
9
+ object = closure.call(object)
10
+ end
11
+ }
12
+ end
13
+ end
14
+
15
+ def follow(object, method = nil, &block)
16
+ !method.nil? || block_given? or raise ArgumentError, "Needs either a method or a block"
17
+ method.nil? == block_given? or raise ArgumentError, "Can't use both method and block"
18
+ FollowEnumerator.new(object, method, &block)
19
+ end
20
+
21
+ module_function :follow
22
+ end
23
+
24
+
25
+
26
+
@@ -30,7 +30,7 @@ module ShellOpts
30
30
  def ancestors() parents.reverse end
31
31
 
32
32
  def inspect
33
- "#{self.class}"
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
- attr_reader :ident
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.sub(".", "!.").split(".").map(&:to_sym))
244
+ when Symbol; lookup(key.to_s.gsub(".", "!.").split(".").map(&:to_sym))
245
245
  when Array; lookup(key)
246
246
  else
247
247
  nil
@@ -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
 
@@ -52,7 +52,7 @@ module ShellOpts
52
52
  # These methods can be overridden by an option or a command (this constant
53
53
  # is not used - it is just for informational purposes)
54
54
  OVERRIDEABLE_METHOD_NAMES = %w(
55
- subcommand subcommand! supercommand!
55
+ subcommand subcommand! subcommands subcommands! supercommand!
56
56
  )
57
57
 
58
58
  # Redefine ::new to call #__initialize__
@@ -62,40 +62,28 @@ module ShellOpts
62
62
  object
63
63
  end
64
64
 
65
- # Return command or option object if present, otherwise nil. Returns a
66
- # possibly empty array of option objects if the option is repeatable
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 name or identifier of the object or any any option
69
- # alias. Eg. :f, '-f', :file, or '--file' are all usable as option keys
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 [](key)
73
- case object = __grammar__[key]
74
- when ::ShellOpts::Grammar::Command
75
- object.ident == __subcommand__!.__ident__ ? __subcommand__! : nil
76
- when ::ShellOpts::Grammar::Option
77
- if object.repeatable?
78
- __option_hash__[object.ident] || []
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__[object.ident]
81
- end
82
- else
83
- ::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
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
- # Return true if the given command or option is present
88
- def key?(key)
89
- case object = __grammar__[key]
90
- when ::ShellOpts::Grammar::Command
91
- object.ident == __subcommand__
92
- when ::ShellOpts::Grammar::Option
93
- __option_hash__.key?(object.ident)
94
- else
95
- ::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
96
- end
97
- end
98
-
99
87
  # Returns a hash of the given options if defined. Returns all options if no
100
88
  # options are given
101
89
  def to_h(*keys)
@@ -133,6 +121,13 @@ module ShellOpts
133
121
  #
134
122
  def subcommand!() __subcommand__! end
135
123
 
124
+ # Returns the concatenated identifier of subcommands (eg. :cmd.subcmd!)
125
+ def subcommands() __subcommands__ end
126
+
127
+ # Returns the subcommands in an array. This doesn't include the top-level
128
+ # program object
129
+ def subcommands!() __subcommands__! end
130
+
136
131
  # The parent command or nil. Initialized by #add_command
137
132
  #
138
133
  # Note: Can be overridden by a subcommand declaration (but not an
@@ -141,7 +136,7 @@ module ShellOpts
141
136
  #
142
137
  def supercommand!() __supercommand__ end
143
138
 
144
- # UID of command/program
139
+ # UID of command/program (String)
145
140
  def __uid__() @__grammar__.uid end
146
141
 
147
142
  # Identfier including the exclamation mark (Symbol)
@@ -180,6 +175,16 @@ module ShellOpts
180
175
  # The actual subcommand object or nil if not present
181
176
  def __subcommand__!() @__subcommand__ end
182
177
 
178
+ # Implementation of the #subcommands method
179
+ def __subcommands__()
180
+ __subcommands__!.last&.__uid__&.to_sym
181
+ end
182
+
183
+ # Implementation of the #subcommands! method
184
+ def __subcommands__!()
185
+ ::Algorithm.follow(self.__subcommand__!, :__subcommand__!).to_a
186
+ end
187
+
183
188
  private
184
189
  def __initialize__(grammar)
185
190
  @__grammar__ = grammar
@@ -292,6 +297,18 @@ module ShellOpts
292
297
 
293
298
  # The top-level command
294
299
  class Program < Command
300
+ # Accessors for standard options values that are not affected if the option
301
+ # is renamed
302
+ attr_accessor :__quiet__
303
+ attr_accessor :__verbose__
304
+ attr_accessor :__debug__
305
+
306
+ def initialize
307
+ super
308
+ @__quiet__ = false
309
+ @__verbose__ = 0
310
+ @__debug__ = false
311
+ end
295
312
  end
296
313
 
297
314
  # 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.16"
2
+ VERSION = "2.0.19"
3
3
  end
data/lib/shellopts.rb CHANGED
@@ -5,6 +5,7 @@ require 'constrain'
5
5
  include Constrain
6
6
 
7
7
  require 'ext/array.rb'
8
+ require 'ext/follow.rb'
8
9
  require 'ext/forward_to.rb'
9
10
  require 'ext/lcs.rb'
10
11
  include ForwardTo
@@ -95,17 +96,20 @@ module ShellOpts
95
96
  # Automatically add a -h and a --help option if true
96
97
  attr_reader :help
97
98
 
98
- # Version of client program. If not nil a --version option is added to the program
99
- def version
100
- return @version if @version
101
- exe = caller.find { |line| line =~ /`<top \(required\)>'$/ }&.sub(/:.*/, "")
102
- file = Dir.glob(File.dirname(exe) + "/../lib/*/version.rb").first
103
- @version = IO.read(file).sub(/^.*VERSION\s*=\s*"(.*?)".*$/m, '\1') or
104
- raise ArgumentError, "ShellOpts needs an explicit version"
105
- 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
106
104
 
107
- # Add message options (TODO)
108
- 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
109
113
 
110
114
  # Floating options
111
115
  attr_accessor :float
@@ -121,12 +125,33 @@ module ShellOpts
121
125
  attr_reader :tokens
122
126
  alias_method :ast, :grammar
123
127
 
124
- 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
+
125
146
  @name = name || File.basename($PROGRAM_NAME)
126
147
  @help = help
127
- @use_version = version ? true : false
128
- @version = @use_version && @version != true ? @version : nil
129
- @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
130
155
  end
131
156
 
132
157
  # Compile source and return grammar object. Also sets #spec and #grammar.
@@ -138,8 +163,31 @@ module ShellOpts
138
163
  @file = find_caller_file
139
164
  @tokens = Lexer.lex(name, @spec, @oneline)
140
165
  ast = Parser.parse(tokens)
141
- ast.add_version_option if @use_version
142
- 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
+
143
191
  @grammar = Analyzer.analyze(ast)
144
192
  }
145
193
  self
@@ -152,16 +200,22 @@ module ShellOpts
152
200
  handle_exceptions {
153
201
  @argv = argv.dup
154
202
  @program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)
155
- if @program.version?
156
- puts version
157
- exit
158
- elsif @program.help?
159
- if @program[:help].name == "-h"
160
- ShellOpts.brief
161
- else
203
+
204
+ # Process standard options (that may have been renamed)
205
+ if @program.__send__(:"#{@help_option.ident}?")
206
+ if @program[:help].name =~ /^--/
162
207
  ShellOpts.help
208
+ else
209
+ ShellOpts.brief
163
210
  end
164
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
165
219
  end
166
220
  }
167
221
  self
@@ -234,6 +288,13 @@ module ShellOpts
234
288
  def self.help(subject = nil) ::ShellOpts.instance.help(subject) end
235
289
 
236
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
+
237
298
  def handle_exceptions(&block)
238
299
  return yield if exception
239
300
  begin
@@ -321,9 +382,13 @@ module ShellOpts
321
382
  end
322
383
  end
323
384
 
324
- def self.process(spec, argv, msgopts: false, **opts)
325
- msgopts ||= Messages.is_included?
326
- ShellOpts.process(spec, argv, msgopts: msgopts, **opts)
385
+
386
+ def self.process(spec, argv, quiet: nil, verbose: nil, debug: nil, **opts)
387
+ constrain quiet, String, true, false, nil
388
+ quiet = quiet.nil? ? Message.is_included? || Verbose.is_included? : quiet
389
+ verbose = verbose.nil? ? ::ShellOpts::Verbose.is_included? : verbose
390
+ debug = debug.nil? ? Debug.is_included? : debug
391
+ ShellOpts.process(spec, argv, quiet: quiet, verbose: verbose, debug: debug, **opts)
327
392
  end
328
393
 
329
394
  @instance = nil
@@ -344,31 +409,57 @@ module ShellOpts
344
409
  exit 1
345
410
  end
346
411
 
347
- # The Include module brings the reporting methods into the namespace when
348
- # included
349
- module Messages
412
+ def self.notice(message)
413
+ $stderr.puts "#{instance.program.__grammar__.name}: #{message}" \
414
+ if !instance.quiet || !instance.program.quiet?
415
+ end
416
+
417
+ def self.mesg(message)
418
+ $stdout.puts message if !instance.quiet || !instance.program.__quiet__
419
+ end
420
+
421
+ def self.verb(level = 1, message)
422
+ $stdout.puts message if instance.verbose && level <= instance.program.__verbose__
423
+ end
424
+
425
+ def self.debug(message)
426
+ $stdout.puts message if instance.debug && instance.program.__debug__
427
+ end
428
+
429
+ def self.quiet_flag
430
+ end
431
+
432
+ def self.verbose_flag
433
+ end
434
+
435
+ def self.debug_flag
436
+ end
437
+
438
+ module Message
350
439
  @is_included = false
351
440
  def self.is_included?() @is_included end
352
- def self.include(...)
353
- @is_included = true
354
- super
355
- end
441
+ def self.included(...) @is_included = true; super end
356
442
 
357
- def notice(message)
358
- $stderr.puts "#{name}: #{message}" if !quiet?
359
- end
443
+ def notice(message) ::ShellOpts.notice(message) end
444
+ def mesg(message) ::ShellOpts.mesg(message) end
445
+ end
360
446
 
361
- def mesg(message)
362
- $stdout.puts message if !quiet?
363
- end
447
+ module Verbose
448
+ @is_included = false
449
+ def self.is_included?() @is_included end
450
+ def self.included(...) @is_included = true; super end
364
451
 
365
- def verb(level = 1, message)
366
- $stdout.puts message if level <= @verbose
367
- end
452
+ def notice(message) ::ShellOpts.notice(message) end
453
+ def mesg(message) ::ShellOpts.mesg(message) end
454
+ def verb(level = 1, message) ::ShellOpts.verb(level, message) end
455
+ end
368
456
 
369
- def debug(message)
370
- $stdout.puts message if debug?
371
- end
457
+ module Debug
458
+ @is_included = false
459
+ def self.is_included?() @is_included end
460
+ def self.included(...) @is_included = true; super end
461
+
462
+ def debug(message) ::ShellOpts.debug(message) end
372
463
  end
373
464
 
374
465
  module ErrorHandling