consoler 1.0.3 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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