rake-commander 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +12 -8
  4. data/CHANGELOG.md +69 -4
  5. data/LICENSE +21 -0
  6. data/README.md +94 -2
  7. data/Rakefile +11 -13
  8. data/examples/01_basic_example.rb +28 -0
  9. data/examples/02_a_chainer_example.rb +66 -0
  10. data/examples/02_a_chainer_options_set.rb +8 -0
  11. data/examples/02_b_chained_example.rb +13 -0
  12. data/examples/03_a_chainer_plus_example.rb +34 -0
  13. data/examples/03_b_chained_plus_example.rb +17 -0
  14. data/examples/Examples.rake +7 -0
  15. data/examples/README.md +79 -0
  16. data/examples/libs/shell_helpers.rb +81 -0
  17. data/lib/rake-commander/base/class_auto_loader.rb +45 -7
  18. data/lib/rake-commander/base/class_helpers.rb +16 -61
  19. data/lib/rake-commander/base/class_inheritable.rb +122 -0
  20. data/lib/rake-commander/base/custom_error.rb +52 -0
  21. data/lib/rake-commander/base/object_helpers.rb +42 -0
  22. data/lib/rake-commander/base.rb +16 -2
  23. data/lib/rake-commander/option.rb +115 -25
  24. data/lib/rake-commander/options/arguments.rb +206 -94
  25. data/lib/rake-commander/options/description.rb +17 -0
  26. data/lib/rake-commander/options/error/base.rb +86 -0
  27. data/lib/rake-commander/options/error/handling.rb +106 -0
  28. data/lib/rake-commander/options/error/invalid_argument.rb +21 -0
  29. data/lib/rake-commander/options/error/invalid_option.rb +9 -0
  30. data/lib/rake-commander/options/error/missing_argument.rb +10 -0
  31. data/lib/rake-commander/options/error/missing_option.rb +48 -0
  32. data/lib/rake-commander/options/error/unknown_argument.rb +32 -0
  33. data/lib/rake-commander/options/error.rb +75 -10
  34. data/lib/rake-commander/options/name.rb +67 -23
  35. data/lib/rake-commander/options/result.rb +107 -0
  36. data/lib/rake-commander/options/set.rb +7 -1
  37. data/lib/rake-commander/options.rb +175 -98
  38. data/lib/rake-commander/patcher/README.md +79 -0
  39. data/lib/rake-commander/patcher/application/run_method.rb +46 -0
  40. data/lib/rake-commander/patcher/application/top_level_method.rb +74 -0
  41. data/lib/rake-commander/patcher/application.rb +16 -0
  42. data/lib/rake-commander/patcher/base.rb +45 -0
  43. data/lib/rake-commander/patcher/debug.rb +32 -0
  44. data/lib/rake-commander/patcher/helpers.rb +44 -0
  45. data/lib/rake-commander/patcher.rb +26 -0
  46. data/lib/rake-commander/rake_context/wrapper.rb +2 -0
  47. data/lib/rake-commander/rake_task.rb +49 -54
  48. data/lib/rake-commander/version.rb +1 -1
  49. data/lib/rake-commander.rb +4 -0
  50. data/rake-commander.gemspec +4 -1
  51. metadata +74 -6
  52. data/examples/basic.rb +0 -30
  53. 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