shellopts 2.0.3 → 2.0.6

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: c3b7401560ce40fd21fcbc1e1c666464f2b01a4da8a93a4ec32010aa480bb8aa
4
- data.tar.gz: f998e1ab794ab4e1b711d72e7d4e6820304ab6733bc9ae9d1fa137ab4323c633
3
+ metadata.gz: 52971494b88f77b6b66a95e8409de7cbebcbf030588089c395424b7e86f5e372
4
+ data.tar.gz: e00e482a0db690c2b7bd0861589a5179a67d7cec9b285ebcc1d2b5a7b6f73ff7
5
5
  SHA512:
6
- metadata.gz: a9b7856eb14fa2166bbc80dcace1724ee1a6499e93ce63f4971bdc9c4b27df4e5b26bc9d147a728de059d6e25d4e561a24aff39e2add47ca9a29aa5ad186b88f
7
- data.tar.gz: 2cf14edab9a3cbb43ed3991596574e372af6527736031bd04626fb202cdf739b46565cee5c19cc67e46573c570651b7290852728958b075eeabe0df4f5197b0b
6
+ metadata.gz: 2206c1e56c4239472bf5b697854fc4d7ba06bd9dedfe4eb2f86c2e115f4ddd52f66e9b4f47cbba41b1b4eafb08b8666b2115df7f70d769465dcb3e4a344d322a
7
+ data.tar.gz: f9bc90bdba87159a2821afb3c719ba4c4fbc7077601266d9e61c5f83273924bc23e9fa2208bf727855fc99b6acac09a97ec3dc58be179f649c9ed6dbb4fcc259
@@ -38,16 +38,17 @@ module ShellOpts
38
38
  using Ext::Array::Wrap
39
39
 
40
40
  def puts_usage(bol: false)
41
+ width = [Formatter.rest, Formatter::USAGE_MAX_WIDTH].min
41
42
  if descrs.size == 0
42
43
  print (lead = Formatter.command_prefix || "")
43
44
  indent(lead.size, ' ', bol: bol && lead == "") {
44
- puts render(:multi, Formatter::USAGE_MAX_WIDTH)
45
+ puts render(:multi, width)
45
46
  }
46
47
  else
47
48
  lead = Formatter.command_prefix || ""
48
49
  descrs.each { |descr|
49
50
  print lead
50
- puts render(:single, Formatter::USAGE_MAX_WIDTH, args: [descr.text])
51
+ puts render(:single, width, args: [descr.text])
51
52
  }
52
53
  end
53
54
  end
@@ -199,8 +200,8 @@ module ShellOpts
199
200
  # Number of characters between columns in brief output
200
201
  BRIEF_COL_SEP = 2
201
202
 
202
- # Maximum width of first column in brief option and command lists
203
- BRIEF_COL1_MIN_WIDTH = 6
203
+ # Minimum width of first column in brief option and command lists
204
+ BRIEF_COL1_MIN_WIDTH = 20
204
205
 
205
206
  # Maximum width of first column in brief option and command lists
206
207
  BRIEF_COL1_MAX_WIDTH = 40
@@ -122,6 +122,22 @@ module ShellOpts
122
122
  def self.parse(token)
123
123
  super(nil, token)
124
124
  end
125
+
126
+ def add_stdopts
127
+ option_token = Token.new(:option, 1, 1, "--version")
128
+ brief_token = Token.new(:brief, 1, 1, "Write version number and exit")
129
+ group = OptionGroup.new(self, option_token)
130
+ option = Option.parse(group, option_token)
131
+ brief = Brief.parse(group, brief_token)
132
+
133
+ option_token = Token.new(:option, 1, 1, "-h,help")
134
+ brief_token = Token.new(:brief, 1, 1, "Write help text and exit")
135
+ paragraph_token = Token.new(:text, 1, 1, "-h prints a brief help text, --help prints a longer man-style description of the command")
136
+ group = OptionGroup.new(self, option_token)
137
+ option = Option.parse(group, option_token)
138
+ brief = Brief.parse(group, brief_token)
139
+ paragraph = Paragraph.parse(group, paragraph_token)
140
+ end
125
141
  end
126
142
 
127
143
  class ArgSpec
@@ -146,6 +162,14 @@ module ShellOpts
146
162
  @nodes = {}
147
163
  end
148
164
 
