consoler 1.0.3 → 1.3.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.
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consoler
4
-
5
4
  # Argument/Options matcher
6
5
  #
7
6
  # Given a list of arguments and a list option try to match them
8
7
  class Matcher
9
-
10
8
  # Create a matcher
11
9
  #
12
10
  # @param [Consoler::Arguments] arguments List of arguments
@@ -27,14 +25,14 @@ module Consoler
27
25
  parse_options = true
28
26
 
29
27
  _loop_args do |arg|
30
- unless parse_options then
28
+ unless parse_options
31
29
  @argument_values.push arg
32
30
  next
33
31
  end
34
32
 
35
33
  # when "argument" is --, then stop parsing the rest of the arguments
36
34
  # and treat the rest as regular arguments
37
- if arg == '--' then
35
+ if arg == '--'
38
36
  parse_options = false
39
37
  next
40
38
  end
@@ -49,12 +47,20 @@ module Consoler
49
47
  remaining = _match_arguments
50
48
  _fill_defaults
51
49
 
52
- if @matched_options.size == @options.size then
50
+ if @matched_options.size == @options.size
53
51
  @matched_options['remaining'] = remaining
52
+
53
+ # make sure all aliases are also filled
54
+ @options.each do |option|
55
+ option.aliases.each do |alias_|
56
+ @matched_options[alias_.name] = @matched_options[option.name]
57
+ end
58
+ end
59
+
54
60
  return @matched_options
55
61
  end
56
62
 
57
- return nil
63
+ nil
58
64
  end
59
65
 
60
66
  private
@@ -68,59 +74,58 @@ module Consoler
68
74
  is_short = false
69
75
  name = nil
70
76
 
71
- if arg[0..1] == '--' then
77
+ if arg[0..1] == '--'
72
78
  is_long = true
73
79
  name = arg[2..-1]
74
- elsif arg[0] == '-' then
80
+ elsif arg[0] == '-'
75
81
  is_short = true
76
82
  name = arg[1..-1]
77
83
  end
78
84
 
79
85
  # arg is not a long/short option, add to arguments values
80
- unless is_long or is_short then
86
+ unless is_long || is_short
81
87
  @argument_values.push arg
82
88
  return true
83
89
  end
84
90
 
85
- unless name.nil? then
91
+ unless name.nil?
86
92
  # get the name of the option, short options use the first character
87
- option_name = if is_short then
93
+ option_name = if is_short
88
94
  name[0]
89
95
  else
90
96
  name
91
97
  end
92
98
 
93
- option = @options.get option_name
99
+ option, matched = @options.get_with_alias option_name
94
100
 
95
101
  # no option by this name in options
96
102
  return nil if option.nil?
97
103
 
98
- needs_short = option.is_short
99
- needs_long = option.is_long
100
-
101
104
  # see if the type if right, short or long
102
- if needs_long and not is_long then
105
+ if matched.is_long && !is_long
103
106
  return nil
104
- elsif needs_short and not is_short then
107
+ elsif matched.is_short && !is_short
105
108
  return nil
106
109
  end
107
110
 
108
- if is_long then
109
- if option.is_value then
111
+ if is_long
112
+ if option.is_value
110
113
  # is_value needs a next argument for its value
111
114
  return nil if _peek_next.nil?
112
- @matched_options[name] = _peek_next
115
+
116
+ @matched_options[option.name] = _peek_next
113
117
  _skip_next
114
118
  else
115
- @matched_options[name] = true
119
+ option_value! option
116
120
  end
117
121
  end
118
122
 
119
- if is_short then
120
- if name.size == 1 and option.is_value then
123
+ if is_short
124
+ if name.size == 1 && option.is_value
121
125
  # is_value needs a next argument for its value
122
126
  return nil if _peek_next.nil?
123
- @matched_options[name] = _peek_next
127
+
128
+ @matched_options[option.name] = _peek_next
124
129
  _skip_next
125
130
  else
126
131
  # for every character (short option) increment the option value
@@ -128,17 +133,30 @@ module Consoler
128
133
  short_option = @options.get n
129
134
  return nil if short_option.nil?
130
135
 
131
- if @matched_options[n].nil? then
132
- @matched_options[n] = 0
133
- end
134
-
135
- @matched_options[n] += 1
136
+ option_value! short_option
136
137
  end
137
138
  end
138
139
  end
139
140
  end
140
141
 
141
- return true
142
+ true
143
+ end
144
+
145
+ # Set the value of an option
146
+ #
147
+ # Long or short option needed
148
+ #
149
+ # @param [Consoler::Option]
150
+ def option_value!(option)
151
+ if option.is_short
152
+ if @matched_options[option.name].nil?
153
+ @matched_options[option.name] = 0
154
+ end
155
+
156
+ @matched_options[option.name] += 1
157
+ else
158
+ @matched_options[option.name] = true
159
+ end
142
160
  end
143
161
 
144
162
  # Loop through the arguments
@@ -151,7 +169,7 @@ module Consoler
151
169
 
152
170
  # use an incrementing index, to be able to peek to the next in the list
153
171
  # and to skip an item
154
- while @index < size do
172
+ while @index < size
155
173
  yield @arguments.args[@index]
156
174
 
157
175
  _skip_next
@@ -183,11 +201,13 @@ module Consoler
183
201
 
184
202
  # Match arguments to defined option arguments
185
203
  #
186
- # @return [Array<String>] The remaining args
204
+ # @return [Array<String>, nil] The remaining args,
205
+ # or <tt>nil</tt> if there are not enough arguments
187
206
  def _match_arguments
188
207
  @optionals_before = {}
189
208
  @optionals_before_has_remaining = false
190
209
 
210
+ total_argument_values = @argument_values.size
191
211
  argument_values_index = 0
192
212
 
193
213
  _match_arguments_optionals_before
@@ -197,7 +217,9 @@ module Consoler
197
217
  # arguments supplied (info available from optionals map)
198
218
  optionals.each do |_, optional|
199
219
  optional.each do |before|
200
- if before[:included] then
220
+ if before[:included]
221
+ return nil if argument_values_index >= total_argument_values
222
+
201
223
  @matched_options[before[:name]] = @argument_values[argument_values_index]
202
224
  argument_values_index += 1
203
225
  end
@@ -205,7 +227,9 @@ module Consoler
205
227
  end
206
228
 
207
229
  # only fill mandatory argument if its not the :REMAINING key
208
- if mandatory_arg_name != :REMAINING then
230
+ if mandatory_arg_name != :REMAINING
231
+ return nil if argument_values_index >= total_argument_values
232
+
209
233
  @matched_options[mandatory_arg_name] = @argument_values[argument_values_index]
210
234
  argument_values_index += 1
211
235
  end
@@ -214,7 +238,7 @@ module Consoler
214
238
  remaining = []
215
239
 
216
240
  # left over arguments
217
- while argument_values_index < @argument_values.size do
241
+ while argument_values_index < @argument_values.size
218
242
  remaining.push @argument_values[argument_values_index]
219
243
  argument_values_index += 1
220
244
  end
@@ -229,18 +253,18 @@ module Consoler
229
253
  @optionals_before = {}
230
254
  tracker = {}
231
255
 
232
- @options.each do |option, key|
256
+ @options.each do |option, _key|
233
257
  next unless option.is_argument
234
258
 
235
- if option.is_optional then
259
+ if option.is_optional
236
260
  # setup tracker for optional group
237
261
  tracker[option.is_optional] = [] if tracker[option.is_optional].nil?
238
262
 
239
263
  # mark all optionals as not-included
240
- tracker[option.is_optional].push({
264
+ tracker[option.is_optional].push(
241
265
  included: false,
242
266
  name: option.name,
243
- })
267
+ )
244
268
  else
245
269
  @optionals_before[option.name] = tracker
246
270
  tracker = {}
@@ -248,7 +272,7 @@ module Consoler
248
272
  end
249
273
 
250
274
  # make sure all optionals are accounted for in the map
251
- if tracker != {} then
275
+ if tracker != {}
252
276
  # use a special key so we can handle it differently in the filling process
253
277
  @optionals_before[:REMAINING] = tracker
254
278
  @optionals_before_has_remaining = true
@@ -267,7 +291,7 @@ module Consoler
267
291
  mandatories_matched = @optionals_before.size
268
292
 
269
293
  # there are optionals at the end of the options, don't match the void
270
- if @optionals_before_has_remaining then
294
+ if @optionals_before_has_remaining
271
295
  mandatories_matched -= 1
272
296
  end
273
297
 
@@ -276,11 +300,11 @@ module Consoler
276
300
  # loop through optional map
277
301
  _each_optional_before_sorted do |before|
278
302
  # are there enough arguments left to fill this optional group
279
- if (total + before.size + mandatories_matched) <= @argument_values.size then
303
+ if (total + before.size + mandatories_matched) <= @argument_values.size
280
304
  total += before.size
