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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb87e6b0c01a7f99d70d600c6adc142d96cae3fc18b122c2259c926504fb198b
4
- data.tar.gz: e21e7dd4f0a4a77fb7e2a84b4edc77293023730388e0fcbf4abe2d0e5344d5c5
3
+ metadata.gz: 98c16609314706667d2b776fa71de92ca874b2fcc803ea8f80387fe4f0245d9a
4
+ data.tar.gz: 8cc7023f98016edaa5521d6184985fb4133135e902f0380f81e233b6f52bfb97
5
5
  SHA512:
6
- metadata.gz: 7e5b59dc4a4b9ad41edeaadaf98fa3ee23abe4f262cfbb9ba93752dba66ff5868b704024a3d534b43386977caefe6a4eecea4a5e80c22f5c351f679ec2488bc3
7
- data.tar.gz: 7ecce96f859a83e3dc702740c3d5e72c957d44a42387dff4a70db249bc54fc1df45484bf5ff099e9bbd66d9dae06a548d179f107aa5f6cb87395a36a54551bc0
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 handling any remaining arguments. This organization
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
- find_help_option
37
- find_unknown_options
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 find_help_option
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 find_unknown_options
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
- @arguments[option_name] = args.shift
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 = '2.0.0'
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
@@ -23,10 +23,10 @@ class OptionsByExample
23
23
  #
24
24
  # Usage: connect [options] [mode] host port
25
25
 
26
- text =~ /Usage: (\w+|\$0)( \[options\])?(( \[\w+\])*)(( \w+)*)/
26
+ text =~ /Usage: (\$0|\w+)(?: \[options\])?((?: \[\w+\])*)((?: \w+)*)/
27
27
  raise RuntimeError, "Expected usage string, got none" unless $1
28
- @argument_names_optional = $3.to_s.split.map { |match| match.tr('[]', '').downcase }
29
- @argument_names_required = $5.to_s.split.map(&:downcase)
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 Connection timeout in seconds (default 10)
40
+ # -t, --timeout NUM Set connection timeout in seconds
41
41
 
42
42
  @option_names = {}
43
- text.scan(/((--?\w+)(, --?\w+)*) ?(\w+)?/) do
44
- opts = $1.split(", ")
45
- opts.each { |each| @option_names[each] = [opts.last.tr('-', ''), $4] }
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: 2.0.0
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-04-30 00:00:00.000000000 Z
11
+ date: 2023-05-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: