rake-commander 0.1.2 → 0.2.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +14 -8
  4. data/CHANGELOG.md +84 -4
  5. data/Gemfile +1 -1
  6. data/LICENSE +21 -0
  7. data/README.md +95 -3
  8. data/Rakefile +11 -13
  9. data/examples/01_basic_example.rb +28 -0
  10. data/examples/02_a_chainer_example.rb +66 -0
  11. data/examples/02_a_chainer_options_set.rb +8 -0
  12. data/examples/02_b_chained_example.rb +13 -0
  13. data/examples/03_a_chainer_plus_example.rb +34 -0
  14. data/examples/03_b_chained_plus_example.rb +17 -0
  15. data/examples/Examples.rake +7 -0
  16. data/examples/README.md +79 -0
  17. data/examples/libs/shell_helpers.rb +81 -0
  18. data/lib/rake-commander/base/class_auto_loader.rb +45 -7
  19. data/lib/rake-commander/base/class_helpers.rb +16 -61
  20. data/lib/rake-commander/base/class_inheritable.rb +122 -0
  21. data/lib/rake-commander/base/custom_error.rb +52 -0
  22. data/lib/rake-commander/base/object_helpers.rb +42 -0
  23. data/lib/rake-commander/base.rb +16 -2
  24. data/lib/rake-commander/option.rb +115 -25
  25. data/lib/rake-commander/options/arguments.rb +206 -94
  26. data/lib/rake-commander/options/description.rb +17 -0
  27. data/lib/rake-commander/options/error/base.rb +86 -0
  28. data/lib/rake-commander/options/error/handling.rb +106 -0
  29. data/lib/rake-commander/options/error/invalid_argument.rb +21 -0
  30. data/lib/rake-commander/options/error/invalid_option.rb +9 -0
  31. data/lib/rake-commander/options/error/missing_argument.rb +10 -0
  32. data/lib/rake-commander/options/error/missing_option.rb +48 -0
  33. data/lib/rake-commander/options/error/unknown_argument.rb +32 -0
  34. data/lib/rake-commander/options/error.rb +75 -10
  35. data/lib/rake-commander/options/name.rb +67 -23
  36. data/lib/rake-commander/options/result.rb +107 -0
  37. data/lib/rake-commander/options/set.rb +7 -1
  38. data/lib/rake-commander/options.rb +175 -102
  39. data/lib/rake-commander/patcher/README.md +79 -0
  40. data/lib/rake-commander/patcher/application/run_method.rb +46 -0
  41. data/lib/rake-commander/patcher/application/top_level_method.rb +74 -0
  42. data/lib/rake-commander/patcher/application.rb +16 -0
  43. data/lib/rake-commander/patcher/base.rb +45 -0
  44. data/lib/rake-commander/patcher/debug.rb +32 -0
  45. data/lib/rake-commander/patcher/helpers.rb +44 -0
  46. data/lib/rake-commander/patcher.rb +26 -0
  47. data/lib/rake-commander/rake_context/wrapper.rb +2 -0
  48. data/lib/rake-commander/rake_task.rb +50 -50
  49. data/lib/rake-commander/version.rb +1 -1
  50. data/lib/rake-commander.rb +4 -0
  51. data/rake-commander.gemspec +5 -2
  52. metadata +75 -7
  53. data/examples/basic.rb +0 -30
  54. data/lib/rake-commander/options/error_rely.rb +0 -58
