opt-simple 1.0.0 → 1.1.0

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: 3396db6ba0716fea38377347f6831c74c372324523123b0934c8356796b8b230
4
- data.tar.gz: 60ef677dc68de3ea9459b8b6d428e2a235178187fddb4850405dd9567e3055cc
3
+ metadata.gz: 57cca5d40e69cf2f9a914290d54ca098293e06a122cfecbe0170045de81a3c2a
4
+ data.tar.gz: b069da5060d12936a2d2022053d414c3ecf84708ac62d530aae91d675a72f593
5
5
  SHA512:
6
- metadata.gz: 85f93bd1282c725161c2db2106ff7f3ca19fe1b97f86313b73b0cd000540b8e2f5832b6523aaf087b59ef977921fe7e6aba6a193a852827d8a47635fef77f972
7
- data.tar.gz: 16a47cde44882e07c1ae688db17be9e6df212f34b3227d958510b08f7e79e47bc0346f3e0a19109c75af4aa87af4237eba77204f8e180da35c6660a5b8c8ec6e
6
+ metadata.gz: 61a13e28947dc8ecf33690b7b6013692849fc6075136814fb831facb6cb82bdc0ae0fe90247f525e35b6ff8644cd3cdb98bd6f37d4cdbf8a715ea8e7335bd4b4
7
+ data.tar.gz: 4694df6658044f29a6c0dd2ad616bb2f0cc3ed636db884fe521f601139e4bdb2a0a4ca98a8a50063881d43b30048852a93f991d809169796ccf7152d0c4bca34
data/README.md CHANGED
@@ -34,7 +34,7 @@ See OptSimple for the API specification
34
34
  It is recommended to install OptSimple using RubyGems:
35
35
 
36
36
  ```
37
- $ sudo gem install opt-simple
37
+ $ gem install opt-simple
38
38
  ```
39
39
 
40
40
  ## Examples
@@ -49,7 +49,7 @@ It is recommended to install OptSimple using RubyGems:
49
49
  :range => [5,10]
50
50
  }
51
51
 
52
- opts = OptSimple.new(defaults).parse_opts! do
52
+ opts = OptSimple.new(defaults: defaults).parse_opts! do
53
53
  argument %w[-i --infile], "Infile, multiple allowed", "FILE" do | arg |
54
54
  accumulate_opt arg
55
55
  end
@@ -108,27 +108,27 @@ It is recommended to install OptSimple using RubyGems:
108
108
  puts "Things: #{opts.things.inspect}"
109
109
  ```
110
110
 
111
- ```
112
111
  Which prints out an automatic usage statement:
112
+ ```
113
113
  Usage: opt_ex.rb [options]
114
-
114
+
115
115
  MANDATORY ARGS:
116
- -i, --infile FILE Infile, multiple allowed
117
-
116
+ -i, --infile FILE Infile, multiple allowed
117
+
118
118
  OPTIONS:
119
- -d, --debug debug mode
120
-
121
- -m, --cowbell, --morecowbell I've got a fever, and this is the only prescription.
122
-
123
- -n, -num, --num-values VAL The answer to everything
124
-
125
- -p, --pattern, --glob-pattern PATTERN glob pattern (default is '*')
126
-
127
- --range ARG range: min,max (both >0) (default is '[5, 10]')
119
+ -d, --debug debug mode
128
120
 
129
- -t, --things THINGS Some things - variable number allowed
121
+ -m, --cowbell, --morecowbell I've got a fever, and this is the only prescription.
130
122
 
131
- -h, --help (for this help message)
123
+ -n, -num, --num-values VAL The answer to everything (default is '42')
124
+
125
+ -p, --pattern, --glob-pattern PATTERN glob pattern (default is '*')
126
+
127
+ --range ARG range: min,max (both >0) (default is '5,10')
128
+
129
+ -t, --things THINGS Some things - variable number allowed
130
+
131
+ -h, --help (for this help message)
132
132
  ```
133
133
  ### A very simple example with no error checking. Use at your own risk!