281
305
 
282
306
  before.each do |val|
283
- val[:included] = true;
307
+ val[:included] = true
284
308
  end
285
309
  end
286
310
  end
@@ -293,10 +317,10 @@ module Consoler
293
317
  # @return [Consoler::Matcher]
294
318
  def _fill_defaults
295
319
  @options.each do |option|
296
- if option.is_optional then
297
- unless @matched_options.has_key? option.name then
298
- @matched_options[option.name] = option.default_value
299
- end
320
+ next unless option.is_optional
321
+
322
+ unless @matched_options.key? option.name
323
+ @matched_options[option.name] = option.default_value
300
324
  end
301
325
  end
302
326
 
@@ -312,10 +336,10 @@ module Consoler
312
336
  @optionals_before.each do |_, optionals|
313
337
  tmp = []
314
338
  optionals.each do |optional_index, before|
315
- tmp.push({
339
+ tmp.push(
316
340
  count: before.size,
317
341
  index: optional_index,
318
- })
342
+ )
319
343
  end
320
344
 
321
345
  tmp.sort! { |a, b| b[:count] - a[:count] }.each do |item|
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consoler
4
-
5
4
  # Represents an option
6
5
  #
7
6
  # @attr_reader [String] name Name of the options
@@ -10,6 +9,7 @@ module Consoler
10
9
  # @attr_reader [Boolean] is_argument Is the option an argument
11
10
  # @attr_reader [Boolean] is_value Does the option need a value (<tt>--option=</tt>)
12
11
  # @attr_reader [Integer] is_optional Is the option optional (> 0) (<tt>[option]</tt>)
12
+ # @attr_reader [Array] aliases List of aliases of option (<tt>-v|--verbose</tt>)
13
13
  class Option
14
14
  attr_reader :name
15
15
  attr_reader :is_long
@@ -17,6 +17,7 @@ module Consoler
17
17
  attr_reader :is_argument
18
18
  attr_reader :is_value
19
19
  attr_reader :is_optional
20
+ attr_reader :aliases
20
21
 
21
22
  # Create a option
22
23
  #
@@ -28,13 +29,13 @@ module Consoler
28
29
  option = Option.new option_def, tracker
29
30
 
30
31
  # split short options with more than 1 char in multiple options
31
- if option.is_short and option.name.size > 1 then
32
+ if option.is_short && option.name.size > 1
32
33
  # remember state
33
34
  old_tracking = tracker.is_tracking
34
35
  old_is_value = option.is_value
35
36
 
36
37
  # if the complete option is optional, fake the tracker
37
- if option.is_optional then
38
+ if option.is_optional
38
39
  tracker.is_tracking = true
39
40
  end
40
41
 
@@ -44,7 +45,7 @@ module Consoler
44
45
  new_name = "-#{name}"
45
46
 
46
47
  # if the short option should have a value, this only counts for the last option
47
- if old_is_value and i == names.count - 1 then
48
+ if old_is_value && i == names.count - 1
48
49
  new_name = "#{new_name}="
49
50
  end
50
51
 
@@ -67,14 +68,20 @@ module Consoler
67
68
  def to_definition
68
69
  definition = name
69
70
 
70
- if is_long then
71
+ if is_long
71
72
  definition = "--#{definition}"
72
- elsif is_short then
73
+ elsif is_short
73
74
  definition = "-#{definition}"
74
75
  end
75
76
 
76
- if is_value then
77
+ if is_value
77
78
  definition = "#{definition}="
79
+ elsif is_argument
80
+ definition = "<#{definition}>"
81
+ end
82
+
83
+ aliases.each do |alias_|
84
+ definition = "#{definition}|#{alias_.to_definition}"
78
85
  end
79
86
 
80
87
  definition
@@ -88,7 +95,7 @@ module Consoler
88
95
  return 0 if is_short
89
96
  return false if is_long
90
97
 
91
- return nil
98
+ nil
92
99
  end
93
100
 
94
101
  protected
@@ -103,19 +110,33 @@ module Consoler
103
110
  # Check for multiple attributes in the option definition till we got the
104
111
  # final name and all of its attributes
105
112
 
106
- option, @is_optional = _is_optional option_def, tracker
113
+ # make sure we don't wrongly process any alias
114
+ alias_defs = option_def.split '|'
115
+ option = alias_defs.shift || ''
116
+
117
+ option, @is_optional = _is_optional option, tracker
107
118
  option, @is_long = _is_long option