@@ -0,0 +1,106 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Error
4
+ module Handling
5
+ class << self
6
+ def included(base)
7
+ super(base)
8
+ base.extend RakeCommander::Base::ClassHelpers
9
+ base.extend RakeCommander::Base::ClassInheritable
10
+ base.extend ClassMethods
11
+ base.attr_inheritable :error_on_options, :error_on_options_handler
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ attr_reader :options_latest_error
17
+
18
+ # Whether it should trigger an error when there are `ARGV` option errors during `parse_options`
19
+ # @note
20
+ # 1. It triggers error by default when there are parsing option errors.
21
+ # 2. Even if a `handler` block is defined, if action is `false` it won't trigger error.
22
+ # 3. When specific errors are NOT specified, they will **fallback** to the action defined
23
+ # on the parent class `RakeCommander::Options::Error::Base`. This means that you can define
24
+ # a default behaviour for this.
25
+ # @raise [RakeCommander::Options::Error::Base] the specific option error that was raised.
26
+ # 1. when `action` is `true` (default)
27
+ # 2. when the `handler` is defined and returns `true`.
28
+ # @yield [error, argv, results, leftovers] do some stuff and decide if an error should be raised.
29
+ # @yieldparam error [RakeCommander::Options::Error::Base] the specific error.
30
+ # @yieldparam argv [Array<String>] arguments that were being parsed.
31
+ # @yieldparam results [Hash] the parsed options.
32
+ # @yieldparam leftovers [Array<String>] arguments of `argv` that the parser could not identify.
33
+ # @yieldreturn [Boolean] whether this should trigger an error or not.
34
+ # @param action [Boolean, Symbol] possible values are:
35
+ # 1. `:not_used` -> it will retrieve the currect action value
36
+ # 2. `true` (default) -> it switches `on` the exception triggering
37
+ # 3. `false` -> it will **print** the error and **exit** with status `1`
38
+ # 4. `:continue` -> it will continue with whatver it got (**use this at your own risk**)
39
+ # @param error [RakeCommander::Options::Error::Base:Class] or children thereof.
40
+ # @return [Boolean] whether this error is enabled.
41
+ def error_on_options(action = :not_used, error: RakeCommander::Options::Error::Base, &handler)
42
+ RakeCommander::Options::Error::Base.require_argument!(error, :error, accept_children: true)
43
+ @options_latest_error = nil
44
+ @error_on_options ||= {}
45
+ @error_on_options[error] = action if action != :not_used
46
+
47
+ if block_given?
48
+ error_on_options_handler(error, &handler)
49
+ @error_on_options[error] ||= true
50
+ end
51
+
52
+ return self unless block_given? || action != :not_used
53
+ # default value
54
+ @error_on_options[error] = true unless @error_on_options[error] == false
55
+ @error_on_options[error]
56
+ end
57
+
58
+ # @see #error_on_options
59
+ def error_on_leftovers(action = :not_used, &handler)
60
+ error_on_options(action, error: RakeCommander::Options::Error::UnknownArgument, &handler)
61
+ end
62
+
63
+ # @return [Boolean] whether there is an error `action` defined for `error`
64
+ def error_on_options?(error = RakeCommander::Options::Error::Base)
65
+ RakeCommander::Options::Error::Base.require_argument!(error, :error, accept_children: true)
66
+ _default_action = error_on_options
67
+ @error_on_options.key?(error) || error_on_options_handler.key?(error)
68
+ end
69
+
70
+ protected
71
+
72
+ # Provide error for the given block.
73
+ # @return [String<Array>] the result of `yield` or `leftovers` if there was an error
74
+ # where the `action` was defined as `:continue`
75
+ def with_error_handling(argv, results, leftovers)
76
+ yield
77
+ rescue RakeCommander::Options::Error::Base => e
78
+ @options_latest_error = e
79
+ eklass = e.class
80
+ # Fallback to generic error handling if specific error action is not defined
81
+ eklass = eklass.superclass unless error_on_options?(eklass)
82
+ action = error_on_options(error: eklass)
83
+
84
+ # here is where we ignore the handler (when !action == `true`)
85
+ raise unless !action || handler = error_on_options_handler(eklass)
86
+ raise if handler&.call(e, argv, results, leftovers)
87
+ return leftovers if action == :continue
88
+ puts e.message
89
+ # https://stackoverflow.com/a/23340693/4352306
90
+ exit 1
91
+ end
92
+
93
+ private
94
+
95
+ def error_on_options_handler(error = :not_used, &handler)
96
+ @error_on_options_handler ||= {}
97
+ return @error_on_options_handler if error == :not_used
98
+ RakeCommander::Options::Error::Base.require_argument!(error, :error, accept_children: true)
99
+ @error_on_options_handler[error] = handler if block_given?
100
+ @error_on_options_handler[error]
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Error
4
+ class InvalidArgument < RakeCommander::Options::Error::Base
5
+ option_regex(/invalid argument: (?<option>.+)/i.freeze)
6
+
7
+ private
8
+
9
+ def to_message(value)
10
+ return super unless opt = option
11
+ case value
12
+ when OptionParser::InvalidArgument
13
+ super("invalid option argument: #{opt.name_hyphen} (#{opt.short_hyphen})")
14
+ else
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Error
4
+ class InvalidOption < RakeCommander::Options::Error::Base
5
+ option_regex(/invalid option: (?<option>.+)/i.freeze)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Error
4
+ # Relates to options with missing required argument (when there's no `default` value)
5
+ class MissingArgument < RakeCommander::Options::Error::Base
6
+ option_regex(/missing(?: required|) argument: (?<option>.+)/i.freeze)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,48 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Error
4
+ # Relates to the `required` parameter when defining an option.
5
+ class MissingOption < RakeCommander::Options::Error::Base
6
+ def initialize(value = nil, from: nil)
7
+ super("missing required option: #{to_message(value)}", from: from)
8
+ end
9
+
10
+ def options
11
+ super | to_options(@value)
12
+ end
13
+
14
+ protected
15
+
16
+ def to_message(value)
17
+ case value
18
+ when RakeCommander::Option
19
+ "#{value.name_hyphen} (#{value.short_hyphen})"
20
+ when Hash
21
+ to_message(value.values.uniq)
22
+ when Array
23
+ value.map do |v|
24
+ v.is_a?(RakeCommander::Option)? to_message(v) : v
25
+ end.join(', ')
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def to_options(value)
34
+ case value
35
+ when RakeCommander::Option
36
+ [value]
37
+ when Array
38
+ value.select {|v| v.is_a?(RakeCommander::Option)}
39
+ when Hash
40
+ to_options(value.values)
41
+ else
42
+ []
43
+ end.compact
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Error
4
+ # Relates to `OptionParser#parse` output (**leftovers**)
5
+ class UnknownArgument < RakeCommander::Options::Error::Base
6
+ def initialize(value = nil, from: nil)
7
+ super("unknown arguments: #{to_message(value)}", from: from)
8
+ end
9
+
10
+ def leftovers
11
+ case @value
12
+ when Array
13
+ @value
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def to_message(value)
22
+ case value
23
+ when Array
24
+ value.map {|v| "'#{v}'"}.join(', ')
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,18 +1,83 @@
1
- require_relative 'error_rely'
1
+ require_relative 'error/base'
2
+ require_relative 'error/missing_argument'
3
+ require_relative 'error/invalid_argument'
4
+ require_relative 'error/invalid_option'
5
+ require_relative 'error/missing_option'
6
+ require_relative 'error/unknown_argument'
7
+ require_relative 'error/handling'
8
+
2
9
  class RakeCommander