165
+ # def add_stdopts
166
+ # version_token = Token.new(:option, 1, 1, "--version")
167
+ # version_brief = Token.new(:brief, 1, 1, "Gryf gryf")
168
+ # group = Grammar::OptionGroup.new(@program, version_token)
169
+ # option = Grammar::Option.parse(group, version_token)
170
+ # brief = Grammr::Brief.parse(option, version_brief)
171
+ # end
172
+
149
173
  def parse()
150
174
  @program = Grammar::Program.parse(@tokens.shift)
151
175
  oneline = @tokens.first.lineno == @tokens.last.lineno
@@ -4,30 +4,33 @@
4
4
  module ShellOpts
5
5
  # Command represents a program or a subcommand. It is derived from
6
6
  # BasicObject to have only a minimum of inherited member methods.
7
- # Additional methods defined in Command use the '__<identifier>__' naming
8
- # convention that doesn't collide with option or subcommand names but
9
- # they're rarely used in application code
10
7
  #
11
8
  # The names of the inherited methods can't be used as options or
12
9
  # command namess. They are: instance_eval, instance_exec method_missing,
13
10
  # singleton_method_added, singleton_method_removed, and
14
- # singleton_method_undefined
11
+ # singleton_method_undefined.
12
+ #
13
+ # Additional methods defined in Command use the '__<identifier>__' naming
14
+ # convention that doesn't collide with option or subcommand names but
15
+ # they're rarely used in application code
15
16
  #
16
17
  # Command also defines #subcommand and #subcommand! but they can be
17
18
  # overshadowed by an option or command declaration. Their values can
18
19
  # still be accessed using the dashed name, though
19
20
  #
20
- # Options and subcommands can be accessed using #[]
21
+ # Option and Command objects can be accessed using #[]. #key? is also defined
21
22
  #
22
23
  # The following methods are created dynamically for each declared option
23
24
  # with an attribute name
24
25
  #
25
- # def <identifier>(default = nil) self["<identifier>"] || default end
26
- # def <identifier>=(value) self["<identifier>"] = value end
27
- # def <identifier>?() self.key?("<identifier>") end
26
+ # <identifier>(default = nil)
27
+ # <identifier>=(value)
28
+ # <identifier>?()
29
+ #
30
+ # The default value is used if the option or its value is missing
28
31
  #
29
32
  # Options without an an attribute can still be accessed using #[] or trough
30
- # #__options__ or #__options_list__):
33
+ # #__option_values__, #__option_hash, or #__options_list__
31
34
  #
32
35
  # Each subcommand has a single method:
33
36
  #
@@ -42,9 +45,9 @@ module ShellOpts
42
45
 
43
46
  # These names can't be used as option or command names
44
47
  RESERVED_OPTION_NAMES = %w(
45
- is_a
46
- instance_eval instance_exec method_missing singleton_method_added
47
- singleton_method_removed singleton_method_undefined)
48
+ is_a instance_eval instance_exec method_missing singleton_method_added
49
+ singleton_method_removed singleton_method_undefined
50
+ )
48
51
 
49
52
  # These methods can be overridden by an option (the value is not used -
50
53
  # this is just for informational purposes)
@@ -59,43 +62,25 @@ module ShellOpts
59
62
  object
60
63
  end
61
64
 
62
- # Return command object or option argument value if present, otherwise nil
65
+ # Return command or option object if present, otherwise nil. Returns a
66
+ # possibly empty array of option objects if the option is repeatable
63
67
  #
64
68
  # The key is the name or identifier of the object or any any option
65
69
  # alias. Eg. :f, '-f', :file, or '--file' are all usable as option keys
66
70
  # and :cmd! or 'cmd' as command keys
67
71
  #
68
- # For options, the returned value is the argument given by the user
69
- # optionally converted to Integer or Float or nil if the option doesn't
70
- # take arguments. If the option takes an argument and it is repeatable
71
- # the value is an array of the arguments. Repeatable options without
72
- # arguments have the number of occurences as the value
73
- #
74
72
  def [](key)
75
73
  case object = __grammar__[key]
76
74
  when ::ShellOpts::Grammar::Command
77
75
  object.ident == __subcommand__!.__ident__ ? __subcommand__! : nil
78
76
  when ::ShellOpts::Grammar::Option
