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.
- data/.ruby +63 -0
- data/.yardopts +8 -0
- data/Config.rb +82 -0
- data/HISTORY.md +20 -0
- data/LICENSE.txt +27 -0
- data/NOTES.md +38 -0
- data/README.md +185 -0
- data/lib/c.rb +7 -0
- data/lib/courtier.rb +8 -0
- data/lib/courtier/config.rb +249 -0
- data/lib/courtier/config_filter.rb +104 -0
- data/lib/courtier/configuration.rb +335 -0
- data/lib/courtier/core_ext.rb +78 -0
- data/lib/courtier/interface.rb +353 -0
- data/lib/courtier/properties.rb +44 -0
- data/lib/courtier/setup.rb +46 -0
- data/lib/courtier/tweaks/rake.rb +31 -0
- data/lib/rc.rb +2 -0
- data/spec/00_concept.md +23 -0
- data/spec/01_config.md +14 -0
- data/spec/02_configuration.md +51 -0
- data/spec/03_import.md +49 -0
- data/spec/06_interface.md +39 -0
- data/spec/applique/ae.rb +1 -0
- data/spec/applique/file.rb +8 -0
- data/spec/applique/fixture.rb +10 -0
- data/spec/applique/fixture/config.rb +16 -0
- data/spec/cov.rb +7 -0
- metadata +135 -0
@@ -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
|