rake-commander 0.4.0 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a73808173b38457d53944c3dc6d62f739746133a6d42f0b306a5d4bb40b3d23
4
- data.tar.gz: 9f2d2754d5464e0d34fe3db2f2fcddba471652936d8e8ea73106cb33bd044e02
3
+ metadata.gz: fced681b17be5244fd8cf23294e246e060d7d52daba92a11f84e396d3f0c2ec4
4
+ data.tar.gz: b7937192c15dab0e9acf907ee4d7dacc505de9724967b96064881a30be2c5f6e
5
5
  SHA512:
6
- metadata.gz: caf48388e42c830e15d761a97354291c9354b94652de4e7b0f059f10303d57467970769497980cae1f2aa7eaf7559e4c5f404e9e98e34cc094ee67b256627cf1
7
- data.tar.gz: 41e1c1afd1cb0613a9fc2c10348e7b0bb2c54c1abbbfb8d839c60edd10c827400a8777adf74f84fe54694489103a5b89f580e2b4d0d8a586b1a8ec132bc4ce72
6
+ metadata.gz: 2c36cf00c0dc4afa116e548e7e643b39a7f64f38f64990c9b8f7c11b2dca340979de9f0b6a9d53c532fa041898182dcc4a1c634ba2eedf874515c4257091a999
7
+ data.tar.gz: cecf9b603645401cf14e38d4745be6d6619f505adf3cbf1f2f482f4901d220e45539b434bcf75d99d61e6bb460c22594d7eb52e3da15312ebcd85fd4c46d94fd
data/CHANGELOG.md CHANGED
@@ -31,14 +31,20 @@ All notable changes to this project will be documented in this file.
31
31
  - Example: `on_options(:t, :s, present: true) {|options| do-stuff}` <- block to be called only when the option `:t` and `:s` are both present in the parsed `options` result.
32
32
  - Once this has been done, think about it being a hash-alike object with methods for the option names (i.e. `options.debug?`)
33
33
 
34
- ## [0.4.1] - 2023-08-xx
34
+ ## [0.4.1] - 2025-02-xx
35
35
 
36
36
  ### Added
37
37
 
38
+ - Predefined values cohertion (i.e. `Enum`)
39
+
38
40
  ### Fixed
39
41
 
42
+ - Description: break lines should be respected.
43
+
40
44
  ### Changed
41
45
 
46
+ - Allow multiple `desc` declarations to be aligned with `OptsParser` behaviour.
47
+
42
48
  ## [0.4.0] - 2023-08-01
43
49
 
44
50
  ### Changed
@@ -8,11 +8,13 @@ class RakeCommander::Custom::BasicExample < RakeCommander
8
8
  option '-w', :show_time, TrueClass, desc: 'Displays the local time'
9
9
  option :z, '--timezone', TrueClass, default: false, required: true
10
10
  option :o, '--hello NAME', String, desc: 'It greets.'