3
10
  module Options
4
- class MissingOption < RakeCommander::Options::ErrorRely
5
- def initialize(value)
6
- super("missing required option: #{to_description(value)}")
11
+ module Error
12
+ class << self
13
+ def included(base)
14
+ super(base)
15
+ base.send :include, RakeCommander::Options::Error::Handling
16
+ base.extend ClassMethods
17
+ end
7
18
  end
8
- end
9
19
 
10
- class MissingArgument < RakeCommander::Options::ErrorRely
11
- OPTION_REGEX = /missing (?:required|) argument: (?<option>.+)/i.freeze
12
- end
20
+ module ClassMethods
21
+ # Re-open method to add all the error handling.
22
+ # @see RakeCommander::Options::Result
23
+ def parse_options(argv = ARGV, results: {}, leftovers: [], &block)
24
+ with_error_handling(argv, results, leftovers) do
25
+ super.tap do |_|
26
+ check_on_leftovers(leftovers)
27
+ check_required_presence(results)
28
+ end
29
+ rescue OptionParser::InvalidOption => e
30
+ eklass = RakeCommander::Options::Error::InvalidOption
31
+ raise eklass.new(e, from: self), nil, cause: nil
32
+ rescue OptionParser::MissingArgument => e
33
+ eklass = RakeCommander::Options::Error::MissingArgument
34
+ opt = error_option(e, eklass)
35
+ msg = e.message
36
+ msg = "missing required argument: #{opt.name_hyphen} (#{opt.short_hyphen})" if opt
37
+ raise eklass.new(from: self, option: opt), msg, cause: nil
38
+ rescue OptionParser::InvalidArgument => e
39
+ eklass = RakeCommander::Options::Error::InvalidArgument
40
+ opt = error_option(e, eklass)
41
+ raise eklass.new(e, from: self, option: opt), nil, cause: nil unless opt&.argument_required?
42
+ eklass = RakeCommander::Options::Error::MissingArgument
43
+ msg = "missing required argument in option: #{opt.name_hyphen} (#{opt.short_hyphen})"
44
+ raise eklass.new(from: self, option: opt), msg, cause: nil
45
+ end
46
+ end
47
+
48
+ protected
13
49
 