108
119
  option, @is_short = _is_short option
109
- @is_argument = (not @is_long and not @is_short)
120
+ @is_argument = (!@is_long && !@is_short)
110
121
  option, @is_value = _value option, @is_argument
122
+ option, @aliases = _aliases option, alias_defs, tracker
123
+
124
+ if option[0] == '<'
125
+ raise 'Invalid <, missing >' if option[-1] != '>'
126
+ raise 'Only arguments support <, > around name' unless @is_argument
127
+
128
+ option = option[1..-2]
129
+ end
130
+
131
+ raise 'Missing starting <' if option[-1] == '>'
111
132
 
112
133
  @name = option
113
134
 
114
- if @name.empty? then
135
+ if @name.empty?
115
136
  raise 'Option must have a name'
116
137
  end
117
138
 
118
- if @is_long and @is_short
139
+ if @is_long && @is_short
119
140
  raise 'Option can not be a long and a short option'
120
141
  end
121
142
  end
@@ -135,8 +156,8 @@ module Consoler
135
156
  # @raise [RuntimeError] if you try to close an unopened optional
136
157
  # @return [(String, Integer|nil)] Remaining option definition, and, optional group if available
137
158
  def _is_optional(option, tracker)
138
- if option[0] == '[' then
139
- if !tracker.is_tracking then
159
+ if option[0] == '['
160
+ if !tracker.is_tracking
140
161
  # mark tracker as tracking
141
162
  tracker.is_tracking = true
142
163
  tracker.index += 1
@@ -147,14 +168,12 @@ module Consoler
147
168
  end
148
169
 
149
170
  # get optional group index from tracking, if tracking
150
- optional = if tracker.is_tracking then
171
+ optional = if tracker.is_tracking
151
172
  tracker.index
152
- else
153
- nil
154
173
  end
155
174
 
156
- if option[-1] == ']' then
157
- if tracker.is_tracking then
175
+ if option[-1] == ']'
176
+ if tracker.is_tracking
158
177
  # mark tracker as non-tracking
159
178
  tracker.is_tracking = false
160
179
  option = option[0..-2]
@@ -163,7 +182,7 @@ module Consoler
163
182
  end
164
183
  end
165
184
 
166
- return option, optional
185
+ [option, optional]
167
186
  end
168
187
 
169
188
  # Check long definition
@@ -171,14 +190,14 @@ module Consoler
171
190
  # @param [String] option Option definition
172
191
  # @return [(String, Boolean)]
173
192
  def _is_long(option)
174
- if option[0..1] == '--' then
193
+ if option[0..1] == '--'
175
194
  long = true
176
195
  option = option[2..-1]
177
196
  else
178
197
  long = false
179
198
  end
180
199
 
181
- return option, long
200
+ [option, long]
182
201
  end
183
202
 
184
203
  # Check short definition
@@ -186,14 +205,14 @@ module Consoler
186
205
  # @param [String] option Option definition
187
206
  # @return [(String, Boolean)]
188
207
  def _is_short(option)
189
- if option[0] == '-' then
208
+ if option[0] == '-'
190
209
  short = true
191
210
  option = option[1..-1]
192
211
  else
193
212
  short = false
194
213
  end
195
214
 
196
- return option, short
215
+ [option, short]
197
216
  end
198
217
 
199
218
  # Check value definition
@@ -202,8 +221,8 @@ module Consoler
202
221
  # @raise [RuntimeError] if you try to assign a value to an argument
203
222
  # @return [(String, Boolean)]
204
223
  def _value(option, argument)
205
- if option[-1] == '=' then
206
- if argument then
224
+ if option[-1] == '='
225
+ if argument
207
226
  raise 'Arguments can\'t have a value'
208
227
  end
209
228
 
@@ -213,7 +232,36 @@ module Consoler
213
232
  value = false
214
233
  end
215
234
 
