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.
@@ -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