134
134
  ``` ruby
@@ -138,7 +138,7 @@ Which prints out an automatic usage statement:
138
138
  'max' => 40
139
139
  }
140
140
 
141
- options = OptSimple.new(defaults).parse_opts!
141
+ options = OptSimple.new(defaults: defaults).parse_opts!
142
142
 
143
143
  puts "Options"
144
144
  puts options
@@ -165,7 +165,7 @@ Which prints out an automatic usage statement:
165
165
  "max" => 10
166
166
  }
167
167
 
168
- options = OptSimple.new(defaults).parse_opts! do
168
+ options = OptSimple.new(defaults: defaults).parse_opts! do
169
169
  banner "USAGE: #{$0}"
170
170
  summary "Show how to set a banner and summary."
171
171
 
@@ -246,7 +246,7 @@ Which prints out an automatic usage statement:
246
246
  option %w[-p --pattern --glob-pattern], "glob pattern","PATTERN"
247
247
  end
248
248
 
249
- os.register_opts({num: 7}) do
249
+ os.register_opts(defaults: {num: 7}) do
250
250
  option %w[-n -num --num-values],"Number of val","VAL" do |arg|
251
251
  set_opt arg.to_i
252
252
  end
@@ -283,7 +283,7 @@ Which prints out an automatic usage statement:
283
283
 
284
284
  # Note that b/c we define option [--odd] and [--evens] _after_ the max-out flag,
285
285
  # they will override the max-out setting.
286
- opts = OptSimple.new(defaults).parse_opts! do | opt_obj |
286
+ opts = OptSimple.new(defaults: defaults).parse_opts! do | opt_obj |
287
287
  flag %w[-m --max-out], "Maximize evens and odds." do
288
288
  opt_obj.odd_val = allowed_odds.last
289
289
  opt_obj.even_val = allowed_evens.last
@@ -305,6 +305,24 @@ Which prints out an automatic usage statement:
305
305
  puts "Options"
306
306
  puts opts
307
307
  ```
308
+ ## Errors
309
+
310
+ Parsing problems raise a subclass of `OptSimple::Error` (a `StandardError`):
311
+ `MissingArgument`, `InvalidOption` or `ParameterUsageError`, and `error` inside a
312
+ parameter block raises `OptSimple::Error`. The `message` is the reason followed by
313
+ the usage statement; `reason` and `usage` are also available on their own.
314
+
315
+ ```ruby
316
+ begin
317
+ opts = OptSimple.new.parse_opts! { argument '--model', 'Model name', 'MODEL' }
318
+ rescue OptSimple::Error => e
319
+ abort e.message # or: abort "error: #{e.reason}"
320
+ end
321
+ ```
322
+
323
+ A registered switch is never taken as a value, so `--model --num 4` is reported
324
+ as a missing value for `--model` (negative numbers like `--num -1` still work).
325
+
308
326
  ## Questions and/or Comments
309
327
 
310
328
  email [Ethan Stryker](mailto:e.stryker@gmail.com])
data/lib/opt_simple.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  =begin rdoc
2
4
 
3
5
  = OptSimple provides a very simple interface to command line parsing.
@@ -91,12 +93,6 @@ class OptSimple
91
93
  # go through the registered parameters, and pull out
92
94
  # the specified parms from @arg
93
95
 
94
- # first look for a call for help
95
- unless (help_strings & @args).empty?
96
- $stdout.puts self.to_s
97
- exit(0)
98
- end
99
-
100
96
  mandatory_check = mandatory_opts.map {|m| m.switches}
101
97
 
102
98
  positional_args = []
@@ -104,15 +100,28 @@ class OptSimple
104
100
  #remove the '--', but don't include it w/ positional arguments
105
101
  positional_args = @args.slice!(loc..-1)[1..-1]
106
102
  end
103
+
104
+ # look for a call for help, but not in the positional args after '--'
105
+ unless (help_strings & @args).empty?
106
+ $stdout.puts self.to_s
107
+ exit(0)
108
+ end
107
109
 
108
- # Handle the case where a user specifies --foo=bar, or --foo=bar,baz
109
- equal_args = @args.find_all {|arg| arg.include?('=') }
110
- @args.delete_if {|arg| arg.include?('=') }
111
- equal_args.each do | e |
112
- switch,list = e.split('=')
113
- @args << switch
114
- list.split(',').each {|val| @args << val }
110
+ # Handle the case where a user specifies --foo=bar, or --foo=bar,baz.
111
+ # Only switch-looking args are split, and they are expanded in place so
112
+ # values like 'a=b' and the order of positional args are left alone.
113
+ # replace (rather than reassign) keeps parse_opts! destructive on ARGV.
114
+ expanded = @args.flat_map do | arg |
115
+ if (m = arg.match(/\A(-[^=]+)=(.*)\z/m))
116
+ [m[1], *m[2].split(',')]
117
+ else
118
+ arg
119
+ end
115
120
  end
121
+ @args.replace(expanded)
122
+
123
+ # a value that is itself a registered switch means the real value is missing
124
+ all_switches = @parameters.flat_map(&:switches)
116
125
 