14
- class InvalidArgument < RakeCommander::Options::ErrorRely
15
- OPTION_REGEX = /invalid argument: (?<option>.+)/i.freeze
50
+ # Helper to retrieve an existing `RakeCommander::Option` out of a
51
+ # `OptionParser` error.
52
+ # @param e [OptionParser:Error] containing the original error `message`
53
+ # @param eklass [RakeCommander::Options::Error:Base::Class] the error class to retrive the option key
54
+ # @return [RakeCommander::Option, NilClass]
55
+ def error_option(err, eklass)
56
+ return false unless option_sym = eklass.option_sym(err.message)
57
+ options_hash(with_implicit: true)[option_sym]
58
+ end
59
+
60
+ private
61
+
62
+ # It implements the logic defined by `error_on_leftovers`.
63
+ # @param leftovers [Array<String>]
64
+ # @param results [Hash] the parsed options
65
+ # @return [Hash] the results (same object)
66
+ def check_on_leftovers(leftovers)
67
+ return if leftovers.empty?
68
+ eklass = RakeCommander::Options::Error::UnknownArgument
69
+ raise eklass.new(leftovers, from: self)
70
+ end
71
+
72
+ # It throws an exception if any of the required options
73
+ # is missing in results
74
+ def check_required_presence(results)
75
+ missing = options.select(&:required?).reject do |opt|
76
+ results.key?(opt.short) || results.key?(opt.name)
77
+ end
78
+ raise RakeCommander::Options::Error::MissingOption.new(missing, from: self) unless missing.empty?
79
+ end
80
+ end
16
81
  end
17
82
  end
18
83
  end
@@ -1,15 +1,17 @@
1
1
  class RakeCommander
2
2
  module Options
3
3
  module Name
4
+ BOOLEAN_TOKEN = '[no-]'.freeze
4
5
  # Substitions
5
6
  HYPHEN_START_REGEX = /^-+/.freeze
6
7
  HYPEN_REGEX = /-+/.freeze
7
8
  UNDERSCORE_REGEX = /_+/.freeze
8
- SPACE_REGEX = /\s+/.freeze
9
- # Checkers
9
+ WORD_DELIMITER = /[\s=]+/.freeze
10
+ # Checkers / Capturers
10
11
  OPTIONAL_REGEX = /\[\w+\]$/.freeze
11
12
  SINGLE_HYPHEN_REGEX = /^-(?<options>[^- ][^ ]*)/.freeze
