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.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.rubocop.yml +14 -8
- data/CHANGELOG.md +84 -4
- data/Gemfile +1 -1
- data/LICENSE +21 -0
- data/README.md +95 -3
- 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 -102
- 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 +50 -50
- data/lib/rake-commander/version.rb +1 -1
- data/lib/rake-commander.rb +4 -0
- data/rake-commander.gemspec +5 -2
- metadata +75 -7
- data/examples/basic.rb +0 -30
- data/lib/rake-commander/options/error_rely.rb +0 -58
@@ -1,5 +1,8 @@
|
|
1
1
|
require_relative 'options/name'
|
2
|
+
require_relative 'options/description'
|
2
3
|
require_relative 'options/arguments'
|
4
|
+
require_relative 'options/result'
|
5
|
+
require_relative 'options/error'
|
3
6
|
require_relative 'option'
|
4
7
|
|
5
8
|
class RakeCommander
|
@@ -8,164 +11,234 @@ class RakeCommander
|
|
8
11
|
def included(base)
|
9
12
|
super(base)
|
10
13
|
base.extend RakeCommander::Base::ClassHelpers
|
14
|
+
base.extend RakeCommander::Base::ClassInheritable
|
11
15
|
base.extend ClassMethods
|
12
|
-
base.
|
13
|
-
base.
|
16
|
+
base.attr_inheritable :banner, :options_hash
|
17
|
+
base.class_resolver :option_class, RakeCommander::Option
|
18
|
+
base.send :include, RakeCommander::Options::Result
|
19
|
+
base.send :include, RakeCommander::Options::Error
|
20
|
+
base.send :include, RakeCommander::Options::Arguments
|
14
21
|
end
|
15
22
|
end
|
16
23
|
|
17
24
|
module ClassMethods
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
options_hash.values.uniq
|
25
|
+
# Overrides the auto-generated banner
|
26
|
+
def banner(desc = :not_used)
|
27
|
+
return @banner = desc unless desc == :not_used
|
28
|
+
return @banner if @banner
|
29
|
+
return task_options_banner if respond_to?(:task_options_banner, true)
|
24
30
|
end
|
25
31
|
|
26
|
-
|
27
|
-
|
32
|
+
# Defines a new option or opens for edition an existing one if `reopen: true` is used.
|
33
|
+
# @note
|
34
|
+
# - If override is `true`, it will with a Warning when same `short` or `name` clashes.
|
35
|
+
def option(*args, override: true, reopen: false, **kargs, &block)
|
36
|
+
return option_reopen(*args, override: override, **kargs, &block) if reopen
|
37
|
+
opt = option_class.new(*args, **kargs, &block)
|
38
|
+
add_to_options(opt, override: override)
|
28
39
|
end
|
29
40
|
|
30
|
-
|
31
|
-
|
41
|
+
# It re-opens an option for edition. If it does not exist, it **upserts** it.
|
42
|
+
# @note
|
43
|
+
# 1. If `override` is `false, it will fail to change the `name` or the `short`
|
44
|
+
# when they are already taken by some other option.
|
45
|
+
# 2. It will have the effect of overriding existing options
|
46
|
+
# @note when `short` and `name` are provided, `name` takes precedence over `short`
|
47
|
+
# in the lookup (to identify the existing option)
|
48
|
+
def option_reopen(*args, override: false, **kargs, &block)
|
49
|
+
aux = option_class.new(*args, **kargs, sample: true, &block)
|
50
|
+
opt = options_hash.values_at(aux.name, aux.short).compact.first
|
51
|
+
return option(*args, **kargs, &block) unless opt
|
52
|
+
replace_in_options(opt, opt.merge(aux), override: override)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes options with short or name `keys` from options
|
56
|
+
def option_remove(*keys)
|
57
|
+
keys.map do |key|
|
58
|
+
aux = option_class.new(key, sample: true)
|
59
|
+
opt = options_hash.values_at(aux.name, aux.short).compact.first
|
60
|
+
delete_from_options(opt) if opt
|
61
|
+
end
|
32
62
|
end
|
33
63
|
|
34
64
|
# Allows to use a set of options
|
65
|
+
# @note it does a deep dup of each option.
|
66
|
+
# @param override [Boolean] wheter existing options with same option name
|
67
|
+
# should be overriden, may they clash
|
35
68
|
# @param options [Enumerable<RakeCommander::Option>]
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
add_to_options(opt)
|
69
|
+
def options_use(opts, override: true)
|
70
|
+
raise "Could not obtain list of RakeCommander::Option from #{opts.class}" unless opts = to_options(opts)
|
71
|
+
opts.each do |opt|
|
72
|
+
add_to_options(opt.deep_dup, override: override)
|
40
73
|
end
|
41
74
|
self
|
42
75
|
end
|
43
76
|
|
44
|
-
#
|
45
|
-
# @note
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
77
|
+
# It builds the `OptionParser` injecting the `middleware` block and parses `argv`
|
78
|
+
# @note this method is extended in via the following modules:
|
79
|
+
# 1. `RakeCommander::Options::Result`: makes the method to return a `Hash` with results,
|
80
|
+
# as well as captures/moves the **leftovers** to their own keyed argument.
|
81
|
+
# 2. `RakeCommander::Options:Error`: adds error handling (i.e. forward to rake commander errors)
|
82
|
+
# @param argv [Array<String>] the command line arguments to be parsed.
|
83
|
+
# @param method [Symbol] the parsing method (default is `:parse`; others: `:order`)
|
84
|
+
# @return [Array<String>] the **leftovers** of the `OptionParser#parse` call.
|
85
|
+
def parse_options(argv = ARGV, method: :parse, &middleware)
|
86
|
+
RakeCommander.rake_comm_debug "(#{name}) P A R S E O P T I O N S !", "\n", num: 5, pid: true
|
87
|
+
RakeCommander.rake_comm_debug " ---> ARGV: [#{argv.map {|a| a.nil?? "nil" : "'#{a}'"}.join(', ')}]"
|
88
|
+
options_parser(&middleware).send(method, argv)
|
51
89
|
end
|
52
90
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
return task_options_banner if respond_to?(:task_options_banner, true)
|
91
|
+
# List of configured options
|
92
|
+
# @return [Array<RakeCommander::Option>]
|
93
|
+
def options
|
94
|
+
options_hash.values.uniq
|
58
95
|
end
|
59
96
|
|
60
|
-
# @return [Boolean]
|
61
|
-
|
62
|
-
|
63
|
-
if value.nil?
|
64
|
-
@results_with_all_defaults || false
|
65
|
-
else
|
66
|
-
@results_with_all_defaults = !!value
|
67
|
-
end
|
97
|
+
# @return [Boolean] are there options defined?
|
98
|
+
def options?
|
99
|
+
!options.empty?
|
68
100
|
end
|
69
101
|
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
options_parser_with_results(middleware) do |options_parser|
|
75
|
-
argv = pre_parse_arguments(argv, options_hash)
|
76
|
-
leftovers.push(*options_parser.parse(argv))
|
77
|
-
rescue OptionParser::MissingArgument => e
|
78
|
-
raise RakeCommander::Options::MissingArgument, e, cause: nil
|
79
|
-
rescue OptionParser::InvalidArgument => e
|
80
|
-
error = RakeCommander::Options::InvalidArgument
|
81
|
-
error = error.new(e)
|
82
|
-
if (opt = options_hash[error.option_sym]) && opt.argument_required?
|
83
|
-
msg = "missing required argument: #{opt.name_hyphen} (#{opt.short_hyphen})"
|
84
|
-
raise RakeCommander::Options::MissingArgument, msg, cause: nil
|
85
|
-
else
|
86
|
-
raise error, e, cause: nil
|
87
|
-
end
|
88
|
-
end.tap do |results|
|
89
|
-
check_required_presence!(results)
|
90
|
-
end
|
102
|
+
# Clears all the options.
|
103
|
+
def clear_options!
|
104
|
+
@options_hash = {}
|
105
|
+
self
|
91
106
|
end
|
92
107
|
|
93
108
|
protected
|
94
109
|
|
110
|
+
# It allows to add a middleware block that is called during the parsing phase.
|
95
111
|
# @return [OptionParser] the built options parser.
|
96
112
|
def options_parser(&middleware)
|
97
113
|
new_options_parser do |opts|
|
98
114
|
opts.banner = banner if banner
|
99
|
-
|
115
|
+
option_help(opts)
|
116
|
+
free_shorts = implicit_shorts
|
117
|
+
|
118
|
+
options.each do |opt|
|
119
|
+
free_short = free_shorts.key?(opt.short_implicit)
|
120
|
+
opt.add_switch(opts, implicit_short: free_short, &middleware)
|
121
|
+
end
|
100
122
|
end
|
101
123
|
end
|
102
124
|
|
125
|
+
# @return [OptionParser]
|
103
126
|
def new_options_parser(&block)
|
104
127
|
require 'optparse'
|
105
128
|
OptionParser.new(&block)
|
106
129
|
end
|
107
130
|
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
131
|
+
# The options indexed by the short and the name (so doubled up in the hash).
|
132
|
+
# @param with_implicit [Boolean] whether free implicit shorts of declared options should be included
|
133
|
+
# among the keys (pointing to the specific option that has it implicit).
|
134
|
+
# @return [Hash] with Symbol name and shorts as keys, and `RakeCommander::Option` as values.
|
135
|
+
def options_hash(with_implicit: false)
|
136
|
+
@options_hash ||= {}
|
137
|
+
return @options_hash unless with_implicit
|
138
|
+
@options_hash.merge(implicit_shorts)
|
139
|
+
end
|
140
|
+
|
141
|
+
# This covers the gap where `OptionParser` auto-generates shorts out of option names.
|
142
|
+
# @note `OptionParser` implicitly generates a short for the option name. When defining
|
143
|
+
# an option that uses this short, the short gets overriden/reused by the explicit option.
|
144
|
+
# Otherwise, the short is actually available for the former option, regarldess you
|
145
|
+
# specified a different short for it (i.e. both shorts, expicit and implicit, will work).
|
146
|
+
# @note for two options with same implicit free short, latest in order will take it, which
|
147
|
+
# is what `OptionParser` will actually do.
|
148
|
+
# @return [Hash] with free shorts that are implicit to some option
|
149
|
+
def implicit_shorts
|
150
|
+
options.each_with_object({}) do |opt, implicit|
|
151
|
+
short = opt.short_implicit
|
152
|
+
implicit[short] = opt unless options_hash.key?(short)
|
119
153
|
end
|
120
154
|
end
|
121
155
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
156
|
+
private
|
157
|
+
|
158
|
+
# Explicitly installs the help of the options
|
159
|
+
# @note `OptionParser` already has `-h --help` as a native option.
|
160
|
+
# @param opts [OptionParser] where the help will be added.
|
161
|
+
def option_help(opts)
|
162
|
+
return false if options_hash.key?(:help) || options_hash.key?(:h)
|
163
|
+
option(:h, :help, 'Prints this help') do
|
164
|
+
puts opts
|
165
|
+
exit(0)
|
131
166
|
end
|
167
|
+
self
|
132
168
|
end
|
133
169
|
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
170
|
+
# @return [Array<RakeCommander::Option>]
|
171
|
+
def to_options(value)
|
172
|
+
if value.is_a?(Class) && value <= self
|
173
|
+
value.options
|
174
|
+
elsif value.is_a?(self)
|
175
|
+
value.class.options
|
176
|
+
elsif value.is_a?(Array)
|
177
|
+
value.select {|opt| opt.is_a?(option_class)}
|
178
|
+
elsif value.is_a?(Hash)
|
179
|
+
to_options(value.values)
|
180
|
+
elsif value.is_a?(Enumerable) || value.respond_to?(:to_a)
|
181
|
+
to_options(value.to_a)
|
141
182
|
end
|
142
|
-
raise RakeCommander::Options::MissingOption.new(missing) unless missing.empty?
|
143
183
|
end
|
144
184
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
185
|
+
# Adds to `@options_hash` the option `opt`
|
186
|
+
# @todo add `exception` parameter, to trigger an exception
|
187
|
+
# when `opt` name or short are taken (and override is `false`)
|
188
|
+
# @todo allow to specif if `:tail`, `:top` or `:base` (default)
|
189
|
+
# @param opt [RakeCommander::Option] the option to be added.
|
190
|
+
# @param override [Boolean] whether we should continue, may this action override (an)other option(s).
|
191
|
+
# @return [RakeCommander::Option, NilClass] the option that was added, `nil` is returned otherwise.
|
192
|
+
def add_to_options(opt, override: true)
|
193
|
+
name_ref = respond_to?(:name)? " (#{name})" : ''
|
194
|
+
if sprev = options_hash[opt.short]
|
195
|
+
return nil unless override
|
196
|
+
puts "Warning#{name_ref}: Overriding option '#{sprev.name}' with short '#{sprev.short}' ('#{opt.name}')"
|
197
|
+
delete_from_options(sprev)
|
150
198
|
end
|
151
|
-
if
|
152
|
-
|
153
|
-
|
154
|
-
|
199
|
+
if nprev = options_hash[opt.name]
|
200
|
+
return nil unless override
|
201
|
+
puts "Warning#{name_ref}: Overriding option '#{nprev.short}' with name '#{nprev.name}' ('#{opt.short}')"
|
202
|
+
delete_from_options(nprev)
|
155
203
|
end
|
156
204
|
options_hash[opt.name] = options_hash[opt.short] = opt
|
157
205
|
end
|
158
|
-
end
|
159
206
|
|
160
|
-
|
161
|
-
@
|
162
|
-
|
207
|
+
# Deletes an option from the `options_hash`
|
208
|
+
# @param opt [RakeCommander::Option] the option to be deleted.
|
209
|
+
# @return [RakeCommander::Option, NilClass] the option that was deleted, or `nil` otherwise.
|
210
|
+
def delete_from_options(opt)
|
211
|
+
res = options_hash.delete(opt.short)
|
212
|
+
options_hash.delete(opt.name) || res
|
213
|
+
end
|
214
|
+
|
215
|
+
# Replaces option `ref` with option `opt`.
|
216
|
+
# @note this method was added aiming to keep the same position for an option we override.
|
217
|
+
# @param ref [RakeCommander::Option] the option to be replaced.
|
218
|
+
# @param opt [RakeCommander::Option] the option that will replace `ref`.
|
219
|
+
# @param override [Boolean] whether we should continue, may there be collateral override to other options.
|
220
|
+
# @return [RakeCommander::Option, NilClass] `opt` if it was added, or `nil` otherwise.
|
221
|
+
def replace_in_options(ref, opt, override: false)
|
222
|
+
# Try to keep the same potition
|
223
|
+
options_hash[ref.short] = options_hash[ref.name] = nil
|
224
|
+
add_to_options(opt, override: override).tap do |added_opt|
|
225
|
+
# restore previous status
|
226
|
+
next options_hash[ref.short] = options_hash[ref.name] = ref unless added_opt
|
227
|
+
delete_empty_keys(options_hash)
|
228
|
+
end
|
229
|
+
end
|
163
230
|
|
164
|
-
|
165
|
-
|
231
|
+
# Deletes all keys that have `nil` as value
|
232
|
+
def delete_empty_keys(hash)
|
233
|
+
hash.tap do |_h|
|
234
|
+
hash.dup.each do |k, v|
|
235
|
+
next unless v.nil?
|
236
|
+
hash.delete(k)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
166
240
|
end
|
167
241
|
end
|
168
242
|
end
|
169
243
|
|
170
|
-
require_relative 'options/error'
|
171
244
|
require_relative 'options/set'
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Patching `rake`
|
2
|
+
|
3
|
+
## Patch Rational
|
4
|
+
|
5
|
+
Let's say that when we invoke `rake` from the command line, `rake-commander` is loaded from a `Rakefile` (i.e. `require 'rake-commander'`). Looking at the `Rake::Application#run` method code, this places the patch moment, at the best, during the load of the `Rakefile`; during execution of the `load_rakefile` private method ([here is the call](https://github.com/ruby/rake/blob/48e798484babf725b0562cc417986da513e5d0ae/lib/rake/application.rb#L82)).
|
6
|
+
|
7
|
+
### Challenges encountered with the `rake` executable
|
8
|
+
|
9
|
+
Let's say you require/load `rake-commander` in a `Rakefile`, and invoke the [`rake` executable](https://github.com/ruby/rake/blob/master/exe/rake). By the time rake commander is loaded, `Rake` has already captured the `ARGV`, parsed its own options, and pre-parsed possible task(s) invokations.
|
10
|
+
|
11
|
+
The **two main problems** to deal with are:
|
12
|
+
|
13
|
+
1. Rake has it's own `OptionsParser`. If any of your rake `task` options matches any of those, you will be unintentionally invoking `rake` functionality.
|
14
|
+
2. Let's say you require/load `rake-commander` in a `Rakefile`. By the time rake commander is loaded, `rake` has already collected as `top_level_tasks` the arguments of your task options; so those that do not start with dash `-` ([see private method `collect_command_line_tasks` in `Rake::Application`](https://github.com/ruby/rake/blob/48e798484babf725b0562cc417986da513e5d0ae/lib/rake/application.rb#L782)).
|
15
|
+
|
16
|
+
This is also true when you invoke `rake` via _shell_ from within another task.
|
17
|
+
|
18
|
+
**Example**
|
19
|
+
|
20
|
+
Without the current patch, this is what was happening.
|
21
|
+
|
22
|
+
```
|
23
|
+
$ raked examples:chainer -- --chain --say "Just saying..." --with raked
|
24
|
+
Calling --> 'rake examples:chained -- --say "Just saying..."'
|
25
|
+
Chained task has been called!!
|
26
|
+
Just saying...
|
27
|
+
rake aborted!
|
28
|
+
Don't know how to build task 'Just saying...' (See the list of available tasks with `rake --tasks`)
|
29
|
+
```
|
30
|
+
|
31
|
+
### The alternative of a `raked` executable
|
32
|
+
|
33
|
+
**`raked` executable is not necessary and is not provided for prod environments. The current patch allows to start directly from `rake`**.
|
34
|
+
|
35
|
+
* This has been kept to the only purpose of documentation.
|
36
|
+
|
37
|
+
The `raked` executable would be a modified version of the `rake` executable, where `rake_commander` is loaded right after requiring `rake` and before `Rake.application.run` is invoked.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
#!/usr/bin/env ruby
|
41
|
+
|
42
|
+
require "rake"
|
43
|
+
require "rake-commander"
|
44
|
+
Rake.application.run
|
45
|
+
```
|
46
|
+
|
47
|
+
This would allow the patch to be active right at the beginning, preventing this way the patch to kick in after the `rake` application has been firstly launched (it saves to rake one loop of parsing arguments and loading rake files).
|
48
|
+
|
49
|
+
```
|
50
|
+
$ raked examples:chainer -- --chain --say "Just saying..." --with raked
|
51
|
+
Calling --> 'bin\raked examples:chained -- --say "Just saying..."'
|
52
|
+
Chained task has been called!!
|
53
|
+
Just saying...
|
54
|
+
```
|
55
|
+
|
56
|
+
Using `raked` as separate namespace vs `rake` is completely optional. Most will prefer to keep on just with the main `rake` executable and `rake-commander` as enhancement to it. This is the rational behind the second patch (explained in detail in the next section).
|
57
|
+
|
58
|
+
|
59
|
+
## Reload `Rake` application
|
60
|
+
|
61
|
+
The target is to be able to use `rake` indistinctly (rather than having to rewrite rake commands as `raked`). Unfortunately the **only way around** to the _application-has-started_ is to just **relaunch/reload the application** when the patch kicks in (wouldn't know how to and shouldn't try to reuse the current running application: i.e. task options parsed as rake option modifiers that have already done some stuff).
|
62
|
+
|
63
|
+
Fortunately, the target of `rake-commander` is just to **enhance** existing syntax, which gives a very specific target when it comes to **patching**. The key factor to reach a clean patch is to design the syntax in a fixed way where there is no much flexibility but clearly stated delimiters (i.e. no fancy guessing where dependencies are introduced on defined task options).
|
64
|
+
|
65
|
+
Relaunching the application to a new instance requires very little:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
Rake.application = Rake::Application.new
|
69
|
+
Rake.application.run
|
70
|
+
exit(0) # abort previous application run
|
71
|
+
```
|
72
|
+
|
73
|
+
## Missing tasks on reload
|
74
|
+
|
75
|
+
Relaunching the `rake` application entails issues with `require` in the chain of `Rakefile` files that have already been loaded. Apparently some tasks of some gems are installed during the `require` runtime, rather than explicitly declaring them in the rake file.
|
76
|
+
|
77
|
+
This is the case for `bundler/gem_tasks` (i.e. `require "bundler/gem_tasks"`), where all these `tasks` will be missing: build, build:checksum, clean, clobber, install, install:local, release, release:guard_clean, release:rubygem_push, release:source_control_push.
|
78
|
+
|
79
|
+
It can potentially be looked at, if ever this shows up to new review.
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Patcher
|
3
|
+
module Application
|
4
|
+
module RunMethod
|
5
|
+
include RakeCommander::Patcher::Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def target
|
9
|
+
Rake::Application
|
10
|
+
end
|
11
|
+
|
12
|
+
def patch_prepend(_invoked_by)
|
13
|
+
return unless target_defined?
|
14
|
+
Rake::Application.prepend Patch
|
15
|
+
end
|
16
|
+
|
17
|
+
def target_defined?
|
18
|
+
return true if defined?(target)
|
19
|
+
puts "Warning (#{self}): undefined target #{target}"
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Patch
|
25
|
+
include RakeCommander::Patcher::Debug
|
26
|
+
|
27
|
+
# To extend the command line syntax we need to patch `Rake`, provided that
|
28
|
+
# this gem's extended options are not in `argv` when `Rake` processes it.
|
29
|
+
# @note we define an instance variable so we can know if the patch was applied when it started.
|
30
|
+
# @note This patch only works fine if `Rake::Application#run` is **invoked after****
|
31
|
+
# **`RakeCommander` has been required**.
|
32
|
+
# * So by itself alone it allows to use `raked` executable that this gem provides.
|
33
|
+
def run(argv = ARGV)
|
34
|
+
@rake_commander_run_argv_patch = true unless instance_variable_defined?(:@rake_commander_run_argv_patch)
|
35
|
+
RakeCommander.self_load
|
36
|
+
rake_comm_debug "R U N !", "\n", num: 1, pid: true
|
37
|
+
rake_comm_debug " ---> Command: #{$PROGRAM_NAME}"
|
38
|
+
rake_comm_debug " ---> ARGV: [#{argv.map {|a| "'#{a}'"}.join(', ')}]"
|
39
|
+
argv = RakeCommander.argv_rake_native_arguments(argv)
|
40
|
+
super(argv)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Patcher
|
3
|
+
module Application
|
4
|
+
module TopLevelMethod
|
5
|
+
include RakeCommander::Patcher::Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def target
|
9
|
+
Rake::Application
|
10
|
+
end
|
11
|
+
|
12
|
+
def patch_prepend(_invoked_by)
|
13
|
+
return unless target_defined?
|
14
|
+
Rake::Application.prepend Patch
|
15
|
+
end
|
16
|
+
|
17
|
+
def target_defined?
|
18
|
+
return true if defined?(target)
|
19
|
+
puts "Warning (#{self}): undefined target #{target}"
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Patch
|
25
|
+
include RakeCommander::Patcher::Debug
|
26
|
+
|
27
|
+
# To preserve `rake` as main executable, as the `RunMethod::Patch` is applied only
|
28
|
+
# when `Rake::Application` requires the `Rakefile` that loads `rake-commander`,
|
29
|
+
# we need to:
|
30
|
+
# 1. Intercept the execution on the next stage of the `Rake::Application#run` command,
|
31
|
+
# [the `top_level` call](https://github.com/ruby/rake/blob/48e798484babf725b0562cc417986da513e5d0ae/lib/rake/application.rb#L82),
|
32
|
+
# and **re-launch** the rake application (so it only receives the `ARGV` cut that the main patch provides)
|
33
|
+
# 2. Ensure that **re-launch** is done only once.
|
34
|
+
# 3. Ensure that it does `exit(0)` to the original running application.
|
35
|
+
def top_level
|
36
|
+
unless @rake_commander_run_argv_patch
|
37
|
+
@rake_commander_run_argv_patch = true
|
38
|
+
RakeCommander.relaunch_rake_application
|
39
|
+
# Should not reach this point
|
40
|
+
end
|
41
|
+
rake_comm_debug "T O P L E V E L ( p a t c h a c t i v e )", "\n", num: 2, pid: true
|
42
|
+
rake_comm_debug " ---> Known tasks: #{tasks.map(&:name).join(", ")}"
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
include RakeCommander::Patcher::Debug
|
49
|
+
|
50
|
+
# Reloading `Rakefile` has drawbacks around `require` only being launched once per
|
51
|
+
# dependency. Apparently some tasks of some gems are installed at `require` run-time.
|
52
|
+
# This requires to keep known tasks when we switch the application.
|
53
|
+
def relaunch_rake_application
|
54
|
+
prev_rake_app = Rake.application
|
55
|
+
rake_comm_debug "R A K E R E L A U N C H ( p a t c h i n a c t i v e )", "\n", num: 2, pid: true
|
56
|
+
rake_comm_debug " ---> Known tasks: #{prev_rake_app.tasks.map(&:name).join(", ")}"
|
57
|
+
Rake.application = Rake::Application.new
|
58
|
+
rake_comm_debug "N e w R a k e A p p", "\n", num: 4, pid: true
|
59
|
+
RakeCommander.self_load_reset
|
60
|
+
Rake.application.run #RakeCommander.argv_rake_native_arguments(ARGV)
|
61
|
+
rake_comm_debug "T e r m i n a t i n g R U N", "\n", num: 3, pid: true
|
62
|
+
exit(0)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def rake_reparse_argv(argv = ARGV)
|
68
|
+
RakeCommander.argv_rake_native_arguments(argv)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Patcher
|
3
|
+
module Application
|
4
|
+
include RakeCommander::Patcher::Base
|
5
|
+
require_relative 'application/run_method'
|
6
|
+
require_relative 'application/top_level_method'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def patch_include(base)
|
10
|
+
base.send :include, RunMethod
|
11
|
+
base.send :include, TopLevelMethod
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Patcher
|
3
|
+
# Base of self-applied patchers.
|
4
|
+
# @note a patcher will be applied when it's included.
|
5
|
+
module Base
|
6
|
+
class << self
|
7
|
+
def included(base)
|
8
|
+
super(base)
|
9
|
+
base.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def included(base)
|
15
|
+
super(base)
|
16
|
+
base.extend self::ClassMethods if defined?(self::ClassMethods)
|
17
|
+
invoke_patch_methods!(base) unless self == RakeCommander::Patcher::Base
|
18
|
+
end
|
19
|
+
|
20
|
+
def invoke_patch_methods!(base)
|
21
|
+
raise "#{self}: no patch methods. Patching with include requires at least one." unless any_patch_method?
|
22
|
+
patch_prepend(base) if patch_prepend?
|
23
|
+
patch_include(base) if patch_include?
|
24
|
+
patch_extend(base) if patch_extend?
|
25
|
+
end
|
26
|
+
|
27
|
+
def any_patch_method?
|
28
|
+
patch_prepend? || patch_include? || patch_extend?
|
29
|
+
end
|
30
|
+
|
31
|
+
def patch_prepend?
|
32
|
+
respond_to?(:patch_prepend)
|
33
|
+
end
|
34
|
+
|
35
|
+
def patch_include?
|
36
|
+
respond_to?(:patch_include)
|
37
|
+
end
|
38
|
+
|
39
|
+
def patch_extend?
|
40
|
+
respond_to?(:patch_extend)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Patcher
|
3
|
+
# Helpers to patch
|
4
|
+
module Debug
|
5
|
+
# Helper for debugging
|
6
|
+
def rake_comm_debug(msg, prefix = '', num: nil, pid: false)
|
7
|
+
return unless RakeCommander::Patcher.debug?
|
8
|
+
rake_comm_debug_random_object_id
|
9
|
+
num = num ? "#{num}. " : nil
|
10
|
+
if pid
|
11
|
+
meta = "(PID: #{Process.pid} ++ Thread: #{Thread.current.object_id} ++ Ruby 'main': #{rake_comm_debug_main_object_id})"
|
12
|
+
msg = "#{prefix}( #{num}#{Rake.application.object_id}) #{msg} #{meta}"
|
13
|
+
elsif num
|
14
|
+
msg = "#{prefix}( #{num}) #{msg} "
|
15
|
+
end
|
16
|
+
puts msg
|
17
|
+
end
|
18
|
+
|
19
|
+
def rake_comm_debug_main_object_id
|
20
|
+
eval('self.object_id', TOPLEVEL_BINDING, __FILE__, __LINE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rake_comm_debug_random_object_id
|
24
|
+
return false if @rake_comm_debug_random_object_id
|
25
|
+
@rake_comm_debug_random_object_id = Array(1..20).sample.times.map do |i|
|
26
|
+
i.to_s.tap(&:object_id)
|
27
|
+
end
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|