rc 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ module RC
2
+
3
+ #
4
+ # Looking for a config file relative to root of a project,
5
+ # these are the files considered to indicate the root directory.
6
+ #
7
+ ROOT_INDICATORS = %w{.git .hg _darcs .index .rc .ruby}
8
+
9
+ end
@@ -0,0 +1,6 @@
1
+ require 'rc/core_ext/argv'
2
+ require 'rc/core_ext/hash'
3
+ require 'rc/core_ext/kernel'
4
+ require 'rc/core_ext/string'
5
+ require 'rc/core_ext/symbol'
6
+
@@ -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,17 @@
1
+ class Hash
2
+
3
+ def to_h
4
+ dup #rehash
5
+ end unless method_defined?(:to_h)
6
+
7
+ #def rekey(&block)
8
+ # h = {}
9
+ # each do |k,v|
10
+ # nk = block.call(k)
11
+ # h[nk] = v
12
+ # end
13
+ # h
14
+ #end unless method_defined?(:rekey)
15
+
16
+ end
17
+
@@ -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
+
@@ -0,0 +1,8 @@
1
+ #class Symbol
2
+ #
3
+ # def /(other)
4
+ # "#{self}/#{other}".to_sym
5
+ # end
6
+ #
7
+ #end
8
+
@@ -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
@@ -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