79
- __options__[object.ident]
80
- else
81
- nil
82
- end
83
- end
84
-
85
- # Assign a value to an existing option. This can be used to implement
86
- # default values. #[]= doesn't currently check the type of the given
87
- # value so take care. Note that the corresponding option(s) in
88
- # #__option_list__ is not updated
89
- def []=(key, value)
90
- case object = __grammar__[key]
91
- when ::ShellOpts::Grammar::Command
92
- ::Kernel.raise ArgumentError, "#{key.inspect} is not an option"
93
- when ::ShellOpts::Grammar::Option
94
- object.argument? || object.repeatable? or
95
- ::Kernel.raise ArgumentError, "#{key.inspect} is not assignable"
96
- __options__[object.ident] = value
77
+ if object.repeatable?
78
+ __option_hash__[object.ident] || []
79
+ else
80
+ __option_hash__[object.ident]
81
+ end
97
82
  else
98
- ::Kernel.raise ArgumentError, "Unknown option or command: #{key.inspect}"
83
+ ::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
99
84
  end
100
85
  end
101
86
 
@@ -103,11 +88,11 @@ module ShellOpts
103
88
  def key?(key)
104
89
  case object = __grammar__[key]
105
90
  when ::ShellOpts::Grammar::Command
106
- object.ident == __subcommand__!.ident ? __subcommand__! : nil
91
+ object.ident == __subcommand__
107
92
  when ::ShellOpts::Grammar::Option
108
- __options__.key?(object.ident)
93
+ __option_hash__.key?(object.ident)
109
94
  else
110
- nil
95
+ ::Kernel.raise ::ArgumentError, "Unknown command or option: '#{key}'"
111
96
  end
112
97
  end
113
98
 
@@ -153,13 +138,17 @@ module ShellOpts
153
138
  # depending on the option's type. Repeated options options without
154
139
  # arguments have the number of occurences as the value, with arguments
155
140
  # the value is an array of the given values
156
- attr_reader :__options__
141
+ attr_reader :__option_values__
157
142
 
158
143
  # List of Option objects for the subcommand in the same order as
159
144
  # given by the user but note that options are reordered to come after
160
145
  # their associated subcommand if float is true. Repeated options are not
161
146
  # collapsed
162
147
  attr_reader :__option_list__
148
+
149
+ # Map from identifier to option object or to a list of option objects if
150
+ # the option is repeatable
151
+ attr_reader :__option_hash__
163
152
 
164
153
  # The subcommand identifier (a Symbol incl. the exclamation mark) or nil
165
154
  # if not present. Use #subcommand!, or the dynamically generated
@@ -172,9 +161,10 @@ module ShellOpts
172
161
  private
173
162
  def __initialize__(grammar)
174
163
  @__grammar__ = grammar
175
- @__options__ = {}
164
+ @__option_values__ = {}
176
165
  @__option_list__ = []
177
- @__options__ = {}
166
+ @__option_hash__ = {}
167
+ @__option_values__ = {}
178
168
  @__subcommand__ = nil
179
169
 
180
170
  __define_option_methods__
@@ -182,36 +172,34 @@ module ShellOpts
182
172
 
183
173
  def __define_option_methods__
