rc 0.1.1
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 +51 -0
- data/.yardopts +8 -0
- data/Config.rb +68 -0
- data/HISTORY.md +10 -0
- data/LICENSE.txt +27 -0
- data/NOTES.md +38 -0
- data/README.md +182 -0
- data/lib/c.rb +1 -0
- data/lib/rc.rb +10 -0
- data/lib/rc/config.rb +142 -0
- data/lib/rc/configuration.rb +192 -0
- data/lib/rc/core_ext.rb +49 -0
- data/lib/rc/interface.rb +163 -0
- data/lib/rc/properties.rb +44 -0
- data/lib/rc/tool_configuration.rb +54 -0
- data/lib/rc/tweaks/rake.rb +28 -0
- data/spec/00_concept.md +22 -0
- data/spec/01_config.md +14 -0
- data/spec/02_configuration.md +65 -0
- data/spec/03_import.md +48 -0
- data/spec/06_interface.md +36 -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
- metadata +121 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
module RC
|
2
|
+
|
3
|
+
# Configuration
|
4
|
+
#
|
5
|
+
class Configuration < Module
|
6
|
+
|
7
|
+
#
|
8
|
+
# Configuration file pattern. The standard configuration file name is
|
9
|
+
# `Config.rb`, and that name should be used in most cases. However,
|
10
|
+
# `.config.rb` can also be use and will take precedence if found.
|
11
|
+
# Conversely, `config.rb` (lowercase form) can also be used but has
|
12
|
+
# the least precedence.
|
13
|
+
#
|
14
|
+
# Config files looked for in the order or precedence:
|
15
|
+
#
|
16
|
+
# * `.config.rb`
|
17
|
+
# * `Config.rb`
|
18
|
+
# * `config.rb`
|
19
|
+
#
|
20
|
+
CONFIG_FILE = '{.c,C,c}onfig{.rb,}'
|
21
|
+
|
22
|
+
#
|
23
|
+
# When looking up config file, it one of these is found
|
24
|
+
# then there is no point to looking further.
|
25
|
+
#
|
26
|
+
ROOT_INDICATORS = %w{.git .hg _darcs} #.ruby
|
27
|
+
|
28
|
+
#
|
29
|
+
# Load configuration file from local project or other gem.
|
30
|
+
#
|
31
|
+
# @param options [Hash] Load options.
|
32
|
+
#
|
33
|
+
# @option options [String] :from
|
34
|
+
# Name of gem or library.
|
35
|
+
#
|
36
|
+
def self.load(options={})
|
37
|
+
if from = options[:from]
|
38
|
+
file = Find.path(CONFIG_FILE, :from=>from).first
|
39
|
+
else
|
40
|
+
file = lookup(CONFIG_FILE)
|
41
|
+
end
|
42
|
+
new(file)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Initialize new Configuration object.
|
47
|
+
#
|
48
|
+
# @param [String] file
|
49
|
+
# Configuration file (optional).
|
50
|
+
#
|
51
|
+
def initialize(file=nil)
|
52
|
+
@file = file
|
53
|
+
|
54
|
+
@_list = []
|
55
|
+
@_state = {}
|
56
|
+
|
57
|
+
# TODO: does this rescue make sense here?
|
58
|
+
begin
|
59
|
+
import_relative(@file) if @file
|
60
|
+
rescue => e
|
61
|
+
raise e if $DEBUG
|
62
|
+
warn e.message
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Profile block.
|
68
|
+
#
|
69
|
+
# @param [String,Symbol] name
|
70
|
+
# A profile name.
|
71
|
+
#
|
72
|
+
def profile(name, &block)
|
73
|
+
raise SyntaxError, "nested profile sections" if @_state[:profile]
|
74
|
+
original_state = @_state.dup
|
75
|
+
@_state[:profile] = name.to_s
|
76
|
+
instance_eval(&block)
|
77
|
+
@_state = original_state
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Configure a tool.
|
82
|
+
#
|
83
|
+
# @param [Symbol] tool
|
84
|
+
# The name of the tool to configure.
|
85
|
+
#
|
86
|
+
# @param [Hash] opts
|
87
|
+
# Configuration options.
|
88
|
+
#
|
89
|
+
# @options opts [String] :from
|
90
|
+
# Library from which to extract configuration.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# profile :coverage do
|
94
|
+
# config :qed, :from=>'qed'
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# @todo Clean this code up.
|
98
|
+
#
|
99
|
+
def config(tool, *args, &block)
|
100
|
+
options = (Hash===args.last ? args.pop : {})
|
101
|
+
|
102
|
+
# @todo Might we have an option to lockdown tool
|
103
|
+
# So that we do without ToolConfiguration?
|
104
|
+
|
105
|
+
case args.first
|
106
|
+
when Symbol
|
107
|
+
profile = args.shift
|
108
|
+
when String
|
109
|
+
profile = args.shift unless args.first.index("\n")
|
110
|
+
end
|
111
|
+
|
112
|
+
data = args.shift
|
113
|
+
raise ArgumentError, "must use data or block, not both" if data && block
|
114
|
+
if data
|
115
|
+
data = data.tabto(0)
|
116
|
+
block = Proc.new do
|
117
|
+
YAML.load(data)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
from = options[:from]
|
122
|
+
|
123
|
+
raise ArgumentError, "too many arguments" if args.first
|
124
|
+
raise SyntaxError, "nested profile sections" if profile && @_state[:profile]
|
125
|
+
#raise ArgumentError, "use block or :from setting" if options[:from] && block
|
126
|
+
|
127
|
+
profile = @_state[:profile] unless profile
|
128
|
+
|
129
|
+
if from
|
130
|
+
from_config = RC.configuration(from)
|
131
|
+
from_tool = options[:tool] || tool
|
132
|
+
from_profile = options[:profile] || profile
|
133
|
+
from_config.each do |c|
|
134
|
+
if c.match?(from_tool, from_profile)
|
135
|
+
@_list << Config.new(tool, profile, &c)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
return unless block
|
140
|
+
end
|
141
|
+
|
142
|
+
@_list << Config.new(tool, profile, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# @return [Hash] Defined configurations.
|
147
|
+
#
|
148
|
+
def configurations
|
149
|
+
@_list
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# @return [ToolConfiguration] Subset of Configuration.
|
154
|
+
#
|
155
|
+
def [](tool)
|
156
|
+
ToolConfiguration.new(tool, self)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Configuration is Enumerable.
|
160
|
+
include Enumerable
|
161
|
+
|
162
|
+
#
|
163
|
+
def each(&block)
|
164
|
+
@_list.each(&block)
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
def size
|
169
|
+
@_list.size
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
#
|
175
|
+
# Search upward from working directory.
|
176
|
+
#
|
177
|
+
def self.lookup(glob, flags=0)
|
178
|
+
pwd = File.expand_path(Dir.pwd)
|
179
|
+
home = File.expand_path('~')
|
180
|
+
while pwd != '/' && pwd != home
|
181
|
+
if file = Dir.glob(File.join(pwd, glob), flags).first
|
182
|
+
return file
|
183
|
+
end
|
184
|
+
break if ROOT_INDICATORS.any?{ |r| File.exist?(File.join(pwd, r)) }
|
185
|
+
pwd = File.dirname(pwd)
|
186
|
+
end
|
187
|
+
return nil
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
data/lib/rc/core_ext.rb
ADDED
@@ -0,0 +1,49 @@
|
|
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
|
+
end
|
25
|
+
|
26
|
+
class String
|
27
|
+
def tabto(n)
|
28
|
+
if self =~ /^( *)\S/
|
29
|
+
indent(n - $1.length)
|
30
|
+
else
|
31
|
+
self
|
32
|
+
end
|
33
|
+
end unless method_defined?(:tabto)
|
34
|
+
|
35
|
+
def indent(n, c=' ')
|
36
|
+
if n >= 0
|
37
|
+
gsub(/^/, c * n)
|
38
|
+
else
|
39
|
+
gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "")
|
40
|
+
end
|
41
|
+
end unless method_defined?(:indent)
|
42
|
+
end
|
43
|
+
|
44
|
+
#class Symbol
|
45
|
+
# def /(other)
|
46
|
+
# "#{self}/#{other}".to_sym
|
47
|
+
# end
|
48
|
+
#end
|
49
|
+
|
data/lib/rc/interface.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
module RC
|
2
|
+
# External requirements.
|
3
|
+
require 'yaml'
|
4
|
+
require 'finder'
|
5
|
+
|
6
|
+
# Internal requirements.
|
7
|
+
require 'rc/core_ext'
|
8
|
+
require 'rc/config'
|
9
|
+
require 'rc/configuration'
|
10
|
+
require 'rc/tool_configuration'
|
11
|
+
require 'rc/properties'
|
12
|
+
|
13
|
+
#
|
14
|
+
# Configuration file pattern. The standard configuration file name is
|
15
|
+
# `Config.rb`, and that name should be used in most cases. However,
|
16
|
+
# `.config.rb` can also be use and will take precedence if found.
|
17
|
+
# Conversely, `config.rb` (lowercase form) can also be used but has
|
18
|
+
# the least precedence.
|
19
|
+
#
|
20
|
+
# Config files looked for in the order or precedence:
|
21
|
+
#
|
22
|
+
# * `.config.rb`
|
23
|
+
# * `Config.rb`
|
24
|
+
# * `config.rb`
|
25
|
+
#
|
26
|
+
FILE_PATTERN = '{.c,C,c}onfig{.rb,}'
|
27
|
+
|
28
|
+
#
|
29
|
+
def self.cache
|
30
|
+
@cache ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
def self.clear!
|
35
|
+
@cache = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
def self.configuration(gem=nil)
|
40
|
+
key = gem ? gem.to_s : nil #Dir.pwd
|
41
|
+
cache[key] ||= Configuration.load(:from=>gem)
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# @return [Array] List of profiles for given `tool`.
|
46
|
+
#
|
47
|
+
def self.profiles(tool, options={})
|
48
|
+
tool = tool.to_s
|
49
|
+
gem = options[:from]
|
50
|
+
configuration(gem).map{ |c| c.tool.to_s }
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Get current tool.
|
55
|
+
#
|
56
|
+
def self.current_tool
|
57
|
+
File.basename(ENV['tool'] || $0)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Set current tool.
|
62
|
+
#
|
63
|
+
def self.current_tool=(tool)
|
64
|
+
ENV['tool'] = tool.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Get current profile.
|
69
|
+
#
|
70
|
+
def self.current_profile
|
71
|
+
ENV['profile']
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Set current profile.
|
76
|
+
#
|
77
|
+
def self.current_profile=(profile)
|
78
|
+
if profile
|
79
|
+
ENV['profile'] = profile.to_s
|
80
|
+
else
|
81
|
+
ENV['profile'] = nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Project properties.
|
87
|
+
#
|
88
|
+
def self.properties
|
89
|
+
$properties ||= Properties.new
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# Get/set configuration processor. Tools can use this
|
94
|
+
# to gain control over the configuration procedure.
|
95
|
+
#
|
96
|
+
# The block should take a single argument of the current
|
97
|
+
# Configuration.
|
98
|
+
#
|
99
|
+
# This might be used to save the configuration for
|
100
|
+
# a later execution, or to evaluate the procedures
|
101
|
+
# in a special scope, or both.
|
102
|
+
#
|
103
|
+
# Keep in mind that if configurations are evaluated in
|
104
|
+
# a different scope, they may not be able to utilize
|
105
|
+
# any shared methods defined in the config file.
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# RC.processor('qed') do |config|
|
109
|
+
# if i = ARGV.index('--profile') || ARGV.index('-p')
|
110
|
+
# ENV['profile'] = ARGV[i+1]
|
111
|
+
# end
|
112
|
+
# RC.configure
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
def self.processor(tool, &block)
|
116
|
+
@processors ||= {}
|
117
|
+
@processors[tool.to_s] = block if block
|
118
|
+
@processors[tool.to_s]
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
# Setup configuration.
|
123
|
+
#
|
124
|
+
def self.configure(options={})
|
125
|
+
tool = options[:tool] || current_tool
|
126
|
+
profile = options[:profile] || current_profile
|
127
|
+
|
128
|
+
configuration.each do |c|
|
129
|
+
c.call if c.match?(tool, profile)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Start RC.
|
135
|
+
#
|
136
|
+
def self.bootstrap
|
137
|
+
properties # prime global properties
|
138
|
+
|
139
|
+
tweak = File.join(File.dirname(__FILE__), 'tweaks', current_tool + '.rb')
|
140
|
+
if File.exist?(tweak)
|
141
|
+
require tweak
|
142
|
+
else
|
143
|
+
begin
|
144
|
+
require current_tool
|
145
|
+
rescue LoadError
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if proc = processor(current_tool)
|
150
|
+
tool_config = ToolConfiguration.new(current_tool, configuration)
|
151
|
+
proc.call(tool_config)
|
152
|
+
else
|
153
|
+
configure
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# @todo: I'm sure this, #bootstrap and #processor can be simplifed.
|
158
|
+
def self.run(tool, &block)
|
159
|
+
processor(tool, &block)
|
160
|
+
require 'rc'
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RC
|
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
|