rc 0.2.0 → 0.3.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.
- data/.index +66 -0
- data/.rubyrc +88 -0
- data/.yardopts +8 -0
- data/HISTORY.md +40 -0
- data/LICENSE.txt +27 -0
- data/README.md +194 -0
- data/demo/00_concept.md +23 -0
- data/demo/01_config.md +14 -0
- data/demo/02_configuration.md +51 -0
- data/demo/03_import.md +48 -0
- data/demo/06_interface.md +39 -0
- data/demo/applique/ae.rb +1 -0
- data/demo/applique/file.rb +8 -0
- data/demo/applique/fixture.rb +10 -0
- data/demo/applique/fixture/.ruby +11 -0
- data/demo/cov.rb +7 -0
- data/lib/c.rb +8 -0
- data/lib/rc.rb +10 -0
- data/lib/rc/api.rb +4 -0
- data/lib/rc/config.rb +305 -0
- data/lib/rc/config_filter.rb +110 -0
- data/lib/rc/configuration.rb +362 -0
- data/lib/rc/constants.rb +9 -0
- data/lib/rc/core_ext.rb +6 -0
- data/lib/rc/core_ext/argv.rb +22 -0
- data/lib/rc/core_ext/hash.rb +17 -0
- data/lib/rc/core_ext/kernel.rb +38 -0
- data/lib/rc/core_ext/string.rb +20 -0
- data/lib/rc/core_ext/symbol.rb +8 -0
- data/lib/rc/dsl.rb +72 -0
- data/lib/rc/interface.rb +338 -0
- data/lib/rc/properties.rb +108 -0
- data/lib/rc/required.rb +10 -0
- data/lib/rc/setup.rb +57 -0
- data/lib/rc/tweaks/rake.rb +31 -0
- metadata +118 -22
data/lib/rc/constants.rb
ADDED
data/lib/rc/core_ext.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#
|
2
|
+
# @deprecated Add to Ruby Facets ?
|
3
|
+
#
|
4
|
+
def ARGV.env(*switches)
|
5
|
+
mapping = (Hash === switches.last ? swithes.pop : {})
|
6
|
+
|
7
|
+
switches.each do |s|
|
8
|
+
mapping[s] = s.to_s.sub(/^[-]+/,'')
|
9
|
+
end
|
10
|
+
|
11
|
+
mapping.each do |switch, envar|
|
12
|
+
if index = ARGV.index(switch)
|
13
|
+
ENV[envar] = ARGV[index+1]
|
14
|
+
elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ }
|
15
|
+
value = $1
|
16
|
+
value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
|
17
|
+
value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
|
18
|
+
ENV[envar] = value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#require 'finder/import'
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
#
|
8
|
+
# Alias original Kernel#require method.
|
9
|
+
#
|
10
|
+
alias_method :require_without_rc, :require
|
11
|
+
|
12
|
+
#
|
13
|
+
# Redefine Kernel#require with callback.
|
14
|
+
#
|
15
|
+
def require(feature, options=nil)
|
16
|
+
result = require_without_rc(feature)
|
17
|
+
RC.required(feature) if result
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
#
|
23
|
+
# Alias original Kernel.require method.
|
24
|
+
#
|
25
|
+
alias_method :require_without_rc, :require
|
26
|
+
|
27
|
+
#
|
28
|
+
# Redefine Kernel.require with callback.
|
29
|
+
#
|
30
|
+
def require(feature)
|
31
|
+
result = require_without_rc(feature)
|
32
|
+
RC.required(feature) if result
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def tabto(n)
|
4
|
+
if self =~ /^( *)\S/
|
5
|
+
indent(n - $1.length)
|
6
|
+
else
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end unless method_defined?(:tabto)
|
10
|
+
|
11
|
+
def indent(n, c=' ')
|
12
|
+
if n >= 0
|
13
|
+
gsub(/^/, c * n)
|
14
|
+
else
|
15
|
+
gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "")
|
16
|
+
end
|
17
|
+
end unless method_defined?(:indent)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
data/lib/rc/dsl.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module RC
|
2
|
+
|
3
|
+
# Configuration's DSL
|
4
|
+
#
|
5
|
+
class DSL < Module
|
6
|
+
|
7
|
+
#
|
8
|
+
#
|
9
|
+
#
|
10
|
+
def initialize(configuration)
|
11
|
+
@configuration = configuration
|
12
|
+
@_options = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
def import(glob, opts={})
|
17
|
+
@configuration.import(glob, *opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
#
|
22
|
+
#
|
23
|
+
#def profile(name, &block)
|
24
|
+
# raise SyntaxError, "nested profile sections" if @_options[:profile]
|
25
|
+
# @_options[:profile] = name.to_s
|
26
|
+
# instance_eval(&block)
|
27
|
+
# @_options.delete(:profile)
|
28
|
+
#end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Profile block.
|
32
|
+
#
|
33
|
+
# @param [String,Symbol] name
|
34
|
+
# A profile name.
|
35
|
+
#
|
36
|
+
def profile(name, state={}, &block)
|
37
|
+
raise SyntaxError, "nested profile sections" if @_options[:profile]
|
38
|
+
original_state = @_options.dup
|
39
|
+
@_options.update(state)
|
40
|
+
@_options[:profile] = name.to_s
|
41
|
+
|
42
|
+
instance_eval(&block)
|
43
|
+
|
44
|
+
@_options = original_state
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
#
|
49
|
+
def config(command=nil, options={}, &block)
|
50
|
+
nested_keys = @_options.keys & options.keys.map{|k| k.to_sym}
|
51
|
+
raise ArgumentError, "nested #{nested_keys.join(', ')}" unless nested_keys.empty?
|
52
|
+
|
53
|
+
options = @_options.merge(options)
|
54
|
+
|
55
|
+
@configuration.config(command, options, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
#
|
60
|
+
def onload(feature, options={}, &block)
|
61
|
+
nested_keys = @_options.keys & options.keys.map{|k| k.to_sym}
|
62
|
+
raise ArgumentError, "nested #{nested_keys.join(', ')}" unless nested_keys.empty?
|
63
|
+
|
64
|
+
options = @_options.merge(options)
|
65
|
+
options[:onload] = true
|
66
|
+
|
67
|
+
@configuration.config(feature, options, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/rc/interface.rb
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
module RC
|
2
|
+
|
3
|
+
# External requirements.
|
4
|
+
require 'yaml'
|
5
|
+
require 'finder'
|
6
|
+
#require 'loaded'
|
7
|
+
|
8
|
+
# Internal requirements.
|
9
|
+
require 'rc/constants'
|
10
|
+
require 'rc/required'
|
11
|
+
require 'rc/core_ext'
|
12
|
+
require 'rc/config'
|
13
|
+
require 'rc/configuration'
|
14
|
+
require 'rc/dsl'
|
15
|
+
#require 'rc/config_filter'
|
16
|
+
require 'rc/properties'
|
17
|
+
require 'rc/setup'
|
18
|
+
|
19
|
+
# The Interface module extends the RC module.
|
20
|
+
#
|
21
|
+
# A tool can control RC configuration by loading `rc/api` and calling the
|
22
|
+
# `RC.configure` method with a block that handles the configuration
|
23
|
+
# for the feature as provided by a project's config file.
|
24
|
+
#
|
25
|
+
# The block will often need to be conditioned on the current profile and/or the
|
26
|
+
# the current command. This is easy enough to do with #profile? and #command?
|
27
|
+
# methods.
|
28
|
+
#
|
29
|
+
# For example, is RSpec wanted to support RC out-of-the-box, the code would
|
30
|
+
# look something like:
|
31
|
+
#
|
32
|
+
# require 'rc/api'
|
33
|
+
#
|
34
|
+
# RC.configure('rspec') do |config|
|
35
|
+
# if config.profile?
|
36
|
+
# RSpec.configure(&config)
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
module Interface
|
41
|
+
|
42
|
+
#
|
43
|
+
# The tweaks directory is where special augementation script reside
|
44
|
+
# the are used to adjust behavior of certain popular tools to work
|
45
|
+
# with RC that would not otherwise do so.
|
46
|
+
#
|
47
|
+
TWEAKS_DIR = File.dirname(__FILE__) + '/tweaks'
|
48
|
+
|
49
|
+
#
|
50
|
+
# Library configuration cache. Since configuration can be imported from
|
51
|
+
# other libraries, we keep a cache for each library.
|
52
|
+
#
|
53
|
+
# @return [Hash]
|
54
|
+
#
|
55
|
+
def cache
|
56
|
+
@cache ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Clear the library configuration cache. This is mostly used
|
61
|
+
# for testing.
|
62
|
+
#
|
63
|
+
def clear!
|
64
|
+
@cache = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Load library configuration for a given +gem+. If no +gem+ is
|
69
|
+
# specified then the current project's configuration is used.
|
70
|
+
#
|
71
|
+
# @return [Configuration]
|
72
|
+
#
|
73
|
+
def configuration(gem=nil)
|
74
|
+
key = gem ? gem.to_s : nil #Dir.pwd
|
75
|
+
cache[key] ||= Configuration.load(:from=>gem)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Return a list of names of defined profiles for a given +tool+.
|
80
|
+
#
|
81
|
+
# @param [#to_sym] tool
|
82
|
+
# Tool for which lookup defined profiles. If none given
|
83
|
+
# the current tool is used.
|
84
|
+
#
|
85
|
+
# @param [Hash] opts
|
86
|
+
# Options for looking up profiles.
|
87
|
+
#
|
88
|
+
# @option opts [#to_s] :gem
|
89
|
+
# Name of library from which to load the configuration.
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# profile_names(:qed)
|
93
|
+
#
|
94
|
+
def profile_names(tool=nil, opts={})
|
95
|
+
if Hash === tool
|
96
|
+
opts, tool = tool, nil
|
97
|
+
end
|
98
|
+
|
99
|
+
tool = tool || current_tool
|
100
|
+
gem = opts[:from]
|
101
|
+
|
102
|
+
configuration(gem).profile_names(tool)
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Get current tool.
|
107
|
+
#
|
108
|
+
# @todo Not so sure `ENV['tool']` is a good idea.
|
109
|
+
#
|
110
|
+
def current_tool
|
111
|
+
File.basename(ENV['tool'] || $0)
|
112
|
+
end
|
113
|
+
|
114
|
+
alias current_command current_tool
|
115
|
+
|
116
|
+
#
|
117
|
+
# Set current tool.
|
118
|
+
#
|
119
|
+
def current_tool=(tool)
|
120
|
+
ENV['tool'] = tool.to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
alias current_command= current_tool=
|
124
|
+
|
125
|
+
#
|
126
|
+
# Get current profile.
|
127
|
+
#
|
128
|
+
def current_profile
|
129
|
+
ENV['profile'] || ENV['p'] || 'default'
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Set current profile.
|
134
|
+
#
|
135
|
+
def current_profile=(profile)
|
136
|
+
if profile
|
137
|
+
ENV['profile'] = profile.to_s
|
138
|
+
else
|
139
|
+
ENV['profile'] = nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Properties of the current project. These can be used in a project's config file
|
145
|
+
# to make configuration more interchangeable. Presently project properties are
|
146
|
+
# gathered from .index YAML or .gemspec.
|
147
|
+
#
|
148
|
+
# It's important to note that properties are not per-gem. Rather they are global
|
149
|
+
# and belong only the current project.
|
150
|
+
#
|
151
|
+
def properties
|
152
|
+
$properties ||= Properties.new
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Remove a configuration setup.
|
157
|
+
#
|
158
|
+
# NOTE: This is probably a YAGNI.
|
159
|
+
#
|
160
|
+
def unconfigure(tool)
|
161
|
+
@setup[tool.to_s] = false
|
162
|
+
end
|
163
|
+
|
164
|
+
alias :unset :unconfigure
|
165
|
+
|
166
|
+
#
|
167
|
+
# Define a custom configuration handler.
|
168
|
+
#
|
169
|
+
# If the current tool matches the given tool, and autoconfiguration is not being used,
|
170
|
+
# then configuration is applied immediately.
|
171
|
+
#
|
172
|
+
def configure(tool, options={}, &block)
|
173
|
+
tool = tool.to_s
|
174
|
+
|
175
|
+
@setup ||= {}
|
176
|
+
|
177
|
+
if block
|
178
|
+
@setup[tool] = Setup.new(tool, options, &block)
|
179
|
+
|
180
|
+
if tool == current_tool
|
181
|
+
configure_tool(tool) unless autoconfig?
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
@setup[tool]
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Original name of `#configure`.
|
190
|
+
#
|
191
|
+
def setup(tool, options={}, &block)
|
192
|
+
configure(tool, options, &block)
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# Set current profile via ARGV switch. This is done immediately,
|
197
|
+
# setting `ENV['profile']` to the switch value if this setup is
|
198
|
+
# for the current commandline tool. The reason it is done immediately,
|
199
|
+
# rather than assigning it in bootstrap, is b/c option parsers somtimes
|
200
|
+
# consume ARGV as they parse it, and by then it would too late.
|
201
|
+
#
|
202
|
+
# @example
|
203
|
+
# RC.profile_switch('qed', '-p', '--profile')
|
204
|
+
#
|
205
|
+
def profile_switch(command, *switches)
|
206
|
+
return unless command.to_s == RC.current_command
|
207
|
+
|
208
|
+
switches.each do |switch, envar|
|
209
|
+
if index = ARGV.index(switch)
|
210
|
+
self.current_profile = ARGV[index+1]
|
211
|
+
elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ }
|
212
|
+
value = $1
|
213
|
+
value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
|
214
|
+
value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
|
215
|
+
self.currrent_profile = value
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# Set enviroment variable(s) to command line switch value(s). This is a more general
|
222
|
+
# form of #profile_switch and will probably not get much use in this context.
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
# RC.switch('qed', '-p'=>'profile', '--profile'=>'profile')
|
226
|
+
#
|
227
|
+
def switch(command, switches={})
|
228
|
+
return unless command.to_s == RC.current_command
|
229
|
+
|
230
|
+
switches.each do |switch, envar|
|
231
|
+
if index = ARGV.index(switch)
|
232
|
+
ENV[envar] = ARGV[index+1]
|
233
|
+
elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ }
|
234
|
+
value = $1
|
235
|
+
value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
|
236
|
+
value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
|
237
|
+
ENV[envar] = value
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
#
|
244
|
+
#
|
245
|
+
def autoconfig?
|
246
|
+
@autoconfigure
|
247
|
+
end
|
248
|
+
|
249
|
+
protected
|
250
|
+
|
251
|
+
#
|
252
|
+
#
|
253
|
+
#
|
254
|
+
def autoconfigure
|
255
|
+
@autoconfig = true
|
256
|
+
configure_tool(current_tool)
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
#
|
262
|
+
# Configure current commnad.
|
263
|
+
#
|
264
|
+
def configure_tool(tool)
|
265
|
+
tweak(tool)
|
266
|
+
|
267
|
+
configs = RC.configuration[tool]
|
268
|
+
|
269
|
+
return unless configs
|
270
|
+
|
271
|
+
configs.each do |config|
|
272
|
+
next unless config.apply_to_tool?
|
273
|
+
config.require_feature if autoconfig?
|
274
|
+
setup = setup(tool)
|
275
|
+
next if setup == false # deactivated
|
276
|
+
setup ? setup.call(config) : config.call
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
#
|
281
|
+
# Setup the system.
|
282
|
+
#
|
283
|
+
def bootstrap
|
284
|
+
@bootstrap ||= (
|
285
|
+
properties # prime global properties
|
286
|
+
bootstrap_require
|
287
|
+
true
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
291
|
+
#
|
292
|
+
# Tap into require via loaded hook. The hook is only
|
293
|
+
# triggered on #require, not #load.
|
294
|
+
#
|
295
|
+
def bootstrap_require
|
296
|
+
def Kernel.required(feature)
|
297
|
+
config = RC.configuration[feature]
|
298
|
+
if config
|
299
|
+
config.each do |config|
|
300
|
+
next unless config.apply_to_feature?
|
301
|
+
config.call
|
302
|
+
end
|
303
|
+
end
|
304
|
+
super(feature) if defined?(super)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#
|
309
|
+
#
|
310
|
+
#
|
311
|
+
def tweak(command)
|
312
|
+
tweak = File.join(TWEAKS_DIR, command + '.rb')
|
313
|
+
if File.exist?(tweak)
|
314
|
+
require tweak
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
##
|
319
|
+
## IDEA: Preconfigurations occur before other command configs and
|
320
|
+
## do not require feature.
|
321
|
+
##
|
322
|
+
#def preconfigure(options={})
|
323
|
+
# tool = options[:tool] || current_tool
|
324
|
+
# profile = options[:profile] || current_profile
|
325
|
+
#
|
326
|
+
# preconfiguration.each do |c|
|
327
|
+
# c.call if c.match?(tool, profile)
|
328
|
+
# end
|
329
|
+
#end
|
330
|
+
end
|
331
|
+
|
332
|
+
# The Interface extends RC module.
|
333
|
+
extend Interface
|
334
|
+
|
335
|
+
# Prep the system.
|
336
|
+
bootstrap
|
337
|
+
|
338
|
+
end
|