11
- option '-s', '--say [SOMETHING]', "It says 'something'", default: %q(I don't know what to "say"...)
11
+ option '-s', '--say [SOMETHING]', "It says 'something'", 'But it comes with default',
12
+ default: %q(I don't know what to "say"...)
12
13
  option :d, '--folder NAME', default: '.', desc: 'Source local folder', required: true
13
14
  option '-e', :'--enviro ENV', 'The target environment to run this task', required: true
14
15
  option :v, :debug, TrueClass, 'Shows the parsed options'
15
16
  option :V, '[no-]verbose', 'Verbosity', TrueClass
17
+ option :l, '--colour COLOUR', "Choose a colour", type: %i[red yellow green blue]
16
18
  #option :f, :folder, required: false, reopen: true
17
19
 
18
20
  def task(*_args)
@@ -10,8 +10,9 @@ class RakeCommander::Custom::ChainerPlus < RakeCommander::Custom::Chainer
10
10
  # Update option description
11
11
  option_reopen :chain, desc: "Calls: '< rake|raked > #{TARGET_TASK} task'"
12
12
  # Extend with new options
13
- option :e, '--exit-on-error', TrueClass, \
14
- desc: "Whether #{TARGET_TASK} should just exit on 'missing argument' error (or raise an exception)"
13
+ option \
14
+ :e, '--exit-on-error', TrueClass,
15
+ desc: "Whether #{TARGET_TASK} should just exit on 'missing argument' error (or raise an exception)"
15
16
  option :o, '--hello NAME', String, desc: 'It greets.'
16
17
 
17
18
  # Make it default to `exit 1` when there are errors
@@ -50,17 +50,20 @@ class RakeCommander
50
50
  # @return [Boolean] determines if a given namespace is entitled for autoloading
51
51
  def autoload_class?(constant)
52
52
  constants = constant.to_s.split("::").compact
53
- autoload = true
53
+ autoload = true
54
+
54
55
  unless autoloaded_namespaces(:include).empty?
55
56
  autoload = autoloaded_namespaces(:include).any? do |ns|
56
57
  ns.to_s.split("::").compact.zip(constants).all? {|(r, c)| r == c}
57
58
  end
58
59
  end
60
+
59
61
  unless autoloaded_namespaces(:ignore).empty?
60
62
  autoload &&= autoloaded_namespaces(:ignore).none? do |ns|
61
63
  ns.to_s.split("::").compact.zip(constants).all? {|(r, c)| r == c}
62
64
  end
63
65
  end
66
+
64
67
  autoload
65
68
  end
66
69
 
@@ -90,8 +93,10 @@ class RakeCommander
90
93
  # Children classes of `autoloader_class` that have not been created an instance of.
91
94
  def unloaded_children
92
95
  return [] unless autoloaded_class
96
+
93
97
  new_detected = new_classes
94
98
  known_class!(*new_detected)
99
+
95
100
  descendants(parent_class: autoloaded_class, scope: new_detected).select do |child_class|
96
101
  !autoloaded_children.include?(child_class) && \
97
102
  !excluded_children.include?(child_class) && \
@@ -105,9 +110,12 @@ class RakeCommander
105
110
  # @return [Boolean] `true` if there were children loaded, `false` otherwise.
106
111
  def autoload_children(object = nil)
107
112
  return false if !autoloaded_class || @loading_children
113
+
108
114
  pending_children = unloaded_children
109
115
  return false if pending_children.empty?
116
+
110
117
  @loading_children = true
118
+
111
119
  pending_children.each do |klass|
112
120
  exclude = false
113
121
  child = object ? klass.new(object) : klass.new
@@ -119,6 +127,7 @@ class RakeCommander
119
127
  ensure
120
128
  autoloaded_children.push(klass) unless exclude
121
129
  end
130
+
122
131
  @loading_children = false
123
132
  true
124
133
  end
@@ -11,6 +11,7 @@ class RakeCommander
11
11
  # just return @message
12
12
  def to_s
13
13
  return @message if @message
14
+
14
15
  unclassed(super)
15
16
  end
16
17
 
@@ -18,6 +19,7 @@ class RakeCommander
18
19
  # just return @message
19
20
  def message
20
21
  return @message if @message
22
+
21
23
  to_message(unclassed(super))
22
24
  end
23
25
 
@@ -30,9 +32,7 @@ class RakeCommander
30
32
  case value
31
33
  when StandardError
32
34
  to_message(value.message)
33
- when String
34
- value
35
- when NilClass
35
+ when String, NilClass
36
36
  value
37
37
  else
38
38
  raise ArgumentError, "Expecting String, StandardError or NilClass. Given: #{value.class}"
@@ -10,7 +10,8 @@ class RakeCommander
10
10
  module Base
11
11
  class << self
12
12
  def included(base)
13
- super(base)
13
+ super
14
+
14
15
  base.extend RakeCommander::Base::ClassAutoLoader
15
16
  base.autoloads_children_of RakeCommander
16
17
 
@@ -4,6 +4,7 @@ class RakeCommander
4
4
  extend RakeCommander::Base::ClassHelpers
5
5
  extend RakeCommander::Options::Name
6
6
  include RakeCommander::Options::Description
7
+ include RakeCommander::Options::Type
7
8
 
8
9
  attr_reader :name_full, :desc, :default
9
10
 
@@ -13,12 +14,14 @@ class RakeCommander
13
14
 
14
15
  @name_full = name.freeze
15
16
  super(short.freeze, @name_full)
17
+
16
18
  @default = kargs[:default] if kargs.key?(:default)
17
19
  @desc = kargs[:desc] if kargs.key?(:desc)
18
20
  @required = kargs[:required] if kargs.key?(:required)
19
21
  @type_coercion = kargs[:type] if kargs.key?(:type)
20
22
  @other_args = args
21
23
  @original_block = block
24
+
22
25
  configure_other
23
26
  end
24
27
 
@@ -33,7 +36,9 @@ class RakeCommander
33
36
  # Creates a new option, result of merging this `opt` with this option,
34
37
  # @return [RakeCommander::Option] where opt has been merged
35
38
  def merge(opt, **kargs)
36
- raise "Expecting RakeCommander::Option. Given: #{opt.class}" unless opt.is_a?(RakeCommander::Option)
39
+ msg = "Expecting RakeCommander::Option. Given: #{opt.class}"
40
+ raise msg unless opt.is_a?(RakeCommander::Option)
41
+
37
42
  dup(**opt.dup_key_arguments.merge(kargs), &opt.original_block)
38
43
  end
39
44
 
@@ -81,7 +86,8 @@ class RakeCommander
81
86
 
82
87
  # @param [String, Nil] the argument, may it exist
83
88
  def argument
84
- return nil unless argument?
89
+ return unless argument?
90
+
85
91
  self.class.name_argument(name_full)
86
92
  end
87
93
 
@@ -93,10 +99,23 @@ class RakeCommander
93
99
  # @return [Class, NilClass]
94
100
  def type_coercion
95
101
  value = @type_coercion || (default? && default.class)
96
- return nil unless value.is_a?(Class)
102
+ return unless allowed_type?(value)
103
+
97
104
  value
98
105
  end
99
106
 
107
+ # @return [Boolean] whether the option is an enum with fixed values.
108
+ def enum?
109
+ type_coercion.is_a?(Array)
110
+ end
111
+
112
+ # @return [Array, NilClass] the valid options when is `enum?`
113
+ def enum_options
114
+ return unless enum?
115
+
116
+ type_coercion
117
+ end
118
+
100
119
  # @return [Boolean]
101
120
  def default?
102
121
  instance_variable_defined?(:@default)
@@ -107,9 +126,12 @@ class RakeCommander
107
126
  # @param opt_parser [OptionParser] the option parser to add this option's switch.
108
127
  # @param implicit_short [Boolean] whether the implicit short of this option is active in the opts_parser.
109
128
  def add_switch(opts_parser, where: :base, implicit_short: false, &middleware)
110
- raise "Expecting OptionParser. Given: #{opts_parser.class}" unless opts_parser.is_a?(OptionParser)
129
+ msg = "Expecting OptionParser. Given: #{opts_parser.class}"
130
+ raise msg unless opts_parser.is_a?(OptionParser)
131
+
111
132
  args = switch_args(implicit_short: implicit_short)
112
133
  block = option_block(&middleware)
134
+
113
135
  case where
114
136
  when :head, :top
115
137
  opts_parser.on_head(*args, &block)
@@ -127,13 +149,14 @@ class RakeCommander
127
149
 
128
150
  # @return [Hash] keyed arguments to create a new object
129
151
  def dup_key_arguments
130
- configure_other
131
152
  {}.tap do |kargs|
153
+ configure_other
154
+
132
155
  kargs.merge!(short: short.dup.freeze) if short
133
156
  kargs.merge!(name: name_full.dup.freeze) if name_full
134
157
  kargs.merge!(desc: @desc.dup) if @desc
135
158
  kargs.merge!(default: @default.dup) if default?
136
- kargs.merge!(type: @type_coercion) if @type_coercion.is_a?(Class)
159
+ kargs.merge!(type: @type_coercion) if allowed_type?(@type_coercion)
137
160
  kargs.merge!(required: required?)
138
161
  end
139
162
  end
@@ -141,6 +164,7 @@ class RakeCommander
141
164
  # @return [Array<Variant>]
142
165
  def switch_args(implicit_short: false)
143
166
  configure_other
167
+
144
168
  args = [short_hyphen, name_hyphen]
145
169
  args.push(*switch_desc(implicit_short: implicit_short))
146
170
  args << type_coercion if type_coercion
@@ -152,10 +176,13 @@ class RakeCommander
152
176
  # Called on parse runtime
153
177
  def option_block(&middleware)
154
178
  block_extra_args = [default, short, name, self]
179
+
155
180
  proc do |value|
156
181
  value = !value if type_coercion == FalseClass
157
182
  args = block_extra_args.dup.unshift(value)
183
+
158
184
  original_block&.call(*args)
185
+
159
186
  middleware&.call(*args)
160
187
  end
161
188
  end
@@ -165,8 +192,9 @@ class RakeCommander
165
192
  # @return [Array<String>]
166
193
  def switch_desc(implicit_short: false, line_width: DESC_MAX_LENGTH)
167
194
  ishort = implicit_short ? "( -#{short_implicit} ) " : ''
168
- str = "#{required_desc}#{ishort}#{desc}#{default_desc}"
195
+ str = "#{required_desc}#{ishort}#{desc}#{enum_desc}#{default_desc}"
169
196
  return [] if str.empty?
197
+
170
198
  string_to_lines(str, max: line_width)
171
199
  end
172
200
 
@@ -175,12 +203,15 @@ class RakeCommander
175
203
  end
176
204
 
177
205
  def default_desc
178
- return nil unless default?
179
- str = "{ Default: '#{default}' }"
180
- if desc && !desc.downcase.include?('default')
181
- str = desc.end_with?('.') ? " #{str}" : ". #{str}"
182
- end
183
- str
206
+ return unless default?
207
+
208
+ " { Default: '#{default}' }"
209
+ end
210
+
211
+ def enum_desc
212
+ return unless enum?
213
+
214
+ " Options: [ #{enum_options.join(' | ')} ]."
184
215
  end
185
216
 
186
217
  # Helper to simplify `short` and `name` capture from arguments and keyed arguments.
@@ -191,8 +222,11 @@ class RakeCommander
191
222
  name ||= capture_arguments_name!(args, sample_n_short: sample && short)
192
223
 
193
224
  unless sample
194
- raise ArgumentError, "A short of one letter should be provided. Given: #{short}" unless self.class.valid_short?(short)
195
- raise ArgumentError, "A name should be provided. Given: #{name}" unless self.class.valid_name?(name)
225
+ msg = "A short of one letter should be provided. Given: #{short}"
226
+ raise ArgumentError, msg unless self.class.valid_short?(short)
227
+
228
+ msg = "A name should be provided. Given: #{name}"
229
+ raise ArgumentError, msg unless self.class.valid_name?(name)
196
230
  end
197
231
 
198
232
  [short, name]
@@ -213,41 +247,40 @@ class RakeCommander
213
247
  # @note if found it removes it from args.
214
248
  # @return [String, Symbol, NilClass]
215
249
  def capture_arguments_name!(args, sample_n_short: false)
216
- name = nil
250
+ name = nil
217
251
  name ||= self.class.capture_arguments_name!(args, symbol: true)
218
252
  name ||= self.class.capture_arguments_name!(args, symbol: true, strict: false)
219
253
  name ||= self.class.capture_arguments_name!(args)
220
- name || self.class.capture_arguments_name!(args, strict: false) unless sample_n_short
254
+ name ||= self.class.capture_arguments_name!(args, strict: false) unless sample_n_short
255
+ name
221
256
  end
222
257
 
223
258
  # The remaining `args` received in the initialization
224
259
  def other_args(*args)
225
260
  @other_args ||= []
226
- if args.empty?
227
- @other_args
228
- else
229
- @other_args.push(*args)
230
- end
261
+ return @other_args if args.empty?
262
+
263
+ @other_args.push(*args)
231
264
  end
232
265
 
233
266
  # It consumes `other_args`, to prevent direct overrides to be overriden by it.
267
+ # @note at the end we will pass the switch arguments to OptsParser.
234
268
  def configure_other
235
- @type_coercion ||= fetch_type_from_other
236
- @desc ||= fetch_desc_from_other
269
+ @type_coercion = fetch_type_from_other(@type_coercion)
270
+ @desc = fetch_desc_from_other(@desc)
237
271
  nil
238
272
  end
239
273
 
240
- def fetch_type_from_other
241
- return nil unless type = other_args.find {|arg| arg.is_a?(Class)}
242
- type.tap { other_args.delete(type) }
274
+ def fetch_type_from_other(original = nil)
275
+ other_type = fetch_type!(other_args)
276
+
277
+ return original if original
278
+
279
+ other_type
243
280
  end
244
281
 
245
- def fetch_desc_from_other
246
- return nil unless value = other_args.find {|arg| arg.is_a?(String)}
247
- other_args.dup.each do |val|
248
- other_args.delete(val) if val.is_a?(String)
249
- end
250
- value
282
+ def fetch_desc_from_other(original = nil)
283
+ joined_lines(original, fetch_desc!(other_args))
251
284
  end
252
285
  end
253
286
  end
@@ -3,12 +3,12 @@ class RakeCommander
3
3
  # Offers helpers to treat `ARGV`
4
4
  module Arguments
5
5
  RAKE_COMMAND_EXTENDED_OPTIONS_START = '--'.freeze
6
- NAME_ARGUMENT = /^--(?<option>[\w_-]*).*?$/.freeze
7
- BOOLEAN_ARGUMENT = /(?:^|--)no-(?<option>[\w_-]*).*?$/.freeze
6
+ NAME_ARGUMENT = /^--(?<option>[\w_-]*).*?$/
7
+ BOOLEAN_ARGUMENT = /(?:^|--)no-(?<option>[\w_-]*).*?$/
8
8
 
9
9
  class << self
10
10
  def included(base)
11
- super(base)
11
+ super
12
12
  base.extend ClassMethods
13
13
  end
14
14
  end
@@ -20,6 +20,7 @@ class RakeCommander
20
20
  # @return [Boolean] whether enhanced parsing should be switched on or off.
21
21
  def argv_with_enhanced_syntax?(argv = ARGV)
22
22
  return false unless argv.is_a?(Array)
23
+
23
24
  argv.include?(RAKE_COMMAND_EXTENDED_OPTIONS_START)
24
25
  end
25
26
 
@@ -41,6 +42,7 @@ class RakeCommander
41
42
  def argv_cropping_for_rake(value = :not_used)
42
43
  @argv_cropping_for_rake = true if @argv_cropping_for_rake.nil?
43
44
  return @argv_cropping_for_rake if value == :not_used
45
+
44
46
  @argv_cropping_for_rake = !!value
45
47
  end
46
48
 
@@ -51,8 +53,8 @@ class RakeCommander
51
53
  # @param argv [Array<String>] the command line arguments array.
52
54
  # @return [Array<String>] the target arguments to be parsed by `RakeCommander::Options`
53
55
  def argv_extended_options(argv = ARGV.dup)
54
- if idx = argv.index(RAKE_COMMAND_EXTENDED_OPTIONS_START)
55
- argv[idx+1..-1]
56
+ if (idx = argv.index(RAKE_COMMAND_EXTENDED_OPTIONS_START))
57
+ argv[idx + 1..]
56
58
  else
57
59
  []
58
60
  end
@@ -63,9 +65,11 @@ class RakeCommander
63
65
  # @return [Array<String>] the argv without the extended options of this gem.
64
66
  def argv_rake_native_arguments(argv = ARGV.dup)
65
67
  return argv unless argv_cropping_for_rake
66
- if idx = argv.index(RAKE_COMMAND_EXTENDED_OPTIONS_START)
68
+
69
+ if (idx = argv.index(RAKE_COMMAND_EXTENDED_OPTIONS_START))
67
70
  argv = argv[0..idx]
68
71
  end
72
+
69
73
  argv
70
74
  end
71
75
 
@@ -77,10 +81,10 @@ class RakeCommander
77
81
  # - So some tidy up is necessary and the head of the command (i.e. `rake some:task --`)
78
82
  # should be excluded from arguments to input to the options parser.
79
83
  # @see `RakeCommander::Options#parse_options`
80
- def parse_options(argv = ARGV, *args, **kargs, &block)
84
+ def parse_options(argv = ARGV, *_args, **_kargs)
81
85
  argv = argv_extended_options(argv)
82
86
  argv = argv_pre_parsed(argv, options: options_hash(with_implicit: true))
83
- super(argv, *args, **kargs, &block)
87
+ super
84
88
  end
85
89
 
86
90
  # Options with arguments should not take another option as value.
@@ -95,22 +99,31 @@ class RakeCommander
95
99
  # 2. To overcome this limitation, you may enclose in double quotes and argument with
96
100
  # that start (i,e, `"--argument"`).
97
101
  # @example
98
- # 1. `-abc ARGUMENT` where only `c` receives the argument becomes `-ab -c ARGUMENT`
99
- # 3. `-abc ARGUMENT` where `b` and `c` are argument receivers becomes `-a -b nil -c ARGUMENT`
100
- # 2. `-acb ARGUMENT` where only `c` receives the argument becomes `-a -c nil -b ARGUMENT`
101
- # 4. `-c --some-option ARGUMENT` where both options receive argument, becomes `-c nil --some-option ARGUMENT`
102
- # 5. `-c --some-option -d ARGUMENT` where both options receive argument, becomes `-c nil --some-option nil -d ARGUMENT`
103
- # 6. `-cd ARGUMENT` where `c` default is `"yeah"`, becomes `-c yeah -d ARGUMENT`
102
+ # 1. `-abc ARGUMENT` where only `c` receives the argument
103
+ # becomes `-ab -c ARGUMENT`
104
+ # 2. `-abc ARGUMENT` where `b` and `c` are argument receivers
105
+ # becomes `-a -b nil -c ARGUMENT`
106
+ # 3. `-acb ARGUMENT` where only `c` receives the argument
107
+ # becomes `-a -c nil -b ARGUMENT`
108
+ # 4. `-c --some-option ARGUMENT` where both options receive argument,
109
+ # becomes `-c nil --some-option ARGUMENT`
110
+ # 5. `-c --some-option -d ARGUMENT` where both options receive argument,
111
+ # becomes `-c nil --some-option nil -d ARGUMENT`
112
+ # 6. `-cd ARGUMENT` where `c` default is `"yeah"`,
113
+ # becomes `-c yeah -d ARGUMENT`
104
114
  # @param argv [Array<String>]
105
115
  # @param options [Hash] the defined `RakeCommander::Option` to re-arrange `argv` with.
106
116
  # @return [Array<String>] the re-arranged `argv`
107
117
  def argv_pre_parsed(argv = ARGV, options:)
108
- pre_parsed = explicit_argument_options(argv, options)
118
+ pre_parsed = explicit_argument_options(argv, options)
109
119
  compact_short = ''
120
+
110
121
  pre_parsed.each_with_object([]) do |(opt_ref, args), out|
111
122
  next out.push(*args) unless opt_ref.is_a?(Symbol)
123
+
112
124
  is_short = opt_ref.to_s.length == 1
113
125
  next compact_short << opt_ref.to_s if is_short && args.empty?
126
+
114
127
  out.push("-#{compact_short}") unless compact_short.empty?
115
128
  compact_short = ''
116
129
  opt_str = is_short ? "-#{opt_ref}" : name_hyphen(opt_ref)
@@ -139,17 +152,24 @@ class RakeCommander
139
152
  private
140
153
 
141
154
  # @example the output is actually a Hash, keyed by the Symbol of the option (short or name)
142
- # 1. `-abc ARGUMENT` where only `c` receives the argument becomes `:a :b :c ARGUMENT`
143
- # 3. `-abc ARGUMENT` where `b` and `c` are argument receivers becomes `:a :b nil :c ARGUMENT`
144
- # 2. `-acb ARGUMENT` where only `c` receives the argument becomes `:a :c nil :b ARGUMENT`
145
- # 4. `-c --some-option ARGUMENT` where both options receive argument, becomes `:c nil :some_option ARGUMENT`
146
- # 5. `-c --some-option -d ARGUMENT` where first two options receive argument, becomes `:c nil :some_option nil :d ARGUMENT`
147
- # 6. `-cd ARGUMENT` where `c` default is `"yeah"`, becomes `:c yeah :d ARGUMENT`
155
+ # 1. `-abc ARGUMENT` where only `c` receives the argument
156
+ # becomes `:a :b :c ARGUMENT`
157
+ # 2. `-abc ARGUMENT` where `b` and `c` are argument receivers
158
+ # becomes `:a :b nil :c ARGUMENT`
159
+ # 3. `-acb ARGUMENT` where only `c` receives the argument
160
+ # becomes `:a :c nil :b ARGUMENT`
161
+ # 4. `-c --some-option ARGUMENT` where both options receive argument,
162
+ # becomes `:c nil :some_option ARGUMENT`
163
+ # 5. `-c --some-option -d ARGUMENT` where first two options receive argument,
164
+ # becomes `:c nil :some_option nil :d ARGUMENT`
165
+ # 6. `-cd ARGUMENT` where `c` default is `"yeah"`,
166
+ # becomes `:c yeah :d ARGUMENT`
148
167
  # @return [Hash<Symbol, Array>]
149
168
  def explicit_argument_options(argv, options)
150
169
  decoupled = decluster_shorts_n_names_to_sym(argv)
151
170
  grouped = group_symbols_with_strings(decoupled)
152
171
  normalized = insert_missing_argument_to_groups(grouped, options)
172
+
153
173
  normalized.each_with_object({}) do |group, pre_parsed|
154
174
  opt_ref = group.first.is_a?(Symbol)? group.shift : nil
155
175
  pre_parsed[opt_ref] = group
@@ -163,14 +183,17 @@ class RakeCommander
163
183
  # @param groups [@see #pair_symbols_with_strings]
164
184
  def insert_missing_argument_to_groups(groups, options)
165
185
  groups.each do |group|
166
- args = group.dup
186
+ args = group.dup
167
187
  opt_ref = args.shift
188
+
168
189
  next unless args.empty?
169
190
  next unless opt_ref.is_a?(Symbol)
170
- next unless opt = _retrieve_option_ref(opt_ref, options)
191
+ next unless (opt = _retrieve_option_ref(opt_ref, options))
171
192
  next unless opt.argument?
193
+
172
194
  next group.push(opt.default) if opt.default?
173
- next unless opt.argument_required?
195
+ next unless opt.argument_required?
196
+
174
197
  group.push(nil)
175
198
  end
176
199
  end
@@ -180,11 +203,13 @@ class RakeCommander
180
203
  # @return [RakeCommander::Option, NilClass]
181
204
  def _retrieve_option_ref(opt_ref, options)
182
205
  opt = options[opt_ref]
183
- return opt if opt
184
- return nil unless match = opt_ref.to_s.match(BOOLEAN_ARGUMENT)
185
- return nil unless opt_ref = match[:option]
186
- return nil unless opt = options[opt_ref.to_sym]
187
- return nil unless opt.boolean_name?
206
+
207
+ return opt if opt
208
+ return unless (match = opt_ref.to_s.match(BOOLEAN_ARGUMENT))
209
+ return unless (opt_ref = match[:option])
210
+ return unless (opt = options[opt_ref.to_sym])
211
+ return unless opt.boolean_name?
212
+
188
213
  opt
189
214
  end
190
215
 
@@ -193,6 +218,7 @@ class RakeCommander
193
218
  def group_symbols_with_strings(argv)
194
219
  [].tap do |out|
195
220
  curr_ary = nil
221
+
196
222
  argv.each do |arg|
197
223
  if arg.is_a?(Symbol)
198
224
  out << (curr_ary = [arg])
@@ -5,12 +5,39 @@ class RakeCommander
5
5
 
6
6
  private
7
7
 
8
+ def fetch_desc!(args)
9
+ descs = args.dup.select do |arg|
10
+ arg.is_a?(String).tap do |is_string|
11
+ next unless is_string
12
+
13
+ args.delete(arg)
14
+ end
15
+ end.uniq
16
+
17
+ joined_lines(*descs)
18
+ end
19
+
8
20
  def string_to_lines(str, max: DESC_MAX_LENGTH)
9
21
  str.scan(liner_regex(max)).map(&:strip)
10
22
  end
11
23
 
12
24
  def liner_regex(len = DESC_MAX_LENGTH)
13
- /.{0,#{len}}[^ ](?:\s|$)/mi
25
+ /[^\n\r]{0,#{len}}[^ ](?:\s|$)/mi
26
+ end
27
+
28
+ def joined_lines(*lines, join: "\n")
29
+ lines = lines.compact.map(&:strip).reject(&:empty?)
30
+ return unless lines.count.positive?
31
+
32
+ first = lines.first
33
+ first = "#{first}." unless first.end_with?('.')
34
+
35
+ (lines[1..] || []).reduce(first) do |mem, line|
36
+ mem = "#{mem}." unless mem.end_with?('.')
37
+ line = "#{line}." unless line.end_with?('.')
38
+
39
+ "#{mem}#{join}#{line}"
40
+ end
14
41
  end
15
42
  end
16
43
  end
@@ -4,7 +4,7 @@ class RakeCommander
4
4
  # Base error class that does a rely between OptionParser and RakeCommander errors
5
5
  class Base < RakeCommander::Base::CustomError
6
6
  extend RakeCommander::Options::Name
7
- OPTION_REGEX = /(?:argument|option): (?<option>.+)/i.freeze
7
+ OPTION_REGEX = /(?:argument|option): (?<option>.+)/i
8
8
 
9
9
  class << self
10
10
  # Helper to check if `error` is this class or any children class
@@ -20,14 +20,17 @@ class RakeCommander
20
20
  def option_regex(value = :not_used)
21
21
  @option_regex ||= OPTION_REGEX
22
22
  return @option_regex if value == :not_used
23
+
23
24
  @option_regex = value
24
25
  end
25
26
 
26
27
  # Identifies the option `Symbol` (short or name) for a given message
27
28
  def option_sym(message)
28
- return nil unless match = message.match(option_regex)
29
+ return unless (match = message.match(option_regex))
30
+
29
31
  option = match[:option]
30
32
  return name_word_sym(option) if option.length > 1
33
+
31
34
  short_sym(option)
32
35
  end
33
36
  end
@@ -59,6 +62,7 @@ class RakeCommander
59
62
 
60
63
  def from_desc
61
64
  return '' unless from
65
+
62
66
  if from.respond_to?(:name)
63
67
  "(#{from.name}) "
64
68
  elsif from.respond_to?(:to_s)