12
- DOUBLE_HYPHEN_REGEX = /^--(?<option>[^- ][^ ]*)/.freeze
13
+ DOUBLE_HYPHEN_REGEX = /^(?:--\[?no-\]?|--)(?<option>[^- ][^ \r\n]*).*$/.freeze
14
+ BOOLEAN_NAME_REGEX = /^[^ ]*#{Regexp.escape(BOOLEAN_TOKEN)}[^ ]{2,}/.freeze
13
15
 
14
16
  # @return [Boolean]
15
17
  def single_hyphen?(value)
@@ -23,32 +25,68 @@ class RakeCommander
23
25
  !!value.to_s.match(DOUBLE_HYPHEN_REGEX)
24
26
  end
25
27
 
28
+ # @return [Boolean] whether the name has the boolean switch `[no-]`
29
+ def boolean_name?(value)
30
+ return false unless value.respond_to?(:to_s)
31
+ !!value.to_s.match(BOOLEAN_NAME_REGEX)
32
+ end
33
+
26
34
  # @param strict [Boolean] whether hyphen is required when declaring an option `short`
27
35
  # @return [Boolean]
28
36
  def valid_short?(value, strict: false)
29
- return false unless value.respond_to?(:to_s) && !value.to_s.empty?
30
- return false unless !strict || single_hypen(value)
31
- short_sym(value).to_s.length == 1
37
+ return false unless value.respond_to?(:to_s)
38
+ value = value.to_s.strip
39
+ return false if value.empty?
40
+ return false if strict && !single_hyphen?(value)
41
+ value = value.gsub(HYPHEN_START_REGEX, '')
42
+ value.length == 1
32
43
  end
33
44
 
34
45
  # @param strict [Boolean] whether hyphen is required when declaring an option `name`
35
46
  # @return [Boolean]
36
47
  def valid_name?(value, strict: false)
37
- return false unless value.respond_to?(:to_s) && !value.to_s.empty?
38
- return false unless !strict || double_hyphen?(value)
48
+ return false unless value.respond_to?(:to_s)
49
+ value = value.to_s.strip
50
+ return false if value.empty?
51
+ return false if strict && !double_hyphen?(value)
39
52
  name_sym(value).to_s.length > 1
40
53
  end
41
54
 
42
- # @param strict [Boolean] whether hyphen is required when declaring an option `short`
43
- # @return [Boolean] whether `value` is an hyphened option `short`
44
- def short_hyphen?(value, strict: false)
45
- short?(value, strict: strict) && single_hypen(value)
46
- end
47
-
48
- # @param strict [Boolean] whether hyphen is required when declaring an option `name`
49
- # @return [Boolean] whether `value` is an hyphened option `name`
50
- def name_hyphen?(value, strict: false)
51
- name?(value, strict: strict) && double_hyphen?(value)
55
+ # Modifies `args` and returns the short candidate
56
+ # @param args [Array<String, Symbol>]
57
+ # @return [String, Symbol] the short candidate
58
+ def capture_arguments_short!(args, strict: true, symbol: false)
59
+ capture_argument_with!(args) do |arg|
60
+ next false unless arg.is_a?(String) || arg.is_a?(Symbol)
61
+ next false if symbol && !arg.is_a?(Symbol)
62
+ valid_short?(arg, strict: strict)
63
+ end
64
+ end
65
+
66
+ # Modifies `args` and returns the name candidate
67
+ # @param args [Array<String, Symbol>]
68
+ # @return [String, Symbol] the name candidate
69
+ def capture_arguments_name!(args, strict: true, symbol: false)
70
+ capture_argument_with!(args) do |arg|
71
+ next false unless arg.is_a?(String) || arg.is_a?(Symbol)
72
+ next false if symbol && !arg.is_a?(Symbol)
73
+ valid_name?(arg, strict: strict)
74
+ end
75
+ end
76
+
77
+ # Modifies `args` and returns the arg candidate
78
+ # @param args [Array<String, Symbol>]
79
+ # @return [String, Symbol, NilClass] the arg candidate
80
+ def capture_argument_with!(args)
81
+ raise ArgumentError, "Expecting Array. Given: #{args.class}" unless args.is_a?(Array)
82
+ args.dup.find.with_index do |arg, i|
83
+ yield(arg).tap do |valid|
84
+ next unless valid
85
+ args.slice!(i)
86
+ return arg
87
+ end
88
+ end
89
+ nil
52
90
  end
