options_by_example 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -0
- data/lib/options_by_example/parser.rb +70 -8
- data/lib/options_by_example/version.rb +8 -1
- data/lib/options_by_example.rb +10 -7
- 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: 98c16609314706667d2b776fa71de92ca874b2fcc803ea8f80387fe4f0245d9a
|
4
|
+
data.tar.gz: 8cc7023f98016edaa5521d6184985fb4133135e902f0380f81e233b6f52bfb97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4e54eeb9f5beef88733ac4c70055570eeff930ccacd287f88b80905bd1760b6b1ec3513c5ea144d1c7cdc259af7d4991f81c5be82249cecb2f939d291d29032
|
7
|
+
data.tar.gz: 9126c11cba93c504fd41939dfa24c62578889b8546b9f472091a9463eed4cc6e3f409ff7e40a8219ce9a70913ae63872486745629431290e8f87dec6edf351be
|
data/README.md
CHANGED
@@ -9,6 +9,19 @@ Features
|
|
9
9
|
- Parses those arguments and options from the command line (ARGV)
|
10
10
|
- Raises errors for unknown options or missing required arguments
|
11
11
|
|
12
|
+
Installation
|
13
|
+
|
14
|
+
To use options_by_example, first install the gem by running:
|
15
|
+
|
16
|
+
```
|
17
|
+
gem install options_by_example
|
18
|
+
```
|
19
|
+
|
20
|
+
Alternatively, add this line to your Gemfile and run bundle install:
|
21
|
+
|
22
|
+
```
|
23
|
+
gem 'options_by_example'
|
24
|
+
```
|
12
25
|
|
13
26
|
Example
|
14
27
|
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'date'
|
4
|
+
require 'time'
|
5
|
+
|
3
6
|
|
4
7
|
class OptionsByExample
|
5
8
|
|
@@ -11,19 +14,20 @@ class OptionsByExample
|
|
11
14
|
attr_reader :options
|
12
15
|
attr_reader :arguments
|
13
16
|
|
14
|
-
def initialize(argument_names_required, argument_names_optional, option_names)
|
17
|
+
def initialize(argument_names_required, argument_names_optional, default_values, option_names)
|
15
18
|
@argument_names_required = argument_names_required
|
16
19
|
@argument_names_optional = argument_names_optional
|
20
|
+
@default_values = default_values
|
17
21
|
@option_names = option_names
|
18
22
|
|
19
|
-
@arguments =
|
23
|
+
@arguments = @default_values.dup
|
20
24
|
@options = {}
|
21
25
|
end
|
22
26
|
|
23
27
|
def parse(array)
|
24
28
|
|
25
29
|
# Separate command-line options and their respective arguments into
|
26
|
-
# chunks plus
|
30
|
+
# chunks, plus tracking leading excess arguments. This organization
|
27
31
|
# facilitates further processing and validation of the input.
|
28
32
|
|
29
33
|
@chunks = []
|
@@ -33,8 +37,9 @@ class OptionsByExample
|
|
33
37
|
current << each
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
|
40
|
+
detect_help_option
|
41
|
+
flatten_stacked_shorthand_options
|
42
|
+
detect_unknown_options
|
38
43
|
parse_options
|
39
44
|
|
40
45
|
validate_number_of_arguments
|
@@ -46,7 +51,7 @@ class OptionsByExample
|
|
46
51
|
|
47
52
|
private
|
48
53
|
|
49
|
-
def
|
54
|
+
def detect_help_option
|
50
55
|
@chunks.each do |option, *args|
|
51
56
|
case option
|
52
57
|
when '-h', '--help'
|
@@ -55,7 +60,36 @@ class OptionsByExample
|
|
55
60
|
end
|
56
61
|
end
|
57
62
|
|
58
|
-
def
|
63
|
+
def flatten_stacked_shorthand_options
|
64
|
+
|
65
|
+
# Expand any combined shorthand options like -svt into their
|
66
|
+
# separate components (-s, -v, and -t) and assigns any arguments
|
67
|
+
# to the last component. If an unknown shorthand is found, raise
|
68
|
+
# a helpful error message with suggestion if possible.
|
69
|
+
|
70
|
+
list = []
|
71
|
+
@chunks.each do |option, *args|
|
72
|
+
if option =~ /^-([a-zA-Z]{2,})$/
|
73
|
+
shorthands = $1.each_char.map { |char| "-#{char}" }
|
74
|
+
|
75
|
+
shorthands.each do |each|
|
76
|
+
if not @option_names.include?(each)
|
77
|
+
did_you_mean = ", did you mean '-#{option}'?" if @option_names.include?("-#{option}")
|
78
|
+
raise "Found unknown option #{each} inside '#{option}'#{did_you_mean}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
list.concat shorthands.map { |each| [each] }
|
83
|
+
list.last.concat args
|
84
|
+
else
|
85
|
+
list << [option, *args]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@chunks = list
|
90
|
+
end
|
91
|
+
|
92
|
+
def detect_unknown_options
|
59
93
|
@chunks.each do |option, *args|
|
60
94
|
raise "Found unknown option '#{option}'" unless @option_names.include?(option)
|
61
95
|
end
|
@@ -72,7 +106,28 @@ class OptionsByExample
|
|
72
106
|
|
73
107
|
if argument_name
|
74
108
|
raise "Expected argument for option '#{option}', got none" if args.empty?
|
75
|
-
|
109
|
+
value = args.shift
|
110
|
+
|
111
|
+
begin
|
112
|
+
case argument_name
|
113
|
+
when 'NUM'
|
114
|
+
expected_type = 'an integer value'
|
115
|
+
value = Integer value
|
116
|
+
when 'DATE'
|
117
|
+
expected_type = 'a date (e.g. YYYY-MM-DD)'
|
118
|
+
value = Date.parse value
|
119
|
+
when 'TIME'
|
120
|
+
expected_type = 'a timestamp (e.g. HH:MM:SS)'
|
121
|
+
value = Time.parse value
|
122
|
+
end
|
123
|
+
rescue ArgumentError
|
124
|
+
raise "Invalid argument \"#{value}\" for option '#{option}', please provide #{expected_type}"
|
125
|
+
end
|
126
|
+
|
127
|
+
@arguments[option_name] = value
|
128
|
+
@option_took_argument = option
|
129
|
+
else
|
130
|
+
@option_took_argument = nil
|
76
131
|
end
|
77
132
|
|
78
133
|
@remainder = args
|
@@ -82,10 +137,17 @@ class OptionsByExample
|
|
82
137
|
def validate_number_of_arguments
|
83
138
|
min_length = @argument_names_required.size
|
84
139
|
max_length = @argument_names_optional.size + min_length
|
140
|
+
|
85
141
|
if @remainder.size > max_length
|
86
142
|
range = [min_length, max_length].uniq.join(?-)
|
87
143
|
raise "Expected #{range} arguments, but received too many"
|
88
144
|
end
|
145
|
+
|
146
|
+
if @remainder.size < min_length
|
147
|
+
too_few = @remainder.empty? ? 'none' : (@remainder.size == 1 ? 'only one' : 'too few')
|
148
|
+
remark = " (considering #{@option_took_argument} takes an argument)" if @option_took_argument
|
149
|
+
raise "Expected #{min_length} required arguments, but received #{too_few}#{remark}"
|
150
|
+
end
|
89
151
|
end
|
90
152
|
|
91
153
|
def parse_required_arguments
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class OptionsByExample
|
4
|
-
VERSION = '
|
4
|
+
VERSION = '3.0.0'
|
5
5
|
end
|
6
6
|
|
7
7
|
|
@@ -11,6 +11,13 @@ __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
|
+
3.0.0
|
15
|
+
|
16
|
+
- Support options with default values
|
17
|
+
- Improved support for one-line usage messages
|
18
|
+
- Expand combined shorthand options into their separate components
|
19
|
+
- Shorthand options must be single letter only
|
20
|
+
|
14
21
|
2.0.0
|
15
22
|
|
16
23
|
- Replaced dynamic methods with explicit methods for options and arguments
|
data/lib/options_by_example.rb
CHANGED
@@ -23,10 +23,10 @@ class OptionsByExample
|
|
23
23
|
#
|
24
24
|
# Usage: connect [options] [mode] host port
|
25
25
|
|
26
|
-
text =~ /Usage: (
|
26
|
+
text =~ /Usage: (\$0|\w+)(?: \[options\])?((?: \[\w+\])*)((?: \w+)*)/
|
27
27
|
raise RuntimeError, "Expected usage string, got none" unless $1
|
28
|
-
@argument_names_optional = $
|
29
|
-
@argument_names_required = $
|
28
|
+
@argument_names_optional = $2.to_s.split.map { |match| match.tr('[]', '').downcase }
|
29
|
+
@argument_names_required = $3.to_s.split.map(&:downcase)
|
30
30
|
|
31
31
|
# --- 2) Parse option names ---------------------------------------
|
32
32
|
#
|
@@ -37,12 +37,14 @@ class OptionsByExample
|
|
37
37
|
# -s, --secure Use secure connection
|
38
38
|
# -v, --verbose Enable verbose output
|
39
39
|
# -r, --retries NUM Number of connection retries (default 3)
|
40
|
-
# -t, --timeout NUM
|
40
|
+
# -t, --timeout NUM Set connection timeout in seconds
|
41
41
|
|
42
42
|
@option_names = {}
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
@default_values = {}
|
44
|
+
text.scan(/(?:(-\w), ?)?(--(\w+))(?: (\w+))?(?:.*\(default:? (\w+)\))?/) do
|
45
|
+
flags = [$1, $2].compact
|
46
|
+
flags.each { |each| @option_names[each] = [$3, $4] }
|
47
|
+
@default_values[$3] = $5 if $5
|
46
48
|
end
|
47
49
|
|
48
50
|
initialize_argument_accessors
|
@@ -63,6 +65,7 @@ class OptionsByExample
|
|
63
65
|
parser = Parser.new(
|
64
66
|
@argument_names_required,
|
65
67
|
@argument_names_optional,
|
68
|
+
@default_values,
|
66
69
|
@option_names,
|
67
70
|
)
|
68
71
|
|
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: 3.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: 2023-
|
11
|
+
date: 2023-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|