184
174
  @__grammar__.options.each { |opt|
185
- next if opt.attr.nil?
186
175
  if opt.argument? || opt.repeatable?
187
176
  if opt.optional?
188
177
  self.instance_eval %(
189
178
  def #{opt.attr}(default = nil)
190
- if @__options__.key?(:#{opt.attr})
191
- @__options__[:#{opt.attr}] || default
179
+ if @__option_values__.key?(:#{opt.attr})
180
+ @__option_values__[:#{opt.attr}]
192
181
  else
193
- nil
182
+ default
194
183
  end
195
184
  end
196
185
  )
197
- elsif !opt.argument?
186
+ elsif !opt.argument? # Repeatable w/o argument
198
187
  self.instance_eval %(
199
- def #{opt.attr}(default = nil)
200
- if @__options__.key?(:#{opt.attr})
201
- value = @__options__[:#{opt.attr}]
202
- value == 0 ? default : value
188
+ def #{opt.attr}(default = [])
189
+ if @__option_values__.key?(:#{opt.attr})
190
+ @__option_values__[:#{opt.attr}]
203
191
  else
204
- nil
192
+ default
205
193
  end
206
194
  end
207
195
  )
208
196
  else
209
- self.instance_eval("def #{opt.attr}() @__options__[:#{opt.attr}] end")
197
+ self.instance_eval("def #{opt.attr}() @__option_values__[:#{opt.attr}] end")
210
198
  end
211
- self.instance_eval("def #{opt.attr}=(value) @__options__[:#{opt.attr}] = value end")
212
- @__options__[opt.attr] = 0 if !opt.argument?
199
+ self.instance_eval("def #{opt.attr}=(value) @__option_values__[:#{opt.attr}] = value end")
200
+ @__option_values__[opt.attr] = 0 if !opt.argument?
213
201
  end
214
- self.instance_eval("def #{opt.attr}?() @__options__.key?(:#{opt.attr}) end")
202
+ self.instance_eval("def #{opt.attr}?() @__option_values__.key?(:#{opt.attr}) end")
215
203
  }
216
204
 
217
205
  @__grammar__.commands.each { |cmd|
@@ -228,14 +216,15 @@ module ShellOpts
228
216
  ident = option.grammar.ident
229
217
  @__option_list__ << option
230
218
  if option.repeatable?
219
+ (@__option_hash__[ident] ||= []) << option
231
220
  if option.argument?
232
- (@__options__[ident] ||= []) << option.argument
221
+ (@__option_values__[ident] ||= []) << option.argument
233
222
  else
234
- @__options__[ident] ||= 0
235
- @__options__[ident] += 1
223
+ @__option_values__[ident] = (@__option_values__[ident] || 0) + 1
236
224
  end
237
225
  else
238
- @__options__[ident] = option.argument
226
+ @__option_hash__[ident] = option
227
+ @__option_values__[ident] = option.argument
239
228
  end
240
229
  end
241
230
 
@@ -254,7 +243,7 @@ module ShellOpts
254
243
 
255
244
  # Option models an option as given by the user on the subcommand line.
256
245
  # Compiled options (and possibly aggregated) options are stored in the
257
- # Command#__options__ array
246
+ # Command#__option_values__ array
258
247
  class Option
259
248
  # Associated Grammar::Option object
260
249
  attr_reader :grammar
@@ -1,3 +1,3 @@
1
1
  module ShellOpts
2
- VERSION = "2.0.3"
2
+ VERSION = "2.0.6"
3
3
  end
data/lib/shellopts.rb CHANGED
@@ -102,6 +102,9 @@ module ShellOpts
102
102
  attr_accessor :stdopts
103
103
  attr_accessor :msgopts
104
104
 
105
+ # Version of client program. This is only used if +stdopts+ is true
106
+ attr_reader :version
107
+
105
108
  # Interpreter flags
106
109
  attr_accessor :float
107
110
 
@@ -116,9 +119,9 @@ module ShellOpts
116
119
  attr_reader :tokens
117
120
  alias_method :ast, :grammar # Oops - defined earlier FIXME
118
121
 
119
- def initialize(name: nil, stdopts: true, msgopts: false, float: true, exception: false)
122
+ def initialize(name: nil, stdopts: true, version: nil, msgopts: false, float: true, exception: false)
120
123
  @name = name || File.basename($PROGRAM_NAME)
121
- @stdopts, @msgopts, @float, @exception = stdopts, msgopts, float, exception
124
+ @stdopts, @version, @msgopts, @float, @exception = stdopts, version, msgopts, float, exception
122
125
  end
123
126
 
124
127
  # Compile source and return grammar object. Also sets #spec and #grammar.
@@ -130,7 +133,7 @@ module ShellOpts
130
133
  @file = find_caller_file
131
134
  @tokens = Lexer.lex(name, @spec, @oneline)
132
135
  ast = Parser.parse(tokens)
133
- # TODO: Add standard and message options and their handlers
136
+ ast.add_stdopts if stdopts
134
137
  @grammar = Analyzer.analyze(ast)
135
138
  }
136
139
  self
@@ -143,6 +146,20 @@ module ShellOpts
143
146
  handle_exceptions {
144
147
  @argv = argv.dup
145
148
  @program, @args = Interpreter.interpret(grammar, argv, float: float, exception: exception)
149
+ if stdopts
150
+ if @program.version?
151
+ version or raise ArgumentError, "Version not specified"
152
+ puts version
153
+ exit
154
+ elsif @program.help?
155
+ if @program[:help].name == "-h"
156
+ ShellOpts.brief
157
+ else
158
+ ShellOpts.help
159
+ end
160
+ exit
161
+ end
162
+ end
146
163
  }
147
164
  self
148
165
  end
@@ -177,7 +194,7 @@ module ShellOpts
177
194
  saved = $stdout
178
195
  $stdout = $stderr
179
196
  $stderr.puts "#{name}: #{message}"
180
- Formatter.usage(program)
197
+ Formatter.usage(grammar)
181
198
  exit 1
182
199
  ensure
183
200
  $stdout = saved
@@ -201,11 +218,12 @@ module ShellOpts
201
218
  def brief() Formatter.brief(@grammar) end
202
219
 
203
220
  # Print help for the given subject or the full documentation if +subject+
204
- # is nil
221
+ # is nil. Clears the screen beforehand if :clear is true
205
222
  #
206
- def help(subject = nil)
223
+ def help(subject = nil, clear: true)
207
224
  node = (subject ? @grammar[subject] : @grammar) or
208
225
  raise ArgumentError, "No such command: '#{subject&.sub(".", " ")}'"
226
+ print '' if clear
209
227
  Formatter.help(node)
210
228
  end
211
229
 
@@ -310,6 +328,7 @@ module ShellOpts
310
328
  def self.instance?() !@instance.nil? end
311
329
  def self.instance() @instance or raise Error, "ShellOpts is not initialized" end
312
330
  def self.instance=(instance) @instance = instance end
331
+ def self.shellopts() instance end
313
332
 
314
333
  forward_self_to :instance, :error, :failure
315
334
 
data/main CHANGED
@@ -7,18 +7,33 @@ require 'shellopts'
7
7
 
8
8
  include ShellOpts
9
9
 
10
+ VERSION = "1.2.3"
10
11
 
11
12
  SPEC = %(
12
- -a,alpha @ Brief comment for -a and --alpha options
13
- Longer and more elaborate description of the --alpha option
13
+ -a @ An option
14
+ )
15
+ opts, args = ShellOpts::process(SPEC, ARGV, version: VERSION)
16
+ #ShellOpts::ShellOpts.help
14
17
 
15
- -b,beta=ARG
16
- @ Alternative style of brief comment
17
18
 
18
- Longer and more elaborate description of the --beta option
19
- )
20
19
 
21
- opts, args = ShellOpts.process(SPEC, ARGV)
20
+
21
+
22
+
23
+ __END__
24
+
25
+
26
+ #SPEC = %(
27
+ # -a,alpha @ Brief comment for -a and --alpha options
28
+ # Longer and more elaborate description of the --alpha option
29
+ #
30
+ # -b,beta=ARG
31
+ # @ Alternative style of brief comment
32
+ #
33
+ # Longer and more elaborate description of the --beta option
34
+ #)
35
+ #
36
+ #opts, args = ShellOpts.process(SPEC, ARGV)
22
37
  #puts "opts.alpha?: #{opts.alpha?.inspect}"
23
38
  #puts "opts.alpha: #{opts.alpha.inspect}"
24
39
  #puts "opts.beta?: #{opts.beta?.inspect}"
@@ -266,8 +281,9 @@ SPEC4 = %(
266
281
 
267
282
  SPEC5 = "cmd! cmd!"
268
283
 
269
- shellopts = ShellOpts::ShellOpts.new(exception: false)
284
+ shellopts = ShellOpts::ShellOpts.new(exception: true)
270
285
  #shellopts.compile("cmd! cmd!")
286
+
271
287
  shellopts.compile(SPEC)
272
288
  #shellopts.compile(SPEC2)
273
289
  #shellopts.compile(SPEC3)
@@ -277,9 +293,9 @@ shellopts.compile(SPEC)
277
293
  #shellopts.tokens.each(&:dump)
278
294
  #exit
279
295
 
280
- shellopts.usage
296
+ #shellopts.usage
281
297
  #shellopts.brief
282
- #shellopts.help
298
+ shellopts.help
283
299
  #shellopts.help("cmd")
284
300
  #shellopts.help(ARGV.first)
285
301
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shellopts
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claus Rasmussen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-28 00:00:00.000000000 Z
11
+ date: 2022-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: forward_to
@@ -187,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
187
  - !ruby/object:Gem::Version
188
188
  version: '0'
189
189
  requirements: []
190
- rubygems_version: 3.2.26
190
+ rubygems_version: 3.1.4
191
191
  signing_key:
192
192
  specification_version: 4
193
193
  summary: Parse command line options and arguments