53
91
 
54
92
  # Converter
@@ -57,7 +95,8 @@ class RakeCommander
57
95
  # @return [Symbol, NilClass]
58
96
  def short_sym(value)
59
97
  return nil unless value
60
- value = value.to_s.gsub(HYPHEN_START_REGEX, '')
98
+ value = value.to_s.gsub(BOOLEAN_TOKEN, '')
99
+ value = value.gsub(HYPHEN_START_REGEX, '')
61
100
  return nil unless value = value.chars.first
62
101
  value.to_sym
63
102
  end
@@ -74,20 +113,25 @@ class RakeCommander
74
113
  return nil unless value
75
114
  value = value.to_s.gsub(HYPHEN_START_REGEX, '')
76
115
  value = value.gsub(HYPEN_REGEX, '_')
77
- value = value.gsub(SPACE_REGEX, ' ')
116
+ value = value.gsub(WORD_DELIMITER, ' ')
78
117
  return nil if value.empty?
79
118
  value.to_sym
80
119
  end
81
120
 
82
121
  # It's like `#name_sym` but it only gets the option name.
122
+ # @note
123
+ # 1. It also removes the boolean token `[no-]`
83
124
  # @example
84
125
  # * `"--there-we-go ARGUMENT"` becomes `:there_we_go`
126
+ # * `"--[no]-verbose"` becomes `:verbose`
85
127
  # @see #name_sym
86
128
  # @return [Symbol, NilClass]
87
129
  def name_word_sym(value)
88
130
  return nil unless value = name_sym(value)
131
+ value = value.to_s.gsub(BOOLEAN_TOKEN, '')
132
+
89
133
  return nil unless value = name_words(value).first
90
- value.to_sym
134
+ value.downcase.to_sym
91
135
  end
92
136
 
93
137
  # @return [String, NilClass] it returns the hyphened (`-`) version of a short `value`
@@ -98,7 +142,7 @@ class RakeCommander
98
142
 
99
143
  # Gets the actual name of the option. First word.
100
144
  # @example
101
- # * `"--there-we-go ARGUMENT"` becomes `"--there-we-go"`
145
+ # * `"--there-we-go ARGUMENT"` becomes `"--there-we-go ARGUMENT"`
102
146
  # * `"there-we-go"` becomes `"--there-we-go"`
103
147
  # * `:there_we_go` becomes `"--there-we-go"`
104
148
  # @return [String, NilClass] option `name` alone double hypened (`--`)
@@ -153,7 +197,7 @@ class RakeCommander
153
197
  def name_words(value)
154
198
  return nil unless value
155
199
  value = value.to_s.gsub(HYPHEN_START_REGEX, '')
156
- value.to_s.split(SPACE_REGEX)
200
+ value.to_s.split(WORD_DELIMITER)
157
201
  end
158
202
  end
159
203
  end