117
126
  # now actually parse the args, and call all the stored up blocks from the options
118
127
  @parameters.each do | parm |
@@ -152,11 +161,16 @@ class OptSimple
152
161
  if parm.block.arity > 0 and (pieces.length < parm.block.arity) # or # pieces.any? {|p| p.start_with?('-')})
153
162
  raise OptSimple::ParameterUsageError.new "Not enough args following #{intersection}",self
154
163
  end
164
+
165
+ # but a registered switch is never a value: '--model --num 4' is a missing value
166
+ if (clash = pieces.find {|p| all_switches.include?(p) })
167
+ raise OptSimple::ParameterUsageError.new "Missing value for #{intersection.join(' OR ')} (found switch #{clash})",self
168
+ end
155
169
 
156
170
  begin
157
171
  parm.instance_exec(*pieces,&parm.block)
158
172
  rescue OptSimple::Error => e
159
- raise OptSimple::Error.new e.message,self
173
+ raise e.class.new(e.reason,self)
160
174
  end
161
175
  end
162
176
  @results.merge! parm.param_options
@@ -246,7 +260,7 @@ class OptSimple
246
260
  parm.set_opt @defaults[intersection.first]
247
261
  @results.merge! parm.param_options
248
262
  elsif intersection.length > 1
249
- raise OptSimple::Error "Clashes in the defaults for #{parm.switches}"
263
+ raise OptSimple::Error.new("Clashes in the defaults for #{parm.switches}",self)
250
264
  end
251
265
 
252
266
  add_parameter(parm)
@@ -272,7 +286,7 @@ class OptSimple
272
286
  # returns the automatic usage statement
273
287
  def to_s
274
288
  if @help.empty?
275
- help_str = ""
289
+ help_str = +""
276
290
 
277
291
  help_str << " MANDATORY ARGS:\n" unless mandatory_opts.empty?
278
292
  mandatory_opts.each do | parm |
@@ -293,7 +307,9 @@ class OptSimple
293
307
  elsif @defaults[intersection.first].nil?
294
308
  help_str << " (default is 'nil')\n\n"
295
309
  else
296
- help_str << " (default is '#{@defaults[intersection.first]}')\n\n"
310
+ default = @defaults[intersection.first]
311
+ default = default.join(',') if default.is_a?(Array)
312
+ help_str << " (default is '#{default}')\n\n"
297
313
  end
298
314
  end
299
315
  help_str << " SUMMARY:\n\n #{@summary}\n\n" unless @summary.empty?
@@ -359,24 +375,23 @@ class OptSimple
359
375
  end
360
376
 
361
377
  def switch_len #:nodoc:
362
- @switches.join(', ').length
378
+ switch_str.length
363
379
  end
364
380
 
381
+ # Short switches first, then the rest in the order given. Without a short
382
+ # switch, indent so long switches line up under '-x, --long'.
383
+ # (Build a new string: the switches may be frozen literals.)
365
384
  def switch_str #:nodoc:
366
385
  short_parms = @switches.find_all {|st| st.start_with?('-') and st.length == 2 and st[1] != '-'}
367
- long_parms = @switches.find_all {|st| st.start_with?('--') or (st.start_with?('-') and st.length > 2)}
368
- other_parms = @switches.find_all {|st| not st.start_with?('-')}
386
+ other_parms = @switches - short_parms
369
387
 
370
- sh_str = short_parms.empty? ? " " * 4 : short_parms.first
371
- long_str = long_parms.join(', ') + other_parms.join(', ')
372
- sh_str << ', ' unless sh_str =~/^\s+$/ or long_str.empty?
373
-
374
- sh_str + long_str
388
+ indent = short_parms.empty? ? " " * 4 : ""
389
+ indent + (short_parms + other_parms).join(', ')
375
390
  end
376
391
 
377
392
  # a single line that will be put in the overall usage string
378
393
  def help_str(switch_len)
379
- " %-#{switch_len}s" % self.switch_str + " \t#{@help}"
394
+ " %-#{switch_len}s" % self.switch_str + " #{@help}"
380
395
  end
381
396
 
382
397
  # A shortcut for raising an OptSimple::Error exception
@@ -436,7 +451,6 @@ class OptSimple
436
451
  # leading '-'s removed) will be used as keys in the Result
437
452
  # and the values set to the arg following the switch on the CL.
438
453
  class Option < Parameter
439
- attr_reader = :metavar
440
454
  def initialize(switches,help="",metavar="ARG",&block)
441
455
  super(switches,help,&block)
