options_by_example 3.3.0 → 4.0.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 +4 -4
- data/README.md +2 -2
- data/lib/options_by_example/commandline_parser.rb +97 -63
- data/lib/options_by_example/usage_specification.rb +10 -3
- data/lib/options_by_example/version.rb +12 -1
- data/lib/options_by_example.rb +12 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27abecec1f4038208d60c4a623de4664241e4b88db67b8e15918ba8d2e8c4ad3
|
|
4
|
+
data.tar.gz: e57931971bc3f57ac7a59fd9233f338530afc60d2a1f507a9a2344e10d9f5ab9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7513e6747f9269e9618e4a4e277560fcbb7741663cc01a3c5767b0a30c52277cd67590c19b21e2eb79d42bc6678ad329d465b1d0b75f2da6079d12cc9978fe52
|
|
7
|
+
data.tar.gz: 1a4bfc340b15a0f63115e9025e4be96f37fa0d5a0808bbe81d44438c97648159154b41dc8380b94fe8aeb1a4ab5d5d7b6448572e6741ecf8a5e8bdf3d75166a3
|
data/README.md
CHANGED
|
@@ -45,7 +45,7 @@ __END__
|
|
|
45
45
|
Establishes a network connection to a designated host and port, enabling
|
|
46
46
|
users to assess network connectivity and diagnose potential problems.
|
|
47
47
|
|
|
48
|
-
Usage: connect [options] [mode]
|
|
48
|
+
Usage: connect [options] host port [mode]
|
|
49
49
|
|
|
50
50
|
Options:
|
|
51
51
|
-s, --secure Establish a secure connection (SSL/TSL)
|
|
@@ -54,8 +54,8 @@ Options:
|
|
|
54
54
|
-t, --timeout NUM Set connection timeout in seconds
|
|
55
55
|
|
|
56
56
|
Arguments:
|
|
57
|
-
[mode] Optional connection mode (active or passive)
|
|
58
57
|
host The target host to connect to (e.g., example.com)
|
|
59
58
|
port The target port to connect to (e.g., 80)
|
|
59
|
+
[mode] Optional connection mode (active or passive)
|
|
60
60
|
```
|
|
61
61
|
|
|
@@ -17,6 +17,7 @@ class OptionsByExample
|
|
|
17
17
|
def initialize(usage)
|
|
18
18
|
@argument_names = usage.argument_names
|
|
19
19
|
@default_values = usage.default_values
|
|
20
|
+
@ends_with_optional_vararg = usage.ends_with_optional_vararg
|
|
20
21
|
@option_names = usage.option_names
|
|
21
22
|
|
|
22
23
|
@argument_values = @default_values.dup
|
|
@@ -36,25 +37,34 @@ class OptionsByExample
|
|
|
36
37
|
current << each
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
exit_if_help_option
|
|
40
41
|
unpack_combined_shorthand_options
|
|
41
42
|
expand_dash_number_to_dash_n_option
|
|
42
43
|
raise_if_unknown_options
|
|
43
44
|
parse_options
|
|
45
|
+
coerce_num_date_time_etc
|
|
44
46
|
|
|
45
47
|
validate_number_of_arguments
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
parse_positional_arguments
|
|
49
|
+
special_case_if_ends_with_optional_vararg
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
# :nocov:
|
|
52
|
+
raise %{unreachable given we check number of arguments} unless @remainder.empty?
|
|
53
|
+
# :nocov:
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
private
|
|
53
57
|
|
|
54
|
-
def
|
|
58
|
+
def exit_if_help_option
|
|
55
59
|
@slices.each do |option, *args|
|
|
56
60
|
case option
|
|
57
61
|
when '-h', '--help'
|
|
62
|
+
if args.first == 'debug!'
|
|
63
|
+
puts "@argument_names = #{@argument_names.inspect}"
|
|
64
|
+
puts "@default_values = #{@default_values.inspect}"
|
|
65
|
+
puts "@ends_with_optional_vararg = #{@ends_with_optional_vararg}"
|
|
66
|
+
puts "@option_names = #{@option_names.inspect}"
|
|
67
|
+
end
|
|
58
68
|
raise PrintUsageMessage
|
|
59
69
|
end
|
|
60
70
|
end
|
|
@@ -114,25 +124,7 @@ class OptionsByExample
|
|
|
114
124
|
|
|
115
125
|
if argument_name
|
|
116
126
|
raise "Expected argument for option '#{option}', got none" if args.empty?
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
begin
|
|
120
|
-
case argument_name
|
|
121
|
-
when 'NUM'
|
|
122
|
-
expected_type = 'an integer value'
|
|
123
|
-
value = Integer value
|
|
124
|
-
when 'DATE'
|
|
125
|
-
expected_type = 'a date (e.g. YYYY-MM-DD)'
|
|
126
|
-
value = Date.parse value
|
|
127
|
-
when 'TIME'
|
|
128
|
-
expected_type = 'a timestamp (e.g. HH:MM:SS)'
|
|
129
|
-
value = Time.parse value
|
|
130
|
-
end
|
|
131
|
-
rescue ArgumentError
|
|
132
|
-
raise "Invalid argument \"#{value}\" for option '#{option}', please provide #{expected_type}"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
@argument_values[option_name] = value
|
|
127
|
+
@argument_values[option_name] = args.shift
|
|
136
128
|
@option_took_argument = option
|
|
137
129
|
else
|
|
138
130
|
@option_took_argument = nil
|
|
@@ -142,57 +134,99 @@ class OptionsByExample
|
|
|
142
134
|
end
|
|
143
135
|
end
|
|
144
136
|
|
|
137
|
+
def coerce_num_date_time_etc
|
|
138
|
+
@option_names.each do |option, (each, argument_name)|
|
|
139
|
+
next unless value = @argument_values[each]
|
|
140
|
+
begin
|
|
141
|
+
case argument_name
|
|
142
|
+
when 'NUM'
|
|
143
|
+
expected_type = 'an integer value'
|
|
144
|
+
@argument_values[each] = Integer value
|
|
145
|
+
when 'DATE'
|
|
146
|
+
expected_type = 'a date (e.g. YYYY-MM-DD)'
|
|
147
|
+
@argument_values[each] = Date.parse value
|
|
148
|
+
when 'TIME'
|
|
149
|
+
expected_type = 'a timestamp (e.g. HH:MM:SS)'
|
|
150
|
+
@argument_values[each] = Time.parse value
|
|
151
|
+
end
|
|
152
|
+
rescue ArgumentError
|
|
153
|
+
raise "Invalid argument \"#{value}\" for option '#{option}', please provide #{expected_type}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
145
158
|
def validate_number_of_arguments
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
count_vararg_arguments = @argument_names.values.count(:vararg)
|
|
159
|
+
# ASSUME: either varargs or optional arguments, never both. That
|
|
160
|
+
# constraint is guaranteed upstream. Here, we just count
|
|
149
161
|
|
|
150
|
-
|
|
151
|
-
|
|
162
|
+
count_required = @argument_names.values.count(:required)
|
|
163
|
+
count_vararg = @argument_names.values.count(:vararg)
|
|
164
|
+
count_optional = @argument_names.values.count(:optional)
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
min_length = count_required + count_vararg
|
|
167
|
+
max_length = count_required + count_optional
|
|
168
|
+
max_length = nil if @ends_with_optional_vararg
|
|
169
|
+
max_length = nil if count_vararg > 0
|
|
157
170
|
|
|
158
|
-
|
|
159
|
-
too_few = @remainder.empty? ? 'none' : (@remainder.size == 1 ? 'only one' : 'too few')
|
|
160
|
-
remark = " (considering #{@option_took_argument} takes an argument)" if @option_took_argument
|
|
161
|
-
raise "Expected #{min_length} required arguments, but received #{too_few}#{remark}"
|
|
162
|
-
end
|
|
163
|
-
end
|
|
171
|
+
unless (min_length..max_length) === @remainder.size
|
|
164
172
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
if max_length.nil?
|
|
174
|
+
msg = "Expected #{min_length} or more arguments,"
|
|
175
|
+
elsif max_length > min_length
|
|
176
|
+
msg = "Expected #{min_length}-#{max_length} arguments,"
|
|
177
|
+
else
|
|
178
|
+
msg = "Expected #{min_length} arguments,"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if @remainder.empty?
|
|
182
|
+
msg += " but received none"
|
|
183
|
+
elsif @remainder.size == 1 && min_length > 1
|
|
184
|
+
msg += " but received only one"
|
|
185
|
+
elsif @remainder.size < min_length
|
|
186
|
+
msg += " but received too few"
|
|
187
|
+
elsif max_length && @remainder.size > max_length
|
|
188
|
+
msg += " but received too many"
|
|
189
|
+
# :nocov:
|
|
190
|
+
else
|
|
191
|
+
raise %{unreachable given the range check above}
|
|
192
|
+
# :nocov:
|
|
179
193
|
end
|
|
180
|
-
return
|
|
181
|
-
end
|
|
182
194
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
if @option_took_argument
|
|
196
|
+
msg += " (considering #{@option_took_argument} takes an argument)"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
raise msg
|
|
187
200
|
end
|
|
188
201
|
end
|
|
189
202
|
|
|
190
|
-
def
|
|
203
|
+
def parse_positional_arguments
|
|
204
|
+
remaining_arguments = @argument_names.length
|
|
191
205
|
@argument_names.each do |argument_name, arity|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
206
|
+
remaining_arguments -= 1
|
|
207
|
+
case arity
|
|
208
|
+
when :required
|
|
209
|
+
@argument_values[argument_name] = @remainder.shift
|
|
210
|
+
when :vararg
|
|
211
|
+
@argument_values[argument_name] = @remainder.shift(@remainder.length - remaining_arguments)
|
|
212
|
+
when :optional
|
|
213
|
+
break if @remainder.empty?
|
|
214
|
+
@argument_values[argument_name] = @remainder.shift
|
|
215
|
+
# :nocov:
|
|
216
|
+
else
|
|
217
|
+
raise %{unreachable given these are all possible values}
|
|
218
|
+
# :nocov:
|
|
219
|
+
end
|
|
195
220
|
end
|
|
196
221
|
end
|
|
222
|
+
|
|
223
|
+
def special_case_if_ends_with_optional_vararg
|
|
224
|
+
return unless @ends_with_optional_vararg
|
|
225
|
+
final_argument_name = @argument_names.keys.last
|
|
226
|
+
@argument_values[final_argument_name] = [
|
|
227
|
+
*@argument_values[final_argument_name],
|
|
228
|
+
*@remainder.shift(@remainder.length),
|
|
229
|
+
]
|
|
230
|
+
end
|
|
197
231
|
end
|
|
198
232
|
end
|
|
@@ -7,6 +7,7 @@ class OptionsByExample
|
|
|
7
7
|
attr_reader :message
|
|
8
8
|
attr_reader :argument_names
|
|
9
9
|
attr_reader :default_values
|
|
10
|
+
attr_reader :ends_with_optional_vararg
|
|
10
11
|
attr_reader :option_names
|
|
11
12
|
|
|
12
13
|
def initialize(text)
|
|
@@ -34,14 +35,20 @@ class OptionsByExample
|
|
|
34
35
|
tokens.shift
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
while /^(\w+)( ?\.\.\.)?$/ === tokens.first
|
|
39
|
+
vararg_if_dotted = $2 ? :vararg : :required
|
|
40
|
+
@argument_names[sanitize $1] = vararg_if_dotted
|
|
41
|
+
tokens.shift
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
while /^\[(\w+)\]$/ === tokens.first
|
|
38
45
|
@argument_names[sanitize $1] = :optional
|
|
39
46
|
tokens.shift
|
|
40
47
|
end
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@
|
|
49
|
+
if /^\[(\w+) ?\.\.\.\]$/ === tokens.first
|
|
50
|
+
@argument_names[sanitize $1] = :optional
|
|
51
|
+
@ends_with_optional_vararg = true
|
|
45
52
|
tokens.shift
|
|
46
53
|
end
|
|
47
54
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class OptionsByExample
|
|
4
|
-
VERSION = '
|
|
4
|
+
VERSION = '4.0.0'
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
|
|
@@ -11,6 +11,17 @@ __END__
|
|
|
11
11
|
# Minor version bump when backward-compatible changes or enhancements
|
|
12
12
|
# Patch version bump when backward-compatible bug fixes, security updates etc
|
|
13
13
|
|
|
14
|
+
4.0.0
|
|
15
|
+
- Remove support for leading optional arguments (breaking change)
|
|
16
|
+
- Add support for trailing optional arguments
|
|
17
|
+
- Add support for optional vararg argument
|
|
18
|
+
|
|
19
|
+
3.4.0
|
|
20
|
+
- Ensure default values are coerced too
|
|
21
|
+
- Print error message to stdout
|
|
22
|
+
- New method #expect_at_most_one_except (experimental)
|
|
23
|
+
- New method #expect_at_most_one_of (experimental)
|
|
24
|
+
|
|
14
25
|
3.3.0
|
|
15
26
|
- Expand dash-number to dash-n option
|
|
16
27
|
- Complete support for inline specification of options
|
data/lib/options_by_example.rb
CHANGED
|
@@ -28,8 +28,18 @@ class OptionsByExample
|
|
|
28
28
|
puts @usage_spec.message
|
|
29
29
|
exit 0
|
|
30
30
|
rescue RuntimeError => err
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
abort "ERR: #{err.message}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def expect_at_most_one_except(*extra_options)
|
|
35
|
+
expect_at_most_one_of *(@options.keys - extra_options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def expect_at_most_one_of(*mutually_exclusive_options)
|
|
39
|
+
provided_options = @options.keys & mutually_exclusive_options
|
|
40
|
+
if provided_options.length > 1
|
|
41
|
+
abort "ERR: Found more than one mutually-exclusive option {#{provided_options.join ', '}}"
|
|
42
|
+
end
|
|
33
43
|
end
|
|
34
44
|
|
|
35
45
|
def fetch(*args, &block)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: options_by_example
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrian Kuhn
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email:
|