confection 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +1 -1
- data/Confile.rb +57 -0
- data/lib/confection/core_ext.rb +14 -0
- data/lib/confection/current.rb +79 -0
- data/lib/confection/hash_builder.rb +48 -0
- data/lib/confection/project.rb +161 -0
- data/lib/confection/store.rb +182 -0
- data/spec/00_concept.md +33 -0
- data/spec/01_dsl.md +69 -0
- data/spec/02_import.md +52 -0
- data/spec/03_store.md +20 -0
- data/spec/04_controller.md +68 -0
- data/spec/05_config.md +99 -0
- data/spec/06_manage.md +38 -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/confile.rb +15 -0
- metadata +30 -13
data/.ruby
CHANGED
@@ -47,7 +47,7 @@ revision: 0
|
|
47
47
|
created: '2011-11-06'
|
48
48
|
summary: Multi-tenant configuration for Ruby
|
49
49
|
title: Confection
|
50
|
-
version: 0.2.
|
50
|
+
version: 0.2.1
|
51
51
|
name: confection
|
52
52
|
description: Confection is a multi-tenant configuration system for Ruby projects.
|
53
53
|
organization: Rubyworks
|
data/Confile.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# QED test coverage report using SimpleCov.
|
5
|
+
#
|
6
|
+
# coverage_folder - the directory in which to store coverage report
|
7
|
+
# this defaults to `log/coverage`.
|
8
|
+
#
|
9
|
+
config :qed, :cov do
|
10
|
+
require 'simplecov'
|
11
|
+
|
12
|
+
dir = $properties.coverage_folder
|
13
|
+
|
14
|
+
SimpleCov.start do
|
15
|
+
coverage_dir(dir || 'log/coverage')
|
16
|
+
#add_group "Label", "lib/qed/directory"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Example configuration.
|
22
|
+
#
|
23
|
+
config :example do
|
24
|
+
puts "Configuration Example!"
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Detroit assembly.
|
29
|
+
#
|
30
|
+
config :detroit do
|
31
|
+
email do
|
32
|
+
mailto 'ruby-talk@ruby-lang.org', 'rubyworks-mailinglist@googlegroups.com'
|
33
|
+
end
|
34
|
+
|
35
|
+
gem do
|
36
|
+
active true
|
37
|
+
end
|
38
|
+
|
39
|
+
github do
|
40
|
+
folder 'web'
|
41
|
+
end
|
42
|
+
|
43
|
+
dnote do
|
44
|
+
title 'Source Notes'
|
45
|
+
output 'log/notes.html'
|
46
|
+
end
|
47
|
+
|
48
|
+
locat do
|
49
|
+
output 'log/locat.html'
|
50
|
+
end
|
51
|
+
|
52
|
+
vclog do
|
53
|
+
output 'log/history.html',
|
54
|
+
'log/changes.html'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# All core extensions come from Ruby Facets to maintain high standard for
|
2
|
+
# careful core extensions.
|
3
|
+
|
4
|
+
require 'facets/string/tabto'
|
5
|
+
require 'facets/to_hash'
|
6
|
+
|
7
|
+
#require 'facets/ostruct/to_h' # TODO: Newer version of facets.
|
8
|
+
require 'ostruct'
|
9
|
+
class OpenStruct
|
10
|
+
def to_h
|
11
|
+
@table.dup
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Confection
|
2
|
+
|
3
|
+
#
|
4
|
+
# Global properties is set for parsing project configuration.
|
5
|
+
# It is *always* the properties of the current project.
|
6
|
+
#
|
7
|
+
$properties = nil
|
8
|
+
|
9
|
+
# Current mixin extends the Confection module. Primarily is provides
|
10
|
+
# class methods for working with the current project's configurations.
|
11
|
+
#
|
12
|
+
module Current
|
13
|
+
|
14
|
+
#
|
15
|
+
def controller(scope, tool, *options)
|
16
|
+
params = (Hash === options.last ? options.pop : {})
|
17
|
+
params[:profile] = options.shift unless options.empty?
|
18
|
+
|
19
|
+
if from = params[:from]
|
20
|
+
projects[from] ||= Project.load(from)
|
21
|
+
projects[from].controller(scope, tool, params)
|
22
|
+
else
|
23
|
+
bootstrap if $properties.nil? # TODO: better way to go about this?
|
24
|
+
current_project.controller(scope, tool, params)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
def bootstrap
|
30
|
+
$properties = current_project.properties
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
def projects
|
35
|
+
@projects ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
def current_directory
|
40
|
+
@current_directory ||= Dir.pwd
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
def current_project
|
45
|
+
projects[current_directory] ||= Project.lookup(current_directory)
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
def clear!
|
50
|
+
current_project.store.clear!
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
def profiles(tool)
|
55
|
+
current_project.profiles(tool)
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
def each(&block)
|
60
|
+
current_project.each(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
def size
|
65
|
+
current_project.size
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Project properties.
|
70
|
+
#
|
71
|
+
def properties
|
72
|
+
current_project.properties
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
extend Current
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Confection
|
2
|
+
|
3
|
+
# HashBuilder takes a procedure and builds a Hash out of it.
|
4
|
+
#
|
5
|
+
# The procedure must conform to a set of rules to be useful in this respect.
|
6
|
+
# They must either take an argument and use that argument to set values, or
|
7
|
+
# if no argument is taken then `#instance_eval` is used to evaluate the
|
8
|
+
# procedure such that each method represents a key.
|
9
|
+
#
|
10
|
+
class HashBuilder < BasicObject
|
11
|
+
|
12
|
+
#
|
13
|
+
def initialize(hash={}, &block)
|
14
|
+
@hash = hash
|
15
|
+
case block.arity
|
16
|
+
when 0
|
17
|
+
instance_eval(&block)
|
18
|
+
else
|
19
|
+
block.call(self)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
def to_h
|
25
|
+
@hash
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
def method_missing(s, *a, &b)
|
30
|
+
m = s.to_s
|
31
|
+
if a.empty? && !b
|
32
|
+
@hash[m.to_sym]
|
33
|
+
else
|
34
|
+
if b
|
35
|
+
@hash[m.chomp('=').to_sym] = HashBuilder.new(&b).to_h
|
36
|
+
else
|
37
|
+
if a.size > 1
|
38
|
+
@hash[m.chomp('=').to_sym] = a
|
39
|
+
else
|
40
|
+
@hash[m.chomp('=').to_sym] = a.first
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Confection
|
2
|
+
|
3
|
+
# Project configuration.
|
4
|
+
#
|
5
|
+
# @todo Rename to `ProjectConfig` or ?
|
6
|
+
#
|
7
|
+
class Project
|
8
|
+
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
#
|
12
|
+
# Configuration file pattern.
|
13
|
+
#
|
14
|
+
PATTERN = '{.,}confile{.rb,}'
|
15
|
+
|
16
|
+
#
|
17
|
+
# Per library cache.
|
18
|
+
#
|
19
|
+
def self.cache
|
20
|
+
@cache ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Get project configuration from another library.
|
25
|
+
#
|
26
|
+
# This method uses the Finder gem.
|
27
|
+
#
|
28
|
+
# @param [String] lib
|
29
|
+
# Library name.
|
30
|
+
#
|
31
|
+
# @return [Project,nil] Located project.
|
32
|
+
#
|
33
|
+
def self.load(lib=nil)
|
34
|
+
if lib
|
35
|
+
lib = lib.to_s
|
36
|
+
return cache[lib] if cache.key?(lib)
|
37
|
+
cache[lib] ||= (
|
38
|
+
config_path = Find.path(PATTERN, :from=>lib).first
|
39
|
+
config_path ? new(File.dirname(config_path)) : nil
|
40
|
+
)
|
41
|
+
else
|
42
|
+
lookup
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Lookup configuation file.
|
48
|
+
#
|
49
|
+
# @param dir [String]
|
50
|
+
# Optional directory to begin search.
|
51
|
+
#
|
52
|
+
# @return [String] file path
|
53
|
+
#
|
54
|
+
def self.lookup(dir=nil)
|
55
|
+
dir = dir || Dir.pwd
|
56
|
+
home = File.expand_path('~')
|
57
|
+
while dir != '/' && dir != home
|
58
|
+
if file = Dir.glob(File.join(dir, PATTERN), File::FNM_CASEFOLD).first
|
59
|
+
return new(File.dirname(file))
|
60
|
+
end
|
61
|
+
dir = File.dirname(dir)
|
62
|
+
end
|
63
|
+
return nil
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Initialize new ProjectConfig.
|
68
|
+
#
|
69
|
+
# @param [String] root
|
70
|
+
# Project root directory.
|
71
|
+
#
|
72
|
+
def initialize(root)
|
73
|
+
@root = root
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Project root directory.
|
78
|
+
#
|
79
|
+
# @return [String] project's root directory
|
80
|
+
#
|
81
|
+
attr :root
|
82
|
+
|
83
|
+
#
|
84
|
+
# Alias for #root.
|
85
|
+
#
|
86
|
+
alias :directory :root
|
87
|
+
|
88
|
+
#
|
89
|
+
# Configuration store tracks a project's confirguration entries.
|
90
|
+
#
|
91
|
+
def store
|
92
|
+
@store ||= Store.new(*source)
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# The file path of the project's configuration file.
|
97
|
+
#
|
98
|
+
# @return [String] path to configuration file
|
99
|
+
#
|
100
|
+
def source
|
101
|
+
Dir.glob(File.join(root, PATTERN), File::FNM_CASEFOLD)
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# List of configuration profiles.
|
106
|
+
#
|
107
|
+
# @return [Array] profile names
|
108
|
+
#
|
109
|
+
def profiles(tool)
|
110
|
+
store.profiles(tool)
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Project properties.
|
115
|
+
#
|
116
|
+
# @todo Use cascading class, e.g. Confstruct.
|
117
|
+
#
|
118
|
+
def properties
|
119
|
+
dotruby = File.join(directory,'.ruby')
|
120
|
+
if File.exist?(dotruby)
|
121
|
+
data = YAML.load_file(dotruby)
|
122
|
+
OpenStruct.new(data)
|
123
|
+
else
|
124
|
+
OpenStruct.new
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Create a configuration controller.
|
130
|
+
#
|
131
|
+
# @param [Object] scope
|
132
|
+
# Context for which controller is being created.
|
133
|
+
#
|
134
|
+
# @param [Symbol] tool
|
135
|
+
# The tool of the configuration to select.
|
136
|
+
#
|
137
|
+
def controller(scope, tool, options={})
|
138
|
+
profile = options[:profile]
|
139
|
+
configs = store.lookup(tool, profile)
|
140
|
+
Controller.new(scope, *configs)
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Iterate over each configurations.
|
145
|
+
#
|
146
|
+
def each(&block)
|
147
|
+
store.each(&block)
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# The number of configurations.
|
152
|
+
#
|
153
|
+
# @return [Fixnum] config count
|
154
|
+
#
|
155
|
+
def size
|
156
|
+
store.size
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Confection
|
2
|
+
|
3
|
+
class Store
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
=begin
|
8
|
+
#
|
9
|
+
# Bootstrap the system, loading current configurations.
|
10
|
+
#
|
11
|
+
def bootstrap
|
12
|
+
if file
|
13
|
+
@config[root] = []
|
14
|
+
begin
|
15
|
+
DSL.load_file(file)
|
16
|
+
rescue => e
|
17
|
+
raise e if $DEBUG
|
18
|
+
warn e.message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
@config[root]
|
22
|
+
end
|
23
|
+
=end
|
24
|
+
|
25
|
+
#
|
26
|
+
def initialize(*sources)
|
27
|
+
@sources = sources
|
28
|
+
|
29
|
+
@list = []
|
30
|
+
|
31
|
+
sources.each do |source|
|
32
|
+
if File.file?(source)
|
33
|
+
parse(source)
|
34
|
+
else
|
35
|
+
# ignore directories
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
attr :sources
|
42
|
+
|
43
|
+
#
|
44
|
+
def parse(file)
|
45
|
+
DSL.parse(self, file)
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Iterate over each configurations.
|
50
|
+
#
|
51
|
+
def each(&block)
|
52
|
+
@list.each(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# The number of configurations.
|
57
|
+
#
|
58
|
+
# @return [Fixnum] config count
|
59
|
+
#
|
60
|
+
def size
|
61
|
+
@list.size
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Add as configuratio to the store.
|
66
|
+
#
|
67
|
+
def <<(conf)
|
68
|
+
raise TypeError, "not a configuration instance -- `#{conf}'" unless Config === conf
|
69
|
+
@list << conf
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Add a list of configs.
|
74
|
+
#
|
75
|
+
def concat(configs)
|
76
|
+
configs.each{ |c| self << c }
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Lookup configuration by tool and profile name.
|
81
|
+
#
|
82
|
+
# @todo Future versions should allow this to handle regex and fnmatches.
|
83
|
+
#
|
84
|
+
def lookup(tool, profile=nil)
|
85
|
+
if profile == '*'
|
86
|
+
select do |c|
|
87
|
+
c.tool.to_sym == tool.to_sym
|
88
|
+
end
|
89
|
+
else
|
90
|
+
profile = profile.to_sym if profile
|
91
|
+
|
92
|
+
select do |c|
|
93
|
+
c.tool.to_sym == tool.to_sym && c.profile == profile
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Returns list of profiles collected from all configs.
|
100
|
+
#
|
101
|
+
def profiles(tool)
|
102
|
+
names = []
|
103
|
+
each do |c|
|
104
|
+
names << c.profile if c.tool == tool.to_sym
|
105
|
+
end
|
106
|
+
names.uniq
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Clear configs.
|
111
|
+
#
|
112
|
+
def clear!
|
113
|
+
@list = []
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
def first
|
118
|
+
@list.first
|
119
|
+
end
|
120
|
+
|
121
|
+
#
|
122
|
+
def last
|
123
|
+
@list.last
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
#def config(*args, &block)
|
128
|
+
# dsl.config(*args, &block)
|
129
|
+
#end
|
130
|
+
|
131
|
+
#
|
132
|
+
#def controller(scope, name, profile=nil)
|
133
|
+
# configs = lookup(name, profile)
|
134
|
+
# Controller.new(scope, *configs)
|
135
|
+
#end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Import configuration from another project.
|
139
|
+
#
|
140
|
+
def import(tool, profile, options, &block)
|
141
|
+
from_tool = options[:tool] || tool
|
142
|
+
from_profile = options[:profile] || profile
|
143
|
+
|
144
|
+
case from = options[:from]
|
145
|
+
when String, Symbol
|
146
|
+
project = Project.load(from.to_s)
|
147
|
+
store = project ? project.store : nil
|
148
|
+
else
|
149
|
+
from = '(self)'
|
150
|
+
store = self
|
151
|
+
end
|
152
|
+
|
153
|
+
raise "no configuration found in `#{from}'" unless store
|
154
|
+
|
155
|
+
configs = store.lookup(from_tool, from_profile)
|
156
|
+
|
157
|
+
configs.each do |config|
|
158
|
+
new_config = config.copy(:tool=>tool, :profile=>profile)
|
159
|
+
|
160
|
+
#new_options = @_options.dup
|
161
|
+
#new_options[:tool] = tool
|
162
|
+
#new_options[:profile] = profile
|
163
|
+
#new_options[:block] = config.block
|
164
|
+
#new_options[:text] = config.text
|
165
|
+
|
166
|
+
# not so sure about this one
|
167
|
+
if String === new_config.value
|
168
|
+
new_config.value += ("\n" + options[:text].to_s) if options[:text]
|
169
|
+
end
|
170
|
+
|
171
|
+
self << new_config
|
172
|
+
end
|
173
|
+
|
174
|
+
#if block
|
175
|
+
# self << Config::Block.new(tool, profile, nil, &block)
|
176
|
+
#end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
data/spec/00_concept.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= Confection
|
2
|
+
|
3
|
+
The idea of confection is a unified configuration management across multiple
|
4
|
+
tools for Ruby. The structure of a confection configuration file is very simple.
|
5
|
+
It is a ruby script sectioned into named blocks:
|
6
|
+
|
7
|
+
config :rake do
|
8
|
+
# ... rake tasks ...
|
9
|
+
end
|
10
|
+
|
11
|
+
config :vclog do
|
12
|
+
# ... configure vclog ...
|
13
|
+
end
|
14
|
+
|
15
|
+
Utilization of the these configurations is strictly up to the consuming
|
16
|
+
application. For example, save native support in Rake itself, we can add
|
17
|
+
to a Rakefile:
|
18
|
+
|
19
|
+
require 'confection'
|
20
|
+
|
21
|
+
confection('rake').call
|
22
|
+
|
23
|
+
ANALYSIS:
|
24
|
+
|
25
|
+
config :qed, :coverage, -> do
|
26
|
+
name 'Tommy'
|
27
|
+
age 42
|
28
|
+
end
|
29
|
+
|
30
|
+
config :qed, :coverage do
|
31
|
+
puts "Scripted"
|
32
|
+
end
|
33
|
+
|
data/spec/01_dsl.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# DSL
|
2
|
+
|
3
|
+
The DSL class handle evaluation of a project configuration file.
|
4
|
+
|
5
|
+
store = Confection::Store.new
|
6
|
+
|
7
|
+
dsl = Confection::DSL.new(store)
|
8
|
+
|
9
|
+
The DSL instance will have a cached binding.
|
10
|
+
|
11
|
+
#dsl.__binding__ == dsl.__binding__
|
12
|
+
|
13
|
+
We can use the `#instance_eval` method to evaluate a configuration for our
|
14
|
+
demonstration.
|
15
|
+
|
16
|
+
dsl.instance_eval(<<-HERE)
|
17
|
+
config :sample1 do
|
18
|
+
"block code"
|
19
|
+
end
|
20
|
+
HERE
|
21
|
+
|
22
|
+
Evaluation of a configuration file, populate the Confection.config instance.
|
23
|
+
|
24
|
+
sample = store.last
|
25
|
+
sample.tool #=> :sample1
|
26
|
+
sample.profile #=> nil
|
27
|
+
sample.class #=> Confection::Config
|
28
|
+
|
29
|
+
A profile can be used as a means fo defining multiple configuration options
|
30
|
+
for a single tool. This can be done by setting the second argument to a Symbol.
|
31
|
+
|
32
|
+
dsl.instance_eval(<<-HERE)
|
33
|
+
config :sample2, :opt1 do
|
34
|
+
"block code"
|
35
|
+
end
|
36
|
+
HERE
|
37
|
+
|
38
|
+
sample = store.last
|
39
|
+
sample.tool #=> :sample2
|
40
|
+
sample.profile #=> :opt1
|
41
|
+
|
42
|
+
Or it can be done by using a `profile` block.
|
43
|
+
|
44
|
+
dsl.instance_eval(<<-HERE)
|
45
|
+
profile :opt1 do
|
46
|
+
config :sample2 do
|
47
|
+
"block code"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
HERE
|
51
|
+
|
52
|
+
sample = store.last
|
53
|
+
sample.tool #=> :sample2
|
54
|
+
sample.profile #=> :opt1
|
55
|
+
|
56
|
+
Different types of configuration can be defined. For instance, if a multi-line
|
57
|
+
string is passed to the `config` method will be a text-based configuration.
|
58
|
+
|
59
|
+
dsl.instance_eval(<<-HERE)
|
60
|
+
config :sample3, %{
|
61
|
+
text config
|
62
|
+
}
|
63
|
+
HERE
|
64
|
+
|
65
|
+
sample = store.last
|
66
|
+
sample.tool #=> :sample3
|
67
|
+
sample.profile #=> nil
|
68
|
+
sample.to_s.strip.assert == "text config"
|
69
|
+
|
data/spec/02_import.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Importing
|
2
|
+
|
3
|
+
## Configuration Importing
|
4
|
+
|
5
|
+
Configurations can be imported from another project
|
6
|
+
using the `:from` option.
|
7
|
+
|
8
|
+
store = Confection::Store.new
|
9
|
+
|
10
|
+
dsl = Confection::DSL.new(store)
|
11
|
+
|
12
|
+
dsl.config :qed, :profile=>'example', :from=>'qed'
|
13
|
+
|
14
|
+
store.size.assert == 1
|
15
|
+
|
16
|
+
The configuration can also be imported from a different profile.
|
17
|
+
|
18
|
+
dsl.config :qed, :coverage, :from=>'qed', :profile=>:simplecov
|
19
|
+
|
20
|
+
store.size.assert == 2
|
21
|
+
|
22
|
+
Although it will rarely be useful, it may also be imported from another tool.
|
23
|
+
|
24
|
+
dsl.config :example, :from=>'qed', :tool=>:sample
|
25
|
+
|
26
|
+
Imported configurations can also be augmented via a block.
|
27
|
+
|
28
|
+
store = Confection::Store.new
|
29
|
+
|
30
|
+
dsl = Confection::DSL.new(store)
|
31
|
+
|
32
|
+
dsl.config :qed, :from=>'qed', :profile=>:simplecov do
|
33
|
+
# additional code here
|
34
|
+
end
|
35
|
+
|
36
|
+
store.size.assert == 2
|
37
|
+
|
38
|
+
Technically this last form just creates two configurations for the same
|
39
|
+
tool and profile, but the ultimate effect is the same.
|
40
|
+
|
41
|
+
## Script Importing
|
42
|
+
|
43
|
+
Library files can be imported directly into configuration blocks via the
|
44
|
+
`#import` method.
|
45
|
+
|
46
|
+
dsl.config :example do
|
47
|
+
import "fruitbasket/example.rb"
|
48
|
+
end
|
49
|
+
|
50
|
+
This looks up the file via the `finder` gem and then evals it in the context
|
51
|
+
of the config block.
|
52
|
+
|
data/spec/03_store.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
## Store
|
2
|
+
|
3
|
+
A Confection::Store encapsulates the list of configurations that belong
|
4
|
+
to a project.
|
5
|
+
|
6
|
+
store = Confection::Store.new
|
7
|
+
|
8
|
+
Only Config instance can be added to a store. Any other type of object
|
9
|
+
will raise an error.
|
10
|
+
|
11
|
+
expect TypeError do
|
12
|
+
store << 1
|
13
|
+
end
|
14
|
+
|
15
|
+
We can get a list of profiles for a given tool.
|
16
|
+
|
17
|
+
profiles = store.profiles(:text)
|
18
|
+
|
19
|
+
profiles.assert == []
|
20
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Controller
|
2
|
+
|
3
|
+
When working with configurations, Confection wraps configurations in a
|
4
|
+
Controller instance.
|
5
|
+
|
6
|
+
An empty controller can be made simply enough.
|
7
|
+
|
8
|
+
Confection::Controller.new(self, *[])
|
9
|
+
|
10
|
+
The #confection method simple calls this with the configs it finds.
|
11
|
+
|
12
|
+
ctrl = config(:block)
|
13
|
+
|
14
|
+
Confection::Controller.assert === ctrl
|
15
|
+
|
16
|
+
A controller will most oftern encapsulate just a sigle configuration,
|
17
|
+
but it may contain more if the search parameters used to create it
|
18
|
+
returned more than one matching configuration. We can see how many
|
19
|
+
configurations the controller is harnessing using the `#size` method.
|
20
|
+
|
21
|
+
ctrl.size.assert == 1
|
22
|
+
|
23
|
+
With the controller, we can utilize the configuration(s) it encapsulates
|
24
|
+
through the handful of methods it provides for doing so. Probably the
|
25
|
+
most useful, which applies to Ruby-based configurations is the `#call`
|
26
|
+
methods. This will execute the configuration script in the context
|
27
|
+
in which it was defined.
|
28
|
+
|
29
|
+
result = ctrl.call
|
30
|
+
|
31
|
+
result.assert == "example block config"
|
32
|
+
|
33
|
+
Instead of being executed in the context in which a configuration was
|
34
|
+
defined, it can be executed in the current scope using the #exec method.
|
35
|
+
|
36
|
+
result = ctrl.exec
|
37
|
+
|
38
|
+
result.assert == "example block config"
|
39
|
+
|
40
|
+
Of course, as this example only outputs a string, we won't notice any
|
41
|
+
difference here.
|
42
|
+
|
43
|
+
Lastly, script based configurations can be executed in the TOPLEVEL
|
44
|
+
context by using `#main_exec`, or its alias `#load`.
|
45
|
+
|
46
|
+
result = ctrl.main_exec
|
47
|
+
|
48
|
+
result.assert == "example block config"
|
49
|
+
|
50
|
+
The controller can be converted to a Proc object, in which case the
|
51
|
+
underlying execution is equivalent to using `#exec`. This allows
|
52
|
+
the procedure to be evaluated in other bindings as needed.
|
53
|
+
|
54
|
+
proc = ctrl.to_proc
|
55
|
+
|
56
|
+
result = proc.call
|
57
|
+
|
58
|
+
result.assert == "example block config"
|
59
|
+
|
60
|
+
For text configuration, the controller will concat each configuration's
|
61
|
+
content.
|
62
|
+
|
63
|
+
ctrl = config(:text)
|
64
|
+
|
65
|
+
text = ctrl.to_s
|
66
|
+
|
67
|
+
text.strip.assert == 'example text config'
|
68
|
+
|
data/spec/05_config.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Config Classes
|
2
|
+
|
3
|
+
There are three classes of configuration: test, data and block. Block
|
4
|
+
configurations encapsulate a block of Ruby code. Text configurations
|
5
|
+
simply contain a text string --whatever that string may represent.
|
6
|
+
Data configuration contantain a table of key-value pairs.
|
7
|
+
|
8
|
+
## Block Configuration
|
9
|
+
|
10
|
+
File configuration come from separate files rather then from definitions
|
11
|
+
in a project's master configuration file.
|
12
|
+
|
13
|
+
config = confection(:block).first
|
14
|
+
|
15
|
+
Confection::Config.assert === config
|
16
|
+
|
17
|
+
A Ruby-based configuration file, like our example, can be called via the `#call`
|
18
|
+
method. This evaluates the code at the TOPLEVEL, like standard `Kernel.load`
|
19
|
+
would.
|
20
|
+
|
21
|
+
result = config.call
|
22
|
+
|
23
|
+
result.assert == "example block config"
|
24
|
+
|
25
|
+
The call can also be converted into a Proc object via `#to_proc`. This uses
|
26
|
+
`instance_eval` internally, so that the Proc object can be evaluated in
|
27
|
+
any context it may be needed.
|
28
|
+
|
29
|
+
proc = config.to_proc
|
30
|
+
|
31
|
+
Proc.assert === proc
|
32
|
+
|
33
|
+
result = proc.call
|
34
|
+
|
35
|
+
result.strip.assert == "example block config"
|
36
|
+
|
37
|
+
|
38
|
+
## Text Configuraiton
|
39
|
+
|
40
|
+
Text-based configurations
|
41
|
+
|
42
|
+
config = confection(:text).first
|
43
|
+
|
44
|
+
Confection::Config.assert === config
|
45
|
+
|
46
|
+
result = config.to_s
|
47
|
+
|
48
|
+
result.strip.assert == "example text config"
|
49
|
+
|
50
|
+
For a text-based configuration `#call` does the same thing as `#to_s`.
|
51
|
+
|
52
|
+
result = config.call
|
53
|
+
|
54
|
+
result.strip.assert == "example text config"
|
55
|
+
|
56
|
+
As with the other configuration classes, we may also convert this call
|
57
|
+
into a Proc instance.
|
58
|
+
|
59
|
+
proc = config.to_proc
|
60
|
+
|
61
|
+
Proc.assert === proc
|
62
|
+
|
63
|
+
The configuration object can be copied, with a special `#copy` method
|
64
|
+
that accepts option parameters for changing the tool or profile of the copy.
|
65
|
+
|
66
|
+
alt = config.copy(:profile=>:alt)
|
67
|
+
|
68
|
+
alt.profile.assert == :alt
|
69
|
+
|
70
|
+
|
71
|
+
## Data Configuration
|
72
|
+
|
73
|
+
Data-based configuration.
|
74
|
+
|
75
|
+
config = confection(:example, :data).first
|
76
|
+
|
77
|
+
Confection::Config.assert === config
|
78
|
+
|
79
|
+
result = config.to_h
|
80
|
+
|
81
|
+
result.assert == {:name=>'Tommy', :age=>42 }
|
82
|
+
|
83
|
+
For a Data-based configuration `#call` does the same thing as `#to_data`.
|
84
|
+
|
85
|
+
data = OpenStruct.new
|
86
|
+
|
87
|
+
result = config.call(data)
|
88
|
+
|
89
|
+
data.to_h.assert == {:name=>'Tommy', :age=>42 }
|
90
|
+
|
91
|
+
As with the other configuration classes, we may also convert this call
|
92
|
+
into a Proc instance.
|
93
|
+
|
94
|
+
proc = config.to_proc
|
95
|
+
|
96
|
+
Proc.assert === proc
|
97
|
+
|
98
|
+
This just encapsulates the `#to_data` call in a lambda.
|
99
|
+
|
data/spec/06_manage.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Manage
|
2
|
+
|
3
|
+
The Confection module has some convenice class methods for working
|
4
|
+
with the configuration of the *current* project --the one relative
|
5
|
+
to the current working directory.
|
6
|
+
|
7
|
+
The current project root directory can be had via the `current_directory`
|
8
|
+
method.
|
9
|
+
|
10
|
+
Confection.current_directory
|
11
|
+
|
12
|
+
The Project instance can be had via the `current_project` method.
|
13
|
+
|
14
|
+
project = Confection.current_project
|
15
|
+
|
16
|
+
Confection::Project.assert === project
|
17
|
+
|
18
|
+
The configuration properties of the current project can be
|
19
|
+
had via the `properties` method.
|
20
|
+
|
21
|
+
Confection.properties
|
22
|
+
|
23
|
+
The profile names can be looked up for any given tool via the `profiles`
|
24
|
+
method.
|
25
|
+
|
26
|
+
Confection.profiles(:file)
|
27
|
+
|
28
|
+
The number of configurations in the current project can be had via
|
29
|
+
the `size` method. (This is the number of configurations we have
|
30
|
+
defined in our test fixture.)
|
31
|
+
|
32
|
+
Confection.size.assert == 3
|
33
|
+
|
34
|
+
And we can loop through each configuration via the `each` method.
|
35
|
+
|
36
|
+
Confection.each{ |c| c }
|
37
|
+
|
38
|
+
|
data/spec/applique/ae.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ae'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: confection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-03-16 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: finder
|
16
|
-
requirement: &
|
16
|
+
requirement: &69827679742240 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *69827679742240
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: facets
|
27
|
-
requirement: &
|
27
|
+
requirement: &69827679741700 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *69827679741700
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: blankslate
|
38
|
-
requirement: &
|
38
|
+
requirement: &69827679741200 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *69827679741200
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: detroit
|
49
|
-
requirement: &
|
49
|
+
requirement: &69827679740700 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *69827679740700
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: qed
|
60
|
-
requirement: &
|
60
|
+
requirement: &69827679740200 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *69827679740200
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: ae
|
71
|
-
requirement: &
|
71
|
+
requirement: &69827679739700 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *69827679739700
|
80
80
|
description: Confection is a multi-tenant configuration system for Ruby projects.
|
81
81
|
email:
|
82
82
|
- transfire@gmail.com
|
@@ -92,11 +92,28 @@ files:
|
|
92
92
|
- lib/confection/basic_object.rb
|
93
93
|
- lib/confection/config.rb
|
94
94
|
- lib/confection/controller.rb
|
95
|
+
- lib/confection/core_ext.rb
|
96
|
+
- lib/confection/current.rb
|
95
97
|
- lib/confection/dsl.rb
|
98
|
+
- lib/confection/hash_builder.rb
|
99
|
+
- lib/confection/project.rb
|
100
|
+
- lib/confection/store.rb
|
96
101
|
- lib/confection.rb
|
102
|
+
- spec/00_concept.md
|
103
|
+
- spec/01_dsl.md
|
104
|
+
- spec/02_import.md
|
105
|
+
- spec/03_store.md
|
106
|
+
- spec/04_controller.md
|
107
|
+
- spec/05_config.md
|
108
|
+
- spec/06_manage.md
|
109
|
+
- spec/applique/ae.rb
|
110
|
+
- spec/applique/file.rb
|
111
|
+
- spec/applique/fixture/confile.rb
|
112
|
+
- spec/applique/fixture.rb
|
97
113
|
- LICENSE.txt
|
98
114
|
- HISTORY.md
|
99
115
|
- README.md
|
116
|
+
- Confile.rb
|
100
117
|
homepage: http://rubyworks.github.com/confection
|
101
118
|
licenses:
|
102
119
|
- BSD-2-Clause
|