@@ -0,0 +1,107 @@
1
+ class RakeCommander
2
+ module Options
3
+ module Result
4
+ class << self
5
+ def included(base)
6
+ super(base)
7
+ base.extend ClassMethods
8
+ base.attr_inheritable :options_with_defaults
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # Configuration Setting.
14
+ # @return [Boolean] whether results should include options defined
15
+ # with a default, regarless if they are invoked
16
+ def options_with_defaults(value = nil)
17
+ if value.nil?
18
+ @options_with_defaults || false
19
+ else
20
+ @options_with_defaults = !!value
21
+ end
22
+ end
23
+
24
+ # It **re-opens** the method and adds a middleware to gather and return
25
+ # the results of the parsing (including the `leftovers`)
26
+ # @note this extends the method parameters and changes the returned value.
27
+ # @param leftovers [Array<String>] see RakeCommander::Options#parse_options`
28
+ # @param results [Hash] with `short` option as `key` and final value as `value`.
29
+ # @see `RakeCommander::Options#parse_options`
30
+ def parse_options(argv = ARGV, results: {}, leftovers: [], &middleware)
31
+ leftovers.push(*super(argv, &results_collector(results, &middleware)))
32
+ end
33
+
34
+ # **Extend** method to ensure options are parsed before calling task.
35
+ # This only happens if `task_method` has it's context (binding) with
36
+ # an instance object of this class.
37
+ # @note
38
+ # 1. This allows stop before invoking the task, may there be options
39
+ # that have this effect (i.e. `-h`)
40
+ # 2. We use `task_context` to open up extensibility.
41
+ # @todo think if it should rather raise an `ArgumentError` when the task
42
+ # was not defined in an instance object of his class.
43
+ def install_task(&task_method)
44
+ super(&task_context(&task_method))
45
+ end
46
+
47
+ protected
48
+
49
+ # Invoke `options` parsing before calling the task.
50
+ # @return [Proc] our wrapped task block.
51
+ def task_context(&task_method)
52
+ instance = eval('self', task_method.binding, __FILE__, __LINE__)
53
+ return task_method unless instance.is_a?(self)
54
+ proc do |*task_args|
55
+ # launch `ARGV` parsing
56
+ instance.options
57
+ task_method.call(*task_args)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # Expects a block that should do the final call to `OptionParser#parse`.
64
+ # It is invoked on each option parsing (so only when that option is invoked).
65
+ # @note if an invoked option comes empty (`nil`), it uses the `default` value.
66
+ # @return [Proc] the results collector that wraps the middleware.
67
+ def results_collector(results, &middleware)
68
+ results = result_defaults(results)
69
+ proc do |value, default, short, name|
70
+ middleware&.call(value, default, short, name)
71
+ results[short] = value.nil?? default : value
72
+ end
73
+ end
74
+
75
+ # Based on `required` options, it sets the `default`
76
+ def result_defaults(results = {})
77
+ results.tap do |res_def|
78
+ options.select do |opt|
79
+ (options_with_defaults && opt.default?) \
80
+ || (opt.required? && opt.default?)
81
+ end.each do |opt|
82
+ res_def[opt.short] = opt.default
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ # Launches the options parsing of this class.
89
+ # @return [Hash] keyed by short.
90
+ def options(argv = ARGV, &block)
91
+ return @options if instance_variable_defined?(:@options)
92
+ @options = {}
93
+ self.class.parse_options(argv, results: @options, leftovers: options_leftovers, &block)
94
+ @options
95
+ end
96
+
97
+ # The options part (so after `--`) that was NOT processed
98
+ # by the `OptionParser`. They are therefore unknown parameters.
99
+ # @note an unknown option (i.e. `-f something`, `--foo something`) would trigger an invalid option error.
100
+ # This means that the `leftovers` can only refer to arguments not paired to options that receive
101
+ # parameters (i.e. `--no-foo something`).
102
+ def options_leftovers
103
+ @options_leftovers ||= []
104
+ end
105
+ end
106
+ end
107
+ end
@@ -6,9 +6,15 @@ class RakeCommander
6
6
  class << self
7
7
  include Enumerable
8
8
 
9
+ # Name of the `Options::Set`
10
+ def name(value = :not_used)
11
+ return @name if value == :not_used
12
+ @name = value.to_sym
13
+ end
14
+
9
15
  def each(&block)
10
16
  return to_enum(:each) unless block
11
- options.values.each(&block)
17
+ options.each(&block)
12
18
  end
13
19
  end
14
20
  end