442
456
  @metavar = metavar
@@ -447,13 +461,8 @@ class OptSimple
447
461
  @first_call = true # use this so we can accumulate non-defaults
448
462
  end
449
463
 
450
- def switch_len #:nodoc:
451
- metavar_space = @metavar.empty? ? 0 : @metavar.length + 1
452
- super + metavar_space
453
- end
454
-
455
464
  def switch_str #:nodoc:
456
- super + " #{@metavar}"
465
+ @metavar.empty? ? super : super + " #{@metavar}"
457
466
  end
458
467
 
459
468
  # append val to the parameter list
@@ -481,10 +490,15 @@ class OptSimple
481
490
  def mandatory?; true; end
482
491
  end
483
492
 
484
- # A general error in the options
485
- class Error < Exception
486
- def initialize(string,option_obj=nil)
487
- super("#{string}\n#{option_obj.to_s}")
493
+ # A general error in the options. The message is the reason followed by the
494
+ # usage statement; both are also available separately.
495
+ class Error < StandardError
496
+ attr_reader :reason, :usage
497
+
498
+ def initialize(string=nil,option_obj=nil)
499
+ @reason = string.to_s
500
+ @usage = option_obj.to_s
501
+ super("#{@reason}\n#{@usage}")
488
502
  set_backtrace []
489
503
  end
490
504
  end
@@ -583,7 +597,7 @@ class OptSimple
583
597
 
584
598
  # a hash looking return string.
585
599
  def to_s
586
- ret = ""
600
+ ret = +""
587
601
  aliases.each do | a |
588
602
  ret << "#{a}=>#{@inside_hash[a]},"
589
603
  end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Regression tests for bugs found in 1.0.0. The magic comment above matters:
