courtier 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ module Kernel
2
+ #
3
+ # Evaluate script directly into current scope.
4
+ #
5
+ def import(feature)
6
+ file = Find.load_path(feature).first
7
+ raise LoadError, "no such file -- #{feature}" unless file
8
+ instance_eval(::File.read(file), file) if file
9
+ end
10
+
11
+ #
12
+ # Evaluate script directly into current scope.
13
+ #
14
+ def import_relative(file)
15
+ raise LoadError, "no such file -- #{file}" unless File.file?(file)
16
+ instance_eval(::File.read(file), file) if file
17
+ end
18
+ end
19
+
20
+ class Hash
21
+ def to_h
22
+ dup #rehash
23
+ end unless method_defined?(:to_h)
24
+
25
+ #def rekey(&block)
26
+ # h = {}
27
+ # each do |k,v|
28
+ # nk = block.call(k)
29
+ # h[nk] = v
30
+ # end
31
+ # h
32
+ #end unless method_defined?(:rekey)
33
+ end
34
+
35
+ class String
36
+ def tabto(n)
37
+ if self =~ /^( *)\S/
38
+ indent(n - $1.length)
39
+ else
40
+ self
41
+ end
42
+ end unless method_defined?(:tabto)
43
+
44
+ def indent(n, c=' ')
45
+ if n >= 0
46
+ gsub(/^/, c * n)
47
+ else
48
+ gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "")
49
+ end
50
+ end unless method_defined?(:indent)
51
+ end
52
+
53
+ #class Symbol
54
+ # def /(other)
55
+ # "#{self}/#{other}".to_sym
56
+ # end
57
+ #end
58
+
59
+ # @deprecated Add to Ruby Facets ?
60
+ def ARGV.env(*switches)
61
+ mapping = (Hash === switches.last ? swithes.pop : {})
62
+
63
+ switches.each do |s|
64
+ mapping[s] = s.to_s.sub(/^[-]+/,'')
65
+ end
66
+
67
+ mapping.each do |switch, envar|
68
+ if index = ARGV.index(switch)
69
+ ENV[envar] = ARGV[index+1]
70
+ elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ }
71
+ value = $1
72
+ value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
73
+ value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
74
+ ENV[envar] = value
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,353 @@
1
+ module Courtier
2
+ # External requirements.
3
+ require 'yaml'
4
+ require 'finder'
5
+ require 'loaded'
6
+
7
+ # Internal requirements.
8
+ require 'courtier/core_ext'
9
+ require 'courtier/config'
10
+ require 'courtier/configuration'
11
+ #require 'courtier/config_filter'
12
+ require 'courtier/properties'
13
+ require 'courtier/setup'
14
+
15
+ # The Interface module extends Courtier module.
16
+ #
17
+ # A tool can control Courtier configuration by loading `courtier` and calling the
18
+ # toplevel `court` or `Courtier.setup` method with a block that handles the
19
+ # configuration for the feature as provided by a project's config file.
20
+ #
21
+ # The block will often need to be conditioned on the current profile and/or the
22
+ # then current command. This is easy enough to do with #profile? and #command?
23
+ # methods.
24
+ #
25
+ # require 'courtier'
26
+ #
27
+ # Courtier.setup('rspec') do |config|
28
+ # if config.profile?
29
+ # RSpec.configure(&config)
30
+ # end
31
+ # end
32
+ #
33
+ module Interface
34
+
35
+ #
36
+ # Configuration file pattern. The standard configuration file name is
37
+ # `Config.rb`, and that name should be used in most cases. However,
38
+ # `.config.rb` can also be use and will take precedence if found.
39
+ # Conversely, `config.rb` (lowercase form) can also be used but has
40
+ # the least precedence.
41
+ #
42
+ # Config files looked for in the order or precedence:
43
+ #
44
+ # * `.config.rb` or `.confile.rb`
45
+ # * `Config.rb` or `Confile.rb`
46
+ # * `config.rb` or `confile.rb`
47
+ #
48
+ # Yes, there are really too many choices here, but we haven't been able
49
+ # to settle on a smaller list just yet. Please come argue with us about
50
+ # what's best.
51
+ #
52
+ FILE_PATTERN = '{.c,C,c}on{fig.rb,file,file.rb}'
53
+
54
+ #
55
+ # The tweaks directory is where special augementation script reside
56
+ # the are used to adjust behavior of certain popular tools to work
57
+ # with Courtier that would not otherwise do so.
58
+ #
59
+ TWEAKS_DIR = File.dirname(__FILE__) + '/tweaks'
60
+
61
+ #
62
+ # Library configuration cache. Since configuration can be imported from
63
+ # other libraries, we keep a cache for each library.
64
+ #
65
+ def cache
66
+ @cache ||= {}
67
+ end
68
+
69
+ #
70
+ # Clear the library configuration cache. This is mostly used
71
+ # for testing.
72
+ #
73
+ def clear!
74
+ @cache = {}
75
+ end
76
+
77
+ #
78
+ # Load library configuration for a given +gem+. If no +gem+ is
79
+ # specified then the current project's configuration is used.
80
+ #
81
+ # @return [Configuration]
82
+ #
83
+ def configuration(gem=nil)
84
+ key = gem ? gem.to_s : nil #Dir.pwd
85
+ cache[key] ||= Configuration.load(:from=>gem)
86
+ end
87
+
88
+ #
89
+ # Return a list of names of defined profiles for a given +tool+.
90
+ #
91
+ # @param [#to_sym] tool
92
+ # Tool for which lookup defined profiles. If none given
93
+ # the current tool is used.
94
+ #
95
+ # @param [Hash] opts
96
+ # Options for looking up profiles.
97
+ #
98
+ # @option opts [#to_s] :gem
99
+ # Name of library from which to load the configuration.
100
+ #
101
+ # @example
102
+ # profile_names(:qed)
103
+ #
104
+ def profile_names(tool=nil, opts={})
105
+ if Hash === tool
106
+ opts, tool = tool, nil
107
+ end
108
+
109
+ tool = tool || current_tool
110
+ gem = opts[:from]
111
+
112
+ configuration(gem).profile_names(tool)
113
+ end
114
+
115
+ #
116
+ # Get current tool.
117
+ #
118
+ # @todo Not so sure `ENV['tool']` is a good idea.
119
+ #
120
+ def current_tool
121
+ File.basename(ENV['tool'] || $0)
122
+ end
123
+ alias current_command current_tool
124
+
125
+ #
126
+ # Set current tool.
127
+ #
128
+ def current_tool=(tool)
129
+ ENV['tool'] = tool.to_s
130
+ end
131
+ alias current_command= current_tool=
132
+
133
+ #
134
+ # Get current profile.
135
+ #
136
+ def current_profile
137
+ ENV['profile'] || ENV['p'] || 'default'
138
+ end
139
+
140
+ #
141
+ # Set current profile.
142
+ #
143
+ def current_profile=(profile)
144
+ if profile
145
+ ENV['profile'] = profile.to_s
146
+ else
147
+ ENV['profile'] = nil
148
+ end
149
+ end
150
+
151
+ # TODO: Maybe properties should come from Configuration class and be per-gem.
152
+ # I don't see a use for imported properties, but just in case.
153
+
154
+ #
155
+ # Properties of the current project. These can be used in a project's config file
156
+ # to make configuration more interchangeable. Presently project properties are
157
+ # gathered from .ruby YAML or .gemspec.
158
+ #
159
+ # NOTE: How properties are gathered will be refined in the future.
160
+ #
161
+ def properties
162
+ $properties ||= Properties.new
163
+ end
164
+
165
+ #
166
+ # Define a specialized configuration handler.
167
+ #
168
+ def court(tool, options={}, &block)
169
+ @setup ||= {}
170
+ tool = tool.to_s
171
+ if block
172
+ @setup[tool] = Setup.new(tool, options, &block)
173
+ #path = Find.load_path(tool, :absolute=>true)
174
+ #if path
175
+ # if $LOADED_FEATURES.include?(path)
176
+ # configure(tool)
177
+ # end
178
+ #end
179
+ end
180
+ @setup[tool.to_s]
181
+ end
182
+
183
+ #
184
+ # Set current profile via ARGV switch. This is done immediately,
185
+ # setting `ENV['profile']` to the switch value if this setup is
186
+ # for the current commandline tool. The reason it is done immediately,
187
+ # rather than assigning it in bootstrap, is b/c option parsers somtimes
188
+ # consume ARGV as they parse it, and by then it would too late.
189
+ #
190
+ # @example
191
+ # Courtier.profile_switch('qed', '-p', '--profile')
192
+ #
193
+ def profile_switch(command, *switches)
194
+ return unless command.to_s == Courtier.current_command
195
+
196
+ switches.each do |switch, envar|
197
+ if index = ARGV.index(switch)
198
+ self.current_profile = ARGV[index+1]
199
+ elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ }
200
+ value = $1
201
+ value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
202
+ value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
203
+ self.currrent_profile = value
204
+ end
205
+ end
206
+ end
207
+
208
+ #
209
+ # Set enviroment variable(s) to command line switch value(s). This is a more general
210
+ # form of #profile_switch and will probably not get much use in this context.
211
+ #
212
+ # @example
213
+ # Courtier.switch('qed', '-p'=>'profile', '--profile'=>'profile')
214
+ #
215
+ def switch(command, switches={})
216
+ return unless command.to_s == Courtier.current_command
217
+
218
+ switches.each do |switch, envar|
219
+ if index = ARGV.index(switch)
220
+ ENV[envar] = ARGV[index+1]
221
+ elsif arg = ARGV.find{ |a| a =~ /#{switch}=(.*?)/ }
222
+ value = $1
223
+ value = value[1..-2] if value.start_with?('"') && value.end_with?('"')
224
+ value = value[1..-2] if value.start_with?("'") && value.end_with?("'")
225
+ ENV[envar] = value
226
+ end
227
+ end
228
+ end
229
+
230
+ #
231
+ #
232
+ #
233
+ def configure(tool, options={}, &setup)
234
+ court(tool, options, &setup)
235
+ _configure(tool)
236
+ end
237
+
238
+ private
239
+
240
+ #
241
+ # Setup courtier system.
242
+ #
243
+ def bootstrap
244
+ @bootstrap ||= (
245
+ properties # prime global properties
246
+ bootstrap_require
247
+ true
248
+ )
249
+ end
250
+
251
+ # TODO: Also add loaded callback ?
252
+
253
+ #
254
+ # Override require.
255
+ #
256
+ def bootstrap_require
257
+ def Kernel.required(feature)
258
+ config = Courtier.configuration[feature]
259
+ if config
260
+ setup = Courtier.court(feature) # FIXME: how to differentiate feature from command setup ?
261
+ config.each do |config|
262
+ next unless config.onload? # only command config
263
+ next unless config.apply?
264
+ setup ? setup.call(config) : config.call
265
+ end
266
+ end
267
+ super(feature) if defined?(super)
268
+ end
269
+ end
270
+
271
+ #
272
+ # Copnfgure current commnad. This is used by the `rc` script.
273
+ #
274
+ def autoconfigure
275
+ _configure(current_command)
276
+ end
277
+
278
+ #
279
+ #
280
+ #
281
+ def _configure(command) #, &setup)
282
+ tweak(command)
283
+
284
+ command_config = Courtier.configuration[command]
285
+
286
+ return unless command_config
287
+
288
+ setup = Courtier.court(command)
289
+
290
+ command_config.each do |config|
291
+ next if config.onload? # not command config
292
+ next unless config.apply?
293
+ config.require_feature
294
+ setup ? setup.call(config) : config.call
295
+ end
296
+ end
297
+
298
+ #
299
+ #
300
+ #
301
+ def tweak(command)
302
+ tweak = File.join(TWEAKS_DIR, command + '.rb')
303
+ if File.exist?(tweak)
304
+ require tweak
305
+ end
306
+ end
307
+
308
+ ##
309
+ ## IDEA: Preconfigurations occur before other comamnd configs and
310
+ ## do not require feature.
311
+ ##
312
+ #def preconfigure(options={})
313
+ # tool = options[:tool] || current_tool
314
+ # profile = options[:profile] || current_profile
315
+ #
316
+ # preconfiguration.each do |c|
317
+ # c.call if c.match?(tool, profile)
318
+ # end
319
+ #end
320
+
321
+ end
322
+
323
+ extend Interface
324
+
325
+ bootstrap # prepare system
326
+ end
327
+
328
+ # Toplevel convenience method for `Courtier.court`.
329
+ #
330
+ # @example
331
+ # court 'qed' do |config|
332
+ # QED.configure(config.profile, &config)
333
+ # end
334
+ #
335
+ def self.court(tool, options={}, &block)
336
+ Courtier.court(tool, options, &block)
337
+ end
338
+
339
+ # Toplevel convenience method for `Courtier.configure`.
340
+ # Configure's tool immediately.
341
+ #
342
+ # @example
343
+ # configure 'qed' do |config|
344
+ # QED.configure(config.profile, &config)
345
+ # end
346
+ #
347
+ def self.configure(tool, options={}, &block)
348
+ Courtier.configure(tool, options, &block)
349
+ end
350
+
351
+ # @deprecated Alternate namespace name.
352
+ RC = Courtier
353
+
@@ -0,0 +1,44 @@
1
+ module Courtier
2
+
3
+ # Currently properties derive from a project's .ruby file.
4
+ # This will be expanded upon in future version to allow
5
+ # additional customization.
6
+ #
7
+ # @todo Lookup project root directory.
8
+ #
9
+ class Properties
10
+
11
+ #
12
+ #
13
+ #
14
+ DATA_FILE = '.ruby'
15
+
16
+ #
17
+ #
18
+ #
19
+ def initialize
20
+ @data = {}
21
+
22
+ if file = Dir[DATA_FILE].first
23
+ @data.update(YAML.load_file(file))
24
+ end
25
+ end
26
+
27
+ #
28
+ #
29
+ #
30
+ def method_missing(s)
31
+ @data[s.to_s]
32
+ end
33
+
34
+ private
35
+
36
+ # @todo Support gemspec as properties source ?
37
+ def import_gemspec
38
+ file = Dir['{*,,pkg/*}.gemspec'].first
39
+ # ...
40
+ end
41
+
42
+ end
43
+
44
+ end