216
- return option, value
235
+ [option, value]
236
+ end
237
+
238
+ # Parse all possible aliases
239
+ #
240
+ # @param [String] option Option definition
241
+ # @param [Consoler::Tracker] tracker Optional tracker
242
+ # @raise [RuntimeError] On all kinds of occasions
243
+ # @return [(String, Array)] Remaining option definition, and, aliases if available
244
+ def _aliases(option, alias_defs, tracker)
245
+ return option, [] if alias_defs.empty?
246
+
247
+ raise 'Argument can\'t have aliases' if is_argument
248
+ raise 'Aliases are not allowed for multiple short options' if is_short && option.size > 1
249
+
250
+ aliases_ = []
251
+ alias_names = []
252
+
253
+ while (alias_def = alias_defs.shift)
254
+ Consoler::Option.create alias_def, tracker do |alias_|
255
+ raise "Duplicate alias name: #{alias_.name}" if alias_names.include? alias_.name
256
+ raise "Alias must have a value: #{alias_.name}" if is_value && !alias_.is_value
257
+ raise "Alias can't have a value: #{alias_.name}" if !is_value && alias_.is_value
258
+
259
+ aliases_.push alias_
260
+ alias_names.push alias_.name
261
+ end
262
+ end
263
+
264
+ [option, aliases_]
217
265
  end
218
266
  end
219
267
  end
@@ -3,7 +3,6 @@
3
3
  require_relative 'option'
4
4
 
5
5
  module Consoler
6
-
7
6
  # List of options
8
7
  #
9
8
  # @attr_reader [String] description Description of the options
@@ -21,7 +20,7 @@ module Consoler
21
20
  return if options_def.nil?
22
21
 
23
22
  # strip the description
24
- if match = /(^|\s+)-- (?<description>.*)$/.match(options_def) then
23
+ if (match = /(^|\s+)-- (?<description>.*)$/.match(options_def))
25
24
  @description = match[:description]
26
25
  options_def = options_def[0...-match[0].size]
27
26
  end
@@ -31,28 +30,50 @@ module Consoler
31
30
 
32
31
  option_names = []
33
32
 
34
- while option_def = options.shift do
33
+ while (option_def = options.shift)
35
34
  Consoler::Option.create option_def, tracker do |option|
36
35
  raise "Duplicate option name: #{option.name}" if option_names.include? option.name
37
36
 
38
- @options.push option
39
37
  option_names.push option.name
38
+
39
+ option.aliases.each do |alias_|
40
+ raise "Duplicate alias name: #{alias_.name}" if option_names.include? alias_.name
41
+
42
+ option_names.push alias_.name
43
+ end
44
+
45
+ @options.push option
40
46
  end
41
47
  end
42
48
  end
43
49
 
44
- # Get a options by its name
50
+ # Get a option by its name
51
+ #
52
+ # May be matched by one of its aliases
45
53
  #
46
54
  # @param name [String] Name of the option
47
55
  # @return [Consoler::Option, nil]
48
56
  def get(name)
57
+ option, = get_with_alias name
58
+ option
59
+ end
60
+
61
+ # Get a option by its name, with matched alias
62
+ #
63
+ # @param name [String] Name of the option
64
+ # @return [[(Consoler::Option, nil), (Consoler::Option, nil)]]
65
+ def get_with_alias(name)
49
66
  each do |option, _|
50
- if option.name == name then
51
- return option
67
+ if option.name == name
68
+ return option, option
69
+ end
70
+
71
+ option.aliases.each do |alias_|
72
+ return option, alias_ if alias_.name == name
52
73
  end
53
74
  end
54
75
 
55
- return nil
76
+ [nil, nil]
56
77
  end
57
78
 
58
79
  # Loop through all options
@@ -84,19 +105,17 @@ module Consoler
84
105
  each do |option, i|
85
106
  definition += ' '
86
107
 
87
- if optional.nil? and option.is_optional then
108
+ if optional.nil? && option.is_optional
88
109
  definition += '['
89
110
  optional = option.is_optional
90
111
  end
91
112
 
92
113
  definition += option.to_definition
93
114
 
94
- if option.is_optional then
95
- # only close when the next option is not optional, or another optional group
96
- if @options[i + 1].nil? or optional != @options[i + 1].is_optional then
97
- definition += ']'
98
- optional = nil
99
- end
115
+ # only close when the next option is not optional, or another optional group
116
+ if option.is_optional && (@options[i + 1].nil? || optional != @options[i + 1].is_optional)
117
+ definition += ']'
118
+ optional = nil
100
119
  end
101
120
  end
102
121
 
@@ -104,8 +123,6 @@ module Consoler
104
123
  end
105
124
  end
106
125
 
107
- private
108
-
109
126
  # Optionals tracker
110
127
  #
111
128
  # @attr [Boolean] is_tracking Is inside optional options
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consoler
4
-
5
4
  # Current version number
6
- VERSION = '1.0.3'
5
+ VERSION = '1.3.0'.freeze
7
6
  end