4
+ # switch strings below are frozen, as they are in most modern Ruby files.
5
+ require 'stringio'
6
+ require 'opt_simple'
7
+ require 'test/unit'
8
+ require 'test_unit_extensions'
9
+
10
+ class TestRegressions < Test::Unit::TestCase
11
+
12
+ def parse(args, defaults: {}, &block)
13
+ OptSimple.new(defaults: defaults, args: args).parse_opts!(&block)
14
+ end
15
+
16
+ # --- frozen switch strings -------------------------------------------------
17
+
18
+ must "build the usage statement when switches are frozen strings" do
19
+ os = OptSimple.new(args: [])
20
+ os.register_opts { option %w[-n --num], "a number", "N"; flag %w[-v --verbose] }
21
+ assert_nothing_raised { os.to_s }
22
+ assert_match(/-n, --num N/, os.to_s)
23
+ assert_match(/-v, --verbose/, os.to_s)
24
+ end
25
+
26
+ must "report a missing argument, not a FrozenError, when switches are frozen" do
27
+ assert_raise(OptSimple::MissingArgument) { parse([]) { argument %w[-i --infile] } }
28
+ end
29
+
30
+ must "leave the caller's switch strings unchanged after building usage" do
31
+ switches = %w[-n --num]
32
+ os = OptSimple.new(args: [])
33
+ os.register_opts { option switches }
34
+ os.to_s
35
+ assert_equal %w[-n --num], switches
36
+ end
37
+
38
+ must "show every short switch in the usage statement" do
39
+ os = OptSimple.new(args: [])
40
+ os.register_opts { flag %w[-v -V --verbose] }
41
+ assert_match(/-v, -V, --verbose/, os.to_s)
42
+ end
43
+
44
+ # --- '=' handling --------------------------------------------------------------
45
+
46
+ must "not treat a value containing '=' as a switch" do
47
+ o = parse(%w[--model a=b]) { option '--model' }
48
+ assert_equal 'a=b', o['model']
49
+ assert_equal [], o.positional_args
50
+ end
51
+
52
+ must "keep everything after the first '=' in --switch=value" do
53
+ o = parse(%w[--filter=key=val]) { option '--filter' }
54
+ assert_equal 'key=val', o['filter']
55
+ end
56
+
57
+ must "let the last repeated option win whether or not it uses '='" do
58
+ # 1.0.0 moved '=' args to the end, so '--a=1' was applied last
59
+ o = parse(%w[--a=1 --a 2]) { option '--a' }
60
+ assert_equal '2', o['a']
61
+ end
62
+
63
+ # --- switches used as values ---------------------------------------------------
64
+
65
+ must "raise a usage error when a switch is followed by another switch instead of a value" do
66
+ e = assert_raise(OptSimple::ParameterUsageError) do
67
+ parse(%w[--model --num 4]) { option '--model'; option '--num' }
68
+ end
69
+ assert_match(/--model.*--num/, e.reason)
70
+ end
71
+
72
+ # guard: the check above must not reject negative numbers
73
+ must "still allow negative numbers as values" do
74
+ o = parse(%w[--num -1]) { option('--num') { |v| set_opt v.to_i } }
75
+ assert_equal(-1, o['num'])
76
+ end
77
+
78
+ # --- error class ---------------------------------------------------------------
79
+
80
+ must "raise errors a plain rescue can catch" do
81
+ caught = begin
82
+ parse([]) { argument '--model' }
83
+ rescue => e
84
+ e
85
+ end
86
+ assert_kind_of OptSimple::MissingArgument, caught
87
+ assert_kind_of StandardError, caught
88
+ end
89
+
90
+ must "expose the reason and the usage statement separately" do
91
+ e = assert_raise(OptSimple::MissingArgument) { parse([]) { argument '--model' } }
92
+ assert_equal 'Must set the following parameters: --model', e.reason
93
+ assert_match(/\AUsage:/, e.usage)
94
+ assert_equal "#{e.reason}\n#{e.usage}", e.message
95
+ end
96
+
97
+ must "keep the error subclass and add usage when a block calls error" do
98
+ e = assert_raise(OptSimple::Error) do
99
+ parse(%w[--num x]) { option('--num') { |v| error "--num must be a number" } }
100
+ end
101
+ assert_equal '--num must be a number', e.reason
102
+ assert_match(/--num/, e.usage)
103
+ end
104
+
105
+ must "raise OptSimple::Error when two defaults name the same option" do
106
+ assert_raise(OptSimple::Error) do
107
+ parse([], defaults: { 'num-val' => 1, num_val: 2 }) { option '--num-val' }
108
+ end
109
+ end
110
+
111
+ # --- help ----------------------------------------------------------------------
112
+
113
+ must "not show help for --help after '--'" do
114
+ o = parse(%w[-- --help]) { option '--model' }
115
+ assert_equal %w[--help], o.positional_args
116
+ end
117
+
118
+ # guard: moving the help check after the '--' split must not break help
119
+ must "show help and exit for --help before '--'" do
120
+ out = StringIO.new
121
+ begin
122
+ $stdout = out
123
+ assert_raise(SystemExit) { parse(%w[--help --]) { option '--model' } }
124
+ ensure
125
+ $stdout = STDOUT
126
+ end
127
+ assert_match(/--model/, out.string)
128
+ end
129
+
130
+ must "line up the help descriptions in one column" do
131
+ os = OptSimple.new(args: [])
132
+ os.register_opts do
133
+ argument %w[-i --infile], "infile", "FILE"
134
+ option '--a-much-longer-option-name', "long one", "VAL"
135
+ flag '-v', "verbose"
136
+ end
137
+ lines = os.to_s.lines.grep(/infile$|long one$|verbose$/)
138
+ assert_equal 3, lines.size
139
+ columns = lines.map { |l| l.index(/infile$|long one$|verbose$/) }
140
+ assert_equal 1, columns.uniq.size, os.to_s
141
+ end
142
+
143
+ must "show array defaults the way they are typed on the command line" do
144
+ os = OptSimple.new(defaults: { tasks: %w[a b] }, args: [])
145
+ os.register_opts { option('--tasks') { |*t| set_opt t } }
146
+ assert_match(/default is 'a,b'/, os.to_s)
147
+ end
148
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opt-simple
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Stryker
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: " Parameter specification, validity checking and argument transformations
14
13
  \n can be put in one place, default parameters are easily set, and an \n automatic
@@ -25,12 +24,13 @@ files:
25
24
  - lib/test_unit_extensions.rb
26
25
  - test/test_arglist.rb
27
26
  - test/test_help.rb
27
+ - test/test_regressions.rb
28
28
  - test/test_return.rb
29
29
  - test/test_usage.rb
30
- homepage: http://opt-simple.rubyforge.org/
31
- licenses: []
30
+ homepage: https://github.com/estryker/opt-simple
31
+ licenses:
32
+ - MIT
32
33
  metadata: {}
33
- post_install_message:
34
34
  rdoc_options: []
35
35
  require_paths:
36
36
  - lib
@@ -45,8 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  requirements: []
48
- rubygems_version: 3.3.7
49
- signing_key:
48
+ rubygems_version: 4.0.10
50
49
  specification_version: 4
51
50
  summary: A simple and elegant command line option parser.
52
51
  test_files: []