shellopts 2.0.16 → 2.0.19

Sign up to get free protection for your applications and to get access to all the features.
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