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