rake-commander 0.1.4 → 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.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.rubocop.yml +12 -8
- data/CHANGELOG.md +69 -4
- data/LICENSE +21 -0
- data/README.md +94 -2
- data/Rakefile +11 -13
- data/examples/01_basic_example.rb +28 -0
- data/examples/02_a_chainer_example.rb +66 -0
- data/examples/02_a_chainer_options_set.rb +8 -0
- data/examples/02_b_chained_example.rb +13 -0
- data/examples/03_a_chainer_plus_example.rb +34 -0
- data/examples/03_b_chained_plus_example.rb +17 -0
- data/examples/Examples.rake +7 -0
- data/examples/README.md +79 -0
- data/examples/libs/shell_helpers.rb +81 -0
- data/lib/rake-commander/base/class_auto_loader.rb +45 -7
- data/lib/rake-commander/base/class_helpers.rb +16 -61
- data/lib/rake-commander/base/class_inheritable.rb +122 -0
- data/lib/rake-commander/base/custom_error.rb +52 -0
- data/lib/rake-commander/base/object_helpers.rb +42 -0
- data/lib/rake-commander/base.rb +16 -2
- data/lib/rake-commander/option.rb +115 -25
- data/lib/rake-commander/options/arguments.rb +206 -94
- data/lib/rake-commander/options/description.rb +17 -0
- data/lib/rake-commander/options/error/base.rb +86 -0
- data/lib/rake-commander/options/error/handling.rb +106 -0
- data/lib/rake-commander/options/error/invalid_argument.rb +21 -0
- data/lib/rake-commander/options/error/invalid_option.rb +9 -0
- data/lib/rake-commander/options/error/missing_argument.rb +10 -0
- data/lib/rake-commander/options/error/missing_option.rb +48 -0
- data/lib/rake-commander/options/error/unknown_argument.rb +32 -0
- data/lib/rake-commander/options/error.rb +75 -10
- data/lib/rake-commander/options/name.rb +67 -23
- data/lib/rake-commander/options/result.rb +107 -0
- data/lib/rake-commander/options/set.rb +7 -1
- data/lib/rake-commander/options.rb +175 -98
- data/lib/rake-commander/patcher/README.md +79 -0
- data/lib/rake-commander/patcher/application/run_method.rb +46 -0
- data/lib/rake-commander/patcher/application/top_level_method.rb +74 -0
- data/lib/rake-commander/patcher/application.rb +16 -0
- data/lib/rake-commander/patcher/base.rb +45 -0
- data/lib/rake-commander/patcher/debug.rb +32 -0
- data/lib/rake-commander/patcher/helpers.rb +44 -0
- data/lib/rake-commander/patcher.rb +26 -0
- data/lib/rake-commander/rake_context/wrapper.rb +2 -0
- data/lib/rake-commander/rake_task.rb +49 -54
- data/lib/rake-commander/version.rb +1 -1
- data/lib/rake-commander.rb +4 -0
- data/rake-commander.gemspec +4 -1
- metadata +74 -6
- data/examples/basic.rb +0 -30
- data/lib/rake-commander/options/error_rely.rb +0 -58
@@ -3,26 +3,41 @@ class RakeCommander
|
|
3
3
|
class Option < @option_struct
|
4
4
|
extend RakeCommander::Base::ClassHelpers
|
5
5
|
extend RakeCommander::Options::Name
|
6
|
+
include RakeCommander::Options::Description
|
6
7
|
|
7
|
-
|
8
|
-
attr_writer :type_coertion, :required
|
9
|
-
attr_reader :name_full
|
8
|
+
attr_reader :name_full, :desc, :default
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
# @param sample [Boolean] allows to skip the `short` and `name` validations
|
11
|
+
def initialize(*args, sample: false, **kargs, &block)
|
12
|
+
short, name = capture_arguments_short_n_name!(args, kargs, sample: sample)
|
14
13
|
|
15
|
-
@name_full
|
16
|
-
super(short,
|
14
|
+
@name_full = name.freeze
|
15
|
+
super(short.freeze, @name_full)
|
17
16
|
@default = kargs[:default] if kargs.key?(:default)
|
18
17
|
@desc = kargs[:desc] if kargs.key?(:desc)
|
19
18
|
@required = kargs[:required] if kargs.key?(:required)
|
19
|
+
@type_coertion = kargs[:type] if kargs.key?(:type)
|
20
20
|
@other_args = args
|
21
21
|
@original_block = block
|
22
|
-
yield(self) if block_given?
|
23
22
|
configure_other
|
24
23
|
end
|
25
24
|
|
25
|
+
# Makes a copy of this option
|
26
|
+
# @return [RakeCommander::Option]
|
27
|
+
def dup(**kargs, &block)
|
28
|
+
block ||= original_block
|
29
|
+
self.class.new(**dup_key_arguments.merge(kargs), &block)
|
30
|
+
end
|
31
|
+
alias_method :deep_dup, :dup
|
32
|
+
|
33
|
+
# Creates a new option, result of merging this `opt` with this option,
|
34
|
+
# @return [RakeCommander::Option] where opt has been merged
|
35
|
+
def merge(opt)
|
36
|
+
raise "Expecting RakeCommander::Option. Given: #{opt.class}" unless opt.is_a?(RakeCommander::Option)
|
37
|
+
dup(**opt.dup_key_arguments, &opt.original_block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Boolean] whether this option is required.
|
26
41
|
def required?
|
27
42
|
!!@required
|
28
43
|
end
|
@@ -32,6 +47,13 @@ class RakeCommander
|
|
32
47
|
self.class.short_sym(super)
|
33
48
|
end
|
34
49
|
|
50
|
+
# `OptionParser` interprets free shorts that match the first letter of an option name
|
51
|
+
# as an invocation of that option. This method allows to identify this.
|
52
|
+
# return [Symbol]
|
53
|
+
def short_implicit
|
54
|
+
self.class.short_sym(@name_full)
|
55
|
+
end
|
56
|
+
|
35
57
|
# @return [String]
|
36
58
|
def short_hyphen
|
37
59
|
self.class.short_hyphen(short)
|
@@ -47,6 +69,11 @@ class RakeCommander
|
|
47
69
|
self.class.name_hyphen(name_full)
|
48
70
|
end
|
49
71
|
|
72
|
+
# @return [Boolean]
|
73
|
+
def boolean_name?
|
74
|
+
self.class.boolean_name?(name_full)
|
75
|
+
end
|
76
|
+
|
50
77
|
# @param [Boolean] whether this option allows an argument
|
51
78
|
def argument?
|
52
79
|
self.class.name_argument?(name_full)
|
@@ -75,26 +102,43 @@ class RakeCommander
|
|
75
102
|
|
76
103
|
# Adds this option's switch to the `OptionParser`
|
77
104
|
# @note it allows to add a `middleware` block that will be called at `parse` runtime
|
78
|
-
|
105
|
+
# @param opt_parser [OptionParser] the option parser to add this option's switch.
|
106
|
+
# @param implicit_short [Boolean] whether the implicit short of this option is active in the opts_parser.
|
107
|
+
def add_switch(opts_parser, where: :base, implicit_short: false, &middleware)
|
79
108
|
raise "Expecting OptionParser. Given: #{opts_parser.class}" unless opts_parser.is_a?(OptionParser)
|
109
|
+
args = switch_args(implicit_short: implicit_short)
|
110
|
+
block = option_block(&middleware)
|
80
111
|
case where
|
81
112
|
when :head, :top
|
82
|
-
opts_parser.on_head(*
|
113
|
+
opts_parser.on_head(*args, &block)
|
83
114
|
when :tail, :end
|
84
|
-
opts_parser.on_tail(*
|
115
|
+
opts_parser.on_tail(*args, &block)
|
85
116
|
else # :base
|
86
|
-
opts_parser.on(*
|
117
|
+
opts_parser.on(*args, &block)
|
87
118
|
end
|
88
119
|
opts_parser
|
89
120
|
end
|
90
121
|
|
122
|
+
protected
|
123
|
+
|
124
|
+
attr_reader :original_block
|
125
|
+
|
126
|
+
# @return [Hash] keyed arguments to create a new object
|
127
|
+
def dup_key_arguments
|
128
|
+
{}.tap do |kargs|
|
129
|
+
kargs.merge!(short: short.dup.freeze) if short
|
130
|
+
kargs.merge!(name: name_full.dup.freeze) if name_full
|
131
|
+
kargs.merge!(desc: desc.dup) if desc
|
132
|
+
kargs.merge!(default: default.dup) if default?
|
133
|
+
kargs.merge!(required: required?)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
91
137
|
# @return [Array<Variant>]
|
92
|
-
def switch_args
|
138
|
+
def switch_args(implicit_short: false)
|
93
139
|
configure_other
|
94
140
|
args = [short_hyphen, name_hyphen]
|
95
|
-
|
96
|
-
args << str
|
97
|
-
end
|
141
|
+
args.push(*switch_desc(implicit_short: implicit_short))
|
98
142
|
args << type_coertion if type_coertion
|
99
143
|
args
|
100
144
|
end
|
@@ -106,26 +150,72 @@ class RakeCommander
|
|
106
150
|
block_extra_args = [default, short, name]
|
107
151
|
proc do |value|
|
108
152
|
args = block_extra_args.dup.unshift(value)
|
109
|
-
|
153
|
+
original_block&.call(*args)
|
110
154
|
middleware&.call(*args)
|
111
155
|
end
|
112
156
|
end
|
113
157
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
158
|
+
# @note in `OptionParser` you can multiline the description with alignment
|
159
|
+
# by providing multiple strings.
|
160
|
+
# @return [Array<String>]
|
161
|
+
def switch_desc(implicit_short: false, line_width: DESC_MAX_LENGTH)
|
162
|
+
ishort = implicit_short ? "( -#{short_implicit} ) " : ''
|
163
|
+
str = "#{required_desc}#{ishort}#{desc}#{default_desc}"
|
164
|
+
return [] if str.empty?
|
165
|
+
string_to_lines(str, max: line_width)
|
166
|
+
end
|
167
|
+
|
168
|
+
def required_desc
|
169
|
+
required?? "< REQ > " : "[ opt ] "
|
118
170
|
end
|
119
171
|
|
120
172
|
def default_desc
|
121
173
|
return nil unless default?
|
122
|
-
str = "Default: '#{default}'"
|
174
|
+
str = "{ Default: '#{default}' }"
|
123
175
|
if desc && !desc.downcase.include?('default')
|
124
176
|
str = desc.end_with?('.') ? " #{str}" : ". #{str}"
|
125
177
|
end
|
126
178
|
str
|
127
179
|
end
|
128
180
|
|
181
|
+
# Helper to simplify `short` and `name` capture from arguments and keyed arguments.
|
182
|
+
# @return [Array<Symbol, String>] the pair `[short, name]`
|
183
|
+
def capture_arguments_short_n_name!(args, kargs, sample: false)
|
184
|
+
name, short = kargs.values_at(:name, :short)
|
185
|
+
short ||= capture_arguments_short!(args)
|
186
|
+
name ||= capture_arguments_name!(args, sample_n_short: sample && short)
|
187
|
+
|
188
|
+
unless sample
|
189
|
+
raise ArgumentError, "A short of one letter should be provided. Given: #{short}" unless self.class.valid_short?(short)
|
190
|
+
raise ArgumentError, "A name should be provided. Given: #{name}" unless self.class.valid_name?(name)
|
191
|
+
end
|
192
|
+
|
193
|
+
[short, name]
|
194
|
+
end
|
195
|
+
|
196
|
+
# Helper to figure out the option short from args
|
197
|
+
# @note if found it removes it from args.
|
198
|
+
# @return [String, Symbol, NilClass]
|
199
|
+
def capture_arguments_short!(args)
|
200
|
+
short = nil
|
201
|
+
short ||= self.class.capture_arguments_short!(args, symbol: true)
|
202
|
+
short ||= self.class.capture_arguments_short!(args, symbol: true, strict: false)
|
203
|
+
short ||= self.class.capture_arguments_short!(args)
|
204
|
+
short || self.class.capture_arguments_short!(args, strict: false)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Helper to figure out the option name from args
|
208
|
+
# @note if found it removes it from args.
|
209
|
+
# @return [String, Symbol, NilClass]
|
210
|
+
def capture_arguments_name!(args, sample_n_short: false)
|
211
|
+
name = nil
|
212
|
+
name ||= self.class.capture_arguments_name!(args, symbol: true)
|
213
|
+
name ||= self.class.capture_arguments_name!(args, symbol: true, strict: false)
|
214
|
+
name ||= self.class.capture_arguments_name!(args)
|
215
|
+
name || self.class.capture_arguments_name!(args, strict: false) unless sample_n_short
|
216
|
+
end
|
217
|
+
|
218
|
+
# The remaining `args` received in the initialization
|
129
219
|
def other_args(*args)
|
130
220
|
@other_args ||= []
|
131
221
|
if args.empty?
|
@@ -138,11 +228,11 @@ class RakeCommander
|
|
138
228
|
# It consumes `other_args`, to prevent direct overrides to be overriden by it.
|
139
229
|
def configure_other
|
140
230
|
if type = other_args.find {|arg| arg.is_a?(Class)}
|
141
|
-
|
231
|
+
@type_coertion = type
|
142
232
|
other_args.delete(type)
|
143
233
|
end
|
144
234
|
if value = other_args.find {|arg| arg.is_a?(String)}
|
145
|
-
|
235
|
+
@desc = value
|
146
236
|
other_args.dup.each do |val|
|
147
237
|
other_args.delete(val) if val.is_a?(String)
|
148
238
|
end
|
@@ -2,112 +2,224 @@ class RakeCommander
|
|
2
2
|
module Options
|
3
3
|
# Offers helpers to treat `ARGV`
|
4
4
|
module Arguments
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# to prevent the `OptionParser::MissingArgument` error to stop the parsing process.
|
14
|
-
# @note
|
15
|
-
# 1. Any word or letter with _hypen_ -`` or _double hypen_ `--` is interpreted as option(s)
|
16
|
-
# 2. To overcome this limitation, you may enclose in double quotes and argument with
|
17
|
-
# that start (i,e, `"--argument"`).
|
18
|
-
# @example
|
19
|
-
# 1. `-abc ARGUMENT` where only `c` receives the argument becomes `-ab -c ARGUMENT`
|
20
|
-
# 3. `-abc ARGUMENT` where `b` and `c` are argument receivers becomes `-a -b nil -c ARGUMENT`
|
21
|
-
# 2. `-acb ARGUMENT` where only `c` receives the argument becomes `-a -c nil -b ARGUMENT`
|
22
|
-
# 4. `-c --some-option ARGUMENT` where both options receive argument, becomes `-c nil --some-option ARGUMENT`
|
23
|
-
# 5. `-c --some-option -d ARGUMENT` where both options receive argument, becomes `-c nil --some-option nil -d ARGUMENT`
|
24
|
-
# 6. `-cd ARGUMENT` where `c` default is `"yeah"`, becomes `-c yeah -d ARGUMENT`
|
25
|
-
# @param argv [Array<String>]
|
26
|
-
# @param options [Hash] the defined `RakeCommander::Option` to re-arrange `argv` with.
|
27
|
-
# @return [Array<String>] the re-arranged `argv`
|
28
|
-
def pre_parse_arguments(argv = ARGV, options:)
|
29
|
-
pre_parsed = explicit_argument_options(argv, options)
|
30
|
-
compact_short = ''
|
31
|
-
pre_parsed.each_with_object([]) do |(opt_ref, args), out|
|
32
|
-
next out.push(*args) unless opt_ref.is_a?(Symbol)
|
33
|
-
is_short = opt_ref.to_s.length == 1
|
34
|
-
next compact_short << opt_ref.to_s if is_short && args.empty?
|
35
|
-
out.push("-#{compact_short}") unless compact_short.empty?
|
36
|
-
compact_short = ''
|
37
|
-
opt_str = is_short ? "-#{opt_ref}" : name_hyphen(opt_ref)
|
38
|
-
out.push(opt_str, *args)
|
39
|
-
end.tap do |out|
|
40
|
-
out.push("-#{compact_short}") unless compact_short.empty?
|
5
|
+
RAKE_COMMAND_EXTENDED_OPTIONS_START = '--'.freeze
|
6
|
+
NAME_ARGUMENT = /^--(?<option>[\w_-]*).*?$/.freeze
|
7
|
+
BOOLEAN_ARGUMENT = /(?:^|--)no-(?<option>[\w_-]*).*?$/.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def included(base)
|
11
|
+
super(base)
|
12
|
+
base.extend ClassMethods
|
41
13
|
end
|
42
14
|
end
|
43
15
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# 6. `-cd ARGUMENT` where `c` default is `"yeah"`, becomes `:c yeah :d ARGUMENT`
|
53
|
-
# @return [Hash<Symbol, Array>]
|
54
|
-
def explicit_argument_options(argv, options)
|
55
|
-
decoupled = decluster_shorts_n_names_to_sym(argv)
|
56
|
-
grouped = group_symbols_with_strings(decoupled)
|
57
|
-
normalized = insert_missing_argument_to_groups(grouped, options)
|
58
|
-
normalized.each_with_object({}) do |group, pre_parsed|
|
59
|
-
opt_ref = group.first.is_a?(Symbol)? group.shift : nil
|
60
|
-
pre_parsed[opt_ref] = group
|
16
|
+
module ClassMethods
|
17
|
+
include RakeCommander::Options::Name
|
18
|
+
|
19
|
+
# @note it assumes `ARGV` has been left unaltered.
|
20
|
+
# @return [Boolean] whether enhanced parsing should be switched on or off.
|
21
|
+
def argv_with_enhanced_syntax?(argv = ARGV)
|
22
|
+
return false unless argv.is_a?(Array)
|
23
|
+
argv.include?(RAKE_COMMAND_EXTENDED_OPTIONS_START)
|
61
24
|
end
|
62
|
-
end
|
63
25
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
26
|
+
# Configuration setting
|
27
|
+
# Whether the additional arguments (extended options) managed by this gem
|
28
|
+
# should be removed/consumed from `ARGV` before `Rake` processes option arguments.
|
29
|
+
# @note
|
30
|
+
# 1. When `true` it **will enable**
|
31
|
+
# * A **patch** on `Rake::Application`**, provided that `ARGV` is cropped
|
32
|
+
# before `Rake` identifies **tasks** and rake native **options**.
|
33
|
+
# Note that this specific patch only works if rake commander was loaded
|
34
|
+
# BEFORE `Rake::Application#run` is invoked.
|
35
|
+
# 2. When `false`, an implicit `exit(0)` is added at the end of a rake task
|
36
|
+
# defined via `RakeCommander`, as a work-around that prevents `Rake` from
|
37
|
+
# chaining option arguments as if they were actual tasks.
|
38
|
+
# @note
|
39
|
+
# 1. This only refers to what comes after `RAKE_COMMAND_EXTENDED_OPTIONS_START` (`--`)
|
40
|
+
# @return [Boolean]
|
41
|
+
def argv_cropping_for_rake(value = :not_used)
|
42
|
+
@argv_cropping_for_rake = true if @argv_cropping_for_rake.nil?
|
43
|
+
return @argv_cropping_for_rake if value == :not_used
|
44
|
+
@argv_cropping_for_rake = !!value
|
45
|
+
end
|
46
|
+
|
47
|
+
# It returns the part of `ARGV` that are arguments of `RakeCommander::Options` parsing.
|
48
|
+
# @note please observe that `Rake` has it's own options. For this reason using
|
49
|
+
# a delimiter (`RAKE_COMMAND_EXTENDED_OPTIONS_START`) shows up to be necessary to
|
50
|
+
# create some sort of command line argument namespacing.
|
51
|
+
# @param argv [Array<String>] the command line arguments array.
|
52
|
+
# @return [Array<String>] the target arguments to be parsed by `RakeCommander::Options`
|
53
|
+
def argv_extended_options(argv = ARGV.dup)
|
54
|
+
if idx = argv.index(RAKE_COMMAND_EXTENDED_OPTIONS_START)
|
55
|
+
argv[idx+1..-1]
|
56
|
+
else
|
57
|
+
[]
|
58
|
+
end
|
77
59
|
end
|
78
|
-
end
|
79
60
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
argv.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
61
|
+
# It slices from the original `ARGV` the extended_options of this gem.
|
62
|
+
# @note this is necessary to prevent `Rake` to interpret them.
|
63
|
+
# @return [Array<String>] the argv without the extended options of this gem.
|
64
|
+
def argv_rake_native_arguments(argv = ARGV.dup)
|
65
|
+
return argv unless argv_cropping_for_rake
|
66
|
+
if idx = argv.index(RAKE_COMMAND_EXTENDED_OPTIONS_START)
|
67
|
+
argv = argv[0..idx]
|
68
|
+
end
|
69
|
+
argv
|
70
|
+
end
|
71
|
+
|
72
|
+
# **Re-open** `parse_options` method, provided that we slice `ARGV`
|
73
|
+
# to only include the extended options of this gem, which start at
|
74
|
+
# `RAKE_COMMAND_EXTENDED_OPTIONS_START`.
|
75
|
+
# @note
|
76
|
+
# 1. Without this `ARGV` cut, it will throw `OptionParser::InvalidOption` error
|
77
|
+
# - So some tidy up is necessary and the head of the command (i.e. `rake some:task --`)
|
78
|
+
# should be excluded from arguments to input to the options parser.
|
79
|
+
# @see `RakeCommander::Options#parse_options`
|
80
|
+
def parse_options(argv = ARGV, *args, **kargs, &block)
|
81
|
+
argv = argv_extended_options(argv)
|
82
|
+
argv = argv_pre_parsed(argv, options: options_hash(with_implicit: true))
|
83
|
+
super(argv, *args, **kargs, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Options with arguments should not take another option as value.
|
87
|
+
# `OptionParser` can do this even if the the argument is optional.
|
88
|
+
# This method re-arranges the arguments based on options that receive parameters,
|
89
|
+
# provided that an option is not taken as a value of a previous option that accepts arguments.
|
90
|
+
# If an option with argument is missing the argument, but has a `default` value,
|
91
|
+
# that `default` value will be inserted after the option in the array
|
92
|
+
# to prevent the `OptionParser::MissingArgument` error to stop the parsing process.
|
93
|
+
# @note
|
94
|
+
# 1. Any word or letter with _hypen_ -`` or _double hypen_ `--` is interpreted as option(s)
|
95
|
+
# 2. To overcome this limitation, you may enclose in double quotes and argument with
|
96
|
+
# that start (i,e, `"--argument"`).
|
97
|
+
# @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`
|
104
|
+
# @param argv [Array<String>]
|
105
|
+
# @param options [Hash] the defined `RakeCommander::Option` to re-arrange `argv` with.
|
106
|
+
# @return [Array<String>] the re-arranged `argv`
|
107
|
+
def argv_pre_parsed(argv = ARGV, options:)
|
108
|
+
pre_parsed = explicit_argument_options(argv, options)
|
109
|
+
compact_short = ''
|
110
|
+
pre_parsed.each_with_object([]) do |(opt_ref, args), out|
|
111
|
+
next out.push(*args) unless opt_ref.is_a?(Symbol)
|
112
|
+
is_short = opt_ref.to_s.length == 1
|
113
|
+
next compact_short << opt_ref.to_s if is_short && args.empty?
|
114
|
+
out.push("-#{compact_short}") unless compact_short.empty?
|
115
|
+
compact_short = ''
|
116
|
+
opt_str = is_short ? "-#{opt_ref}" : name_hyphen(opt_ref)
|
117
|
+
out.push(opt_str, *args)
|
118
|
+
end.tap do |out|
|
119
|
+
out.push("-#{compact_short}") unless compact_short.empty?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
protected
|
124
|
+
|
125
|
+
# It wraps the `task_method` to check if the patch to crop `ARGV` is active.
|
126
|
+
# If it's not active it will call `exit(0)` at the end of the task run, to prevent
|
127
|
+
# `Rake` from interpreting option arguments as rake tasks.
|
128
|
+
# @note **reopens** `RakeCommander::RakeTask` method
|
129
|
+
# * If `argv_cropping_for_rake` is `false` it calls `exit(0)` right at the end of the task.
|
130
|
+
# * This relates on whether the patch to `Rake::Application` has been applied.
|
131
|
+
# @return [Proc] the wrapped block.
|
132
|
+
def task_context(&task_method)
|
133
|
+
proc do |*task_args|
|
134
|
+
super(&task_method).call(*task_args)
|
135
|
+
exit(0) unless argv_cropping_for_rake
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# @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`
|
148
|
+
# @return [Hash<Symbol, Array>]
|
149
|
+
def explicit_argument_options(argv, options)
|
150
|
+
decoupled = decluster_shorts_n_names_to_sym(argv)
|
151
|
+
grouped = group_symbols_with_strings(decoupled)
|
152
|
+
normalized = insert_missing_argument_to_groups(grouped, options)
|
153
|
+
normalized.each_with_object({}) do |group, pre_parsed|
|
154
|
+
opt_ref = group.first.is_a?(Symbol)? group.shift : nil
|
155
|
+
pre_parsed[opt_ref] = group
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# It ADDS the missing argument to options that expect it.
|
160
|
+
# @note
|
161
|
+
# 1. It uses `default` if present
|
162
|
+
# 2. Otherwise it uses `nil`, but only if required (not when optional).
|
163
|
+
# @param groups [@see #pair_symbols_with_strings]
|
164
|
+
def insert_missing_argument_to_groups(groups, options)
|
165
|
+
groups.each do |group|
|
166
|
+
args = group.dup
|
167
|
+
opt_ref = args.shift
|
168
|
+
next unless args.empty?
|
169
|
+
next unless opt_ref.is_a?(Symbol)
|
170
|
+
next unless opt = _retrieve_option_ref(opt_ref, options)
|
171
|
+
next unless opt.argument?
|
172
|
+
next group.push(opt.default) if opt.default?
|
173
|
+
next unless opt.argument_required?
|
174
|
+
group.push(nil)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Retrieve the option based on `ref`
|
179
|
+
# @note It might be `--no-option-name`
|
180
|
+
# @return [RakeCommander::Option, NilClass]
|
181
|
+
def _retrieve_option_ref(opt_ref, options)
|
182
|
+
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?
|
188
|
+
opt
|
189
|
+
end
|
190
|
+
|
191
|
+
# @return [Array<Array>] where the first element of each `Array` is a symbol
|
192
|
+
# followed by one or more `String`.
|
193
|
+
def group_symbols_with_strings(argv)
|
194
|
+
[].tap do |out|
|
195
|
+
curr_ary = nil
|
196
|
+
argv.each do |arg|
|
197
|
+
if arg.is_a?(Symbol)
|
198
|
+
out << (curr_ary = [arg])
|
199
|
+
else # must be `String`
|
200
|
+
out << (curr_ary = []) unless curr_ary
|
201
|
+
curr_ary << arg
|
202
|
+
end
|
91
203
|
end
|
92
204
|
end
|
93
205
|
end
|
94
|
-
end
|
95
206
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
207
|
+
# It splits `argv` compacted shorts into their `Symbol` version.
|
208
|
+
# Symbolizes option `names` (long version).
|
209
|
+
# @return [Array<String, Symbol>] where symbols are options and strings arguments.
|
210
|
+
def decluster_shorts_n_names_to_sym(argv)
|
211
|
+
argv.each_with_object([]) do |arg, out|
|
212
|
+
if single_hyphen?(arg) # short option(s)
|
213
|
+
options = arg.match(SINGLE_HYPHEN_REGEX)[:options]
|
214
|
+
options.split('').each do |short|
|
215
|
+
out << short_sym(short)
|
216
|
+
end
|
217
|
+
elsif double_hyphen?(arg) # name option
|
218
|
+
name = arg.match(NAME_ARGUMENT)[:option]
|
219
|
+
out << name_sym(name)
|
220
|
+
else # argument
|
221
|
+
out << arg
|
105
222
|
end
|
106
|
-
elsif double_hyphen?(arg) # name option
|
107
|
-
name = arg.match(DOUBLE_HYPHEN_REGEX)[:option]
|
108
|
-
out << name_sym(name)
|
109
|
-
else # argument
|
110
|
-
out << arg
|
111
223
|
end
|
112
224
|
end
|
113
225
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Options
|
3
|
+
module Description
|
4
|
+
DESC_MAX_LENGTH = 80
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def string_to_lines(str, max: DESC_MAX_LENGTH)
|
9
|
+
str.scan(liner_regex(max)).map(&:strip)
|
10
|
+
end
|
11
|
+
|
12
|
+
def liner_regex(len = DESC_MAX_LENGTH)
|
13
|
+
/.{0,#{len}}[^ ](?:\s|$)/mi
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Options
|
3
|
+
module Error
|
4
|
+
# Base error class that does a rely between OptionParser and RakeCommander errors
|
5
|
+
class Base < RakeCommander::Base::CustomError
|
6
|
+
extend RakeCommander::Options::Name
|
7
|
+
OPTION_REGEX = /(?:argument|option): (?<option>.+)/i.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Helper to check if `error` is this class or any children class
|
11
|
+
# @raise ArgumentError if it does not meet this condition.
|
12
|
+
def require_argument!(error, arg_name, accept_children: true)
|
13
|
+
msg = "Expecting #{arg_name} to be #{self}"
|
14
|
+
msg << "or child thereof." if accept_children
|
15
|
+
msg << ". Given: #{error.is_a?(Class)? error : error.class}"
|
16
|
+
raise ArgumentError, msg unless error <= self
|
17
|
+
end
|
18
|
+
|
19
|
+
# To (re)define the RegExp used to identify the option of an error message.
|
20
|
+
def option_regex(value = :not_used)
|
21
|
+
@option_regex ||= OPTION_REGEX
|
22
|
+
return @option_regex if value == :not_used
|
23
|
+
@option_regex = value
|
24
|
+
end
|
25
|
+
|
26
|
+
# Identifies the option `Symbol` (short or name) for a given message
|
27
|
+
def option_sym(message)
|
28
|
+
return nil unless match = message.match(option_regex)
|
29
|
+
option = match[:option]
|
30
|
+
return name_word_sym(option) if option.length > 1
|
31
|
+
short_sym(option)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :from, :option
|
36
|
+
|
37
|
+
def initialize(value = nil, from: nil, option: nil)
|
38
|
+
@from = from
|
39
|
+
@option = option
|
40
|
+
super(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Options that are related to the error. There may not be any.
|
44
|
+
def options
|
45
|
+
[option].compact.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def name?
|
49
|
+
option_sym.to_s.length > 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def short?
|
53
|
+
option_sym.to_s.length == 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def option_sym
|
57
|
+
@option_sym ||= self.class.option_sym(message)
|
58
|
+
end
|
59
|
+
|
60
|
+
def from_desc
|
61
|
+
return '' unless from
|
62
|
+
if from.respond_to?(:name)
|
63
|
+
"(#{from.name}) "
|
64
|
+
elsif from.respond_to?(:to_s)
|
65
|
+
"(#{from}) "
|
66
|
+
else
|
67
|
+
''
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def to_message(value)
|
74
|
+
case value
|
75
|
+
when Array
|
76
|
+
to_message(value.map {|v| "'#{v}'"}.join(', '))
|
77
|
+
when String
|
78
|
+
"#{from_desc}#{super}"
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|