options_by_example 3.4.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 +74 -44
- data/lib/options_by_example/usage_specification.rb +10 -3
- data/lib/options_by_example/version.rb +6 -1
- 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,7 +37,7 @@ 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
|
|
@@ -44,18 +45,26 @@ class OptionsByExample
|
|
|
44
45
|
coerce_num_date_time_etc
|
|
45
46
|
|
|
46
47
|
validate_number_of_arguments
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
parse_positional_arguments
|
|
49
|
+
special_case_if_ends_with_optional_vararg
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
# :nocov:
|
|
52
|
+
raise %{unreachable given we check number of arguments} unless @remainder.empty?
|
|
53
|
+
# :nocov:
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
private
|
|
54
57
|
|
|
55
|
-
def
|
|
58
|
+
def exit_if_help_option
|
|
56
59
|
@slices.each do |option, *args|
|
|
57
60
|
case option
|
|
58
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
|
|
59
68
|
raise PrintUsageMessage
|
|
60
69
|
end
|
|
61
70
|
end
|
|
@@ -147,56 +156,77 @@ class OptionsByExample
|
|
|
147
156
|
end
|
|
148
157
|
|
|
149
158
|
def validate_number_of_arguments
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
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
|
|
153
161
|
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
count_required = @argument_names.values.count(:required)
|
|
163
|
+
count_vararg = @argument_names.values.count(:vararg)
|
|
164
|
+
count_optional = @argument_names.values.count(:optional)
|
|
156
165
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
170
|
|
|
162
|
-
|
|
163
|
-
too_few = @remainder.empty? ? 'none' : (@remainder.size == 1 ? 'only one' : 'too few')
|
|
164
|
-
remark = " (considering #{@option_took_argument} takes an argument)" if @option_took_argument
|
|
165
|
-
raise "Expected #{min_length} required arguments, but received #{too_few}#{remark}"
|
|
166
|
-
end
|
|
167
|
-
end
|
|
171
|
+
unless (min_length..max_length) === @remainder.size
|
|
168
172
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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:
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if @option_took_argument
|
|
196
|
+
msg += " (considering #{@option_took_argument} takes an argument)"
|
|
183
197
|
end
|
|
184
|
-
return
|
|
185
|
-
end
|
|
186
198
|
|
|
187
|
-
|
|
188
|
-
break if arity == :optional
|
|
189
|
-
raise "unreachable" if @remainder.empty?
|
|
190
|
-
@argument_values[argument_name] = @remainder.pop
|
|
199
|
+
raise msg
|
|
191
200
|
end
|
|
192
201
|
end
|
|
193
202
|
|
|
194
|
-
def
|
|
203
|
+
def parse_positional_arguments
|
|
204
|
+
remaining_arguments = @argument_names.length
|
|
195
205
|
@argument_names.each do |argument_name, arity|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
199
220
|
end
|
|
200
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
|
|
201
231
|
end
|
|
202
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,11 @@ __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
|
+
|
|
14
19
|
3.4.0
|
|
15
20
|
- Ensure default values are coerced too
|
|
16
21
|
- Print error message to stdout
|
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:
|