teapot 0.9.10 → 1.0.0.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -3
- data/README.md +3 -4
- data/Rakefile +3 -6
- data/bin/teapot +3 -7
- data/lib/teapot/build.rb +166 -18
- data/lib/teapot/configuration.rb +0 -1
- data/lib/teapot/context.rb +37 -21
- data/lib/teapot/controller/build.rb +24 -7
- data/lib/teapot/controller/fetch.rb +1 -1
- data/lib/teapot/controller.rb +1 -0
- data/lib/teapot/definition.rb +1 -1
- data/lib/teapot/dependency.rb +2 -6
- data/lib/teapot/environment/base.rb +7 -15
- data/lib/teapot/environment/constructor.rb +28 -0
- data/lib/teapot/environment/flatten.rb +42 -1
- data/lib/teapot/environment/system.rb +3 -3
- data/lib/teapot/extractors/linker_extractor.rb +2 -2
- data/lib/teapot/loader.rb +9 -11
- data/lib/teapot/name.rb +4 -0
- data/lib/teapot/package.rb +5 -4
- data/lib/teapot/repository.rb +29 -1
- data/lib/teapot/rule.rb +196 -0
- data/lib/teapot/rulebook.rb +91 -0
- data/lib/teapot/target.rb +15 -40
- data/lib/teapot/version.rb +1 -1
- data/{lib/teapot/build/targets/application.rb → spec/teapot/build_spec.rb} +26 -29
- data/{test/test_teapot.rb → spec/teapot/context_spec.rb} +13 -13
- data/spec/teapot/dependency_spec.rb +113 -0
- data/spec/teapot/environment_spec.rb +91 -0
- data/{lib/teapot/build/graph.rb → spec/teapot/name_spec.rb} +26 -26
- data/{test/test_substitutions.rb → spec/teapot/substitutions_spec.rb} +36 -36
- data/{test → spec/teapot}/teapot.rb +1 -1
- data/teapot.gemspec +16 -9
- metadata +98 -51
- data/lib/teapot/build/component.rb +0 -69
- data/lib/teapot/build/file_list.rb +0 -67
- data/lib/teapot/build/linker.rb +0 -49
- data/lib/teapot/build/target.rb +0 -76
- data/lib/teapot/build/targets/compiler.rb +0 -83
- data/lib/teapot/build/targets/directory.rb +0 -63
- data/lib/teapot/build/targets/executable.rb +0 -56
- data/lib/teapot/build/targets/external.rb +0 -91
- data/lib/teapot/build/targets/files.rb +0 -82
- data/lib/teapot/build/targets/library.rb +0 -117
- data/lib/teapot/commands.rb +0 -139
- data/lib/teapot/controller/run.rb +0 -43
- data/lib/teapot/graph.rb +0 -136
- data/test/test_dependency.rb +0 -112
- data/test/test_environment.rb +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5f2d266ab25fcae9ab8d5582147d0a74d05d747
|
4
|
+
data.tar.gz: 202f83855d31f83ac023107ef3e123e95af83795
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f3c17b8a55d5aab3a5adb1061d518fd45da2bef8a31471b8e7797169345e6a7fec9cb73441d6cf922e1efb16cbdc1fc8f57e8371866c9aa64c2ac019f373f98
|
7
|
+
data.tar.gz: 50318c19defce391b36cd24056d987eeb9f6c9a1f3c61df53ee53dd8e44fd6484b8effa576cf8791467178a77b96c6dd334d073e92eb522094b6512643b552b5
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -60,9 +60,8 @@ You need to make sure any basic tools, e.g. compilers, system libraries, are ins
|
|
60
60
|
|
61
61
|
## Dependency Graph
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
Teapot assumes per-environment dependency graphs and in addition, parts of the dependency graph are generated dynamically. The process of extracting implicit dependencies can be found under `teapot/extractors` and is used extensively in the build graph `teapot/build`.
|
63
|
+
- Should packages be built into a shared prefix or should they be built into unique prefixes and joined together either via install or `-L` and `-I`?
|
64
|
+
- Should packages expose the tools required to build themselves as dependencies? e.g. should `build-cmake` as required by, say, `OpenCV`, be exposed to all who depend on `OpenCV`? Should there be a mechanism for non-public dependencies, i.e. dependencies which are not exposed to dependants?
|
66
65
|
|
67
66
|
## Contributing
|
68
67
|
|
@@ -76,7 +75,7 @@ Teapot assumes per-environment dependency graphs and in addition, parts of the d
|
|
76
75
|
|
77
76
|
Released under the MIT license.
|
78
77
|
|
79
|
-
Copyright, 2012, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
78
|
+
Copyright, 2012, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
80
79
|
|
81
80
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
82
81
|
of this software and associated documentation files (the "Software"), to deal
|
data/Rakefile
CHANGED
data/bin/teapot
CHANGED
@@ -27,7 +27,6 @@ require 'teapot/controller/create'
|
|
27
27
|
require 'teapot/controller/fetch'
|
28
28
|
require 'teapot/controller/generate'
|
29
29
|
require 'teapot/controller/list'
|
30
|
-
require 'teapot/controller/run'
|
31
30
|
require 'teapot/controller/visualize'
|
32
31
|
|
33
32
|
require 'teapot/repository'
|
@@ -39,6 +38,8 @@ OPTIONS = Trollop::options do
|
|
39
38
|
version "teapot v#{Teapot::VERSION}"
|
40
39
|
|
41
40
|
opt :only, "Only compiled direct dependencies."
|
41
|
+
opt :continuous, "Run the build graph continually.", :type => :boolean
|
42
|
+
|
42
43
|
opt :in, "Work in the given directory.", :type => :string
|
43
44
|
opt :unlock, "Don't use package lockfile when fetching."
|
44
45
|
|
@@ -67,11 +68,7 @@ module Application
|
|
67
68
|
def self.build(targets = ARGV)
|
68
69
|
make_controller.build(targets)
|
69
70
|
end
|
70
|
-
|
71
|
-
def self.run(targets = ARGV)
|
72
|
-
make_controller.run(targets)
|
73
|
-
end
|
74
|
-
|
71
|
+
|
75
72
|
def self.list(only = ARGV)
|
76
73
|
if only.size > 0
|
77
74
|
make_controller.list(Set.new(only))
|
@@ -114,7 +111,6 @@ def track_time
|
|
114
111
|
start_time = Time.now
|
115
112
|
|
116
113
|
yield
|
117
|
-
|
118
114
|
ensure
|
119
115
|
end_time = Time.now
|
120
116
|
elapsed_time = end_time - start_time
|
data/lib/teapot/build.rb
CHANGED
@@ -18,34 +18,182 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'teapot/
|
22
|
-
|
23
|
-
require '
|
24
|
-
require '
|
25
|
-
require '
|
26
|
-
|
21
|
+
require 'teapot/rulebook'
|
22
|
+
|
23
|
+
require 'build/files'
|
24
|
+
require 'build/graph'
|
25
|
+
require 'build/makefile'
|
26
|
+
|
27
|
+
require 'teapot/name'
|
28
|
+
|
29
|
+
require 'process/group'
|
30
|
+
require 'system'
|
27
31
|
|
28
32
|
module Teapot
|
29
33
|
module Build
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
Graph = ::Build::Graph
|
35
|
+
Files = ::Build::Files
|
36
|
+
Paths = ::Build::Files::Paths
|
37
|
+
Makefile = ::Build::Makefile
|
33
38
|
|
34
|
-
|
35
|
-
def
|
36
|
-
|
39
|
+
class Node < Graph::Node
|
40
|
+
def initialize(controller, rule, arguments, &block)
|
41
|
+
@arguments = arguments
|
42
|
+
@rule = rule
|
43
|
+
|
44
|
+
@callback = block
|
45
|
+
|
46
|
+
inputs, outputs = rule.files(arguments)
|
47
|
+
|
48
|
+
super(controller, inputs, outputs)
|
49
|
+
end
|
50
|
+
|
51
|
+
attr :arguments
|
52
|
+
attr :rule
|
53
|
+
attr :callback
|
54
|
+
|
55
|
+
def hash
|
56
|
+
[@rule.name, @arguments].hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def eql?(other)
|
60
|
+
other.kind_of?(self.class) and @rule.eql?(other.rule) and @arguments.eql?(other.arguments)
|
61
|
+
end
|
62
|
+
|
63
|
+
def apply!(scope)
|
64
|
+
@rule.apply!(scope, @arguments)
|
65
|
+
|
66
|
+
if @callback
|
67
|
+
scope.instance_exec(@arguments, &@callback)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
37
71
|
|
38
|
-
|
72
|
+
class Top < Graph::Node
|
73
|
+
def initialize(controller, task_class, &update)
|
74
|
+
@update = update
|
75
|
+
@task_class = task_class
|
76
|
+
|
77
|
+
super(controller, Paths::NONE, Paths::NONE)
|
78
|
+
end
|
79
|
+
|
80
|
+
attr :task_class
|
81
|
+
|
82
|
+
def apply!(scope)
|
83
|
+
scope.instance_exec(&@update)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Top level nodes are always considered dirty. This ensures that enclosed nodes are run if they are dirty. The top level node has no inputs or outputs by default, so children who become dirty wouldn't mark it as dirty and thus wouldn't be run.
|
87
|
+
def requires_update?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
end
|
39
91
|
|
40
|
-
|
92
|
+
class Task < Graph::Task
|
93
|
+
def initialize(controller, walker, node, group = nil)
|
94
|
+
super(controller, walker, node)
|
95
|
+
|
96
|
+
@group = group
|
97
|
+
|
98
|
+
if wet?
|
99
|
+
#@file_system = FileUtils
|
100
|
+
@file_system = FileUtils::Verbose
|
101
|
+
else
|
102
|
+
@file_system = FileUtils::NoWrite
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
attr :file_system
|
107
|
+
alias fs file_system
|
108
|
+
|
109
|
+
def wet?
|
110
|
+
@group && @node.requires_update?
|
111
|
+
end
|
112
|
+
|
113
|
+
def update(rule, arguments, &block)
|
114
|
+
arguments = rule.normalize(arguments)
|
115
|
+
|
116
|
+
# A sub-graph for a particular build is isolated based on the task class used to instantiate it, so we use this as part of the key.
|
117
|
+
child_node = @controller.nodes.fetch([self.class, rule.name, arguments]) do |key|
|
118
|
+
@controller.nodes[key] = Node.new(@controller, rule, arguments, &block)
|
119
|
+
end
|
120
|
+
|
121
|
+
@children << child_node
|
122
|
+
|
123
|
+
child_node.update!(@walker)
|
124
|
+
|
125
|
+
return child_node.rule.result(arguments)
|
126
|
+
end
|
127
|
+
|
128
|
+
def run!(*arguments)
|
129
|
+
if wet?
|
130
|
+
# puts Rainbow("Scheduling #{arguments.inspect}").blue
|
131
|
+
status = @group.spawn(*arguments)
|
132
|
+
# puts Rainbow("Finished #{arguments.inspect} with status #{status}").blue
|
133
|
+
|
134
|
+
if status != 0
|
135
|
+
raise Graph::CommandFailure.new(arguments, status)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def visit
|
141
|
+
super do
|
142
|
+
@node.apply!(self)
|
143
|
+
end
|
41
144
|
end
|
145
|
+
end
|
42
146
|
|
43
|
-
|
44
|
-
|
147
|
+
class Controller < Graph::Controller
|
148
|
+
def initialize
|
149
|
+
@module = Module.new
|
150
|
+
|
151
|
+
@top = []
|
152
|
+
|
153
|
+
yield self
|
154
|
+
|
155
|
+
@top.freeze
|
156
|
+
|
157
|
+
@task_class = nil
|
158
|
+
|
159
|
+
super()
|
160
|
+
end
|
45
161
|
|
46
|
-
|
162
|
+
attr :top
|
163
|
+
|
164
|
+
# Because we do a depth first traversal, we can capture global state per branch, such as `@task_class`.
|
165
|
+
def traverse!(walker)
|
166
|
+
@top.each do |node|
|
167
|
+
# Capture the task class for each top level node:
|
168
|
+
@task_class = node.task_class
|
169
|
+
|
170
|
+
node.update!(walker)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def add_target(target, environment, &block)
|
175
|
+
task_class = Rulebook.for(environment).with(Task, environment: environment, target: target)
|
176
|
+
|
177
|
+
# Not sure if this is a good idea - makes debugging slightly easier.
|
178
|
+
Object.const_set("TaskClassFor#{Name.from_target(target.name).identifier}_#{self.object_id}", task_class)
|
179
|
+
|
180
|
+
@top << Top.new(self, task_class, &target.build)
|
181
|
+
end
|
182
|
+
|
183
|
+
def build_graph!
|
184
|
+
super do |walker, node|
|
185
|
+
@task_class.new(self, walker, node)
|
186
|
+
end
|
187
|
+
end
|
47
188
|
|
48
|
-
|
189
|
+
def update!
|
190
|
+
group = Process::Group.new
|
191
|
+
|
192
|
+
super do |walker, node|
|
193
|
+
@task_class.new(self, walker, node, group)
|
194
|
+
end
|
195
|
+
|
196
|
+
group.wait
|
49
197
|
end
|
50
198
|
end
|
51
199
|
end
|
data/lib/teapot/configuration.rb
CHANGED
data/lib/teapot/context.rb
CHANGED
@@ -21,9 +21,11 @@
|
|
21
21
|
require 'teapot/loader'
|
22
22
|
require 'teapot/package'
|
23
23
|
|
24
|
+
require 'teapot/rulebook'
|
25
|
+
|
24
26
|
module Teapot
|
25
|
-
TEAPOT_FILE =
|
26
|
-
DEFAULT_CONFIGURATION_NAME = 'default'
|
27
|
+
TEAPOT_FILE = 'teapot.rb'.freeze
|
28
|
+
DEFAULT_CONFIGURATION_NAME = 'default'.freeze
|
27
29
|
|
28
30
|
class AlreadyDefinedError < StandardError
|
29
31
|
def initialize(definition, previous)
|
@@ -39,33 +41,23 @@ module Teapot
|
|
39
41
|
|
40
42
|
class Context
|
41
43
|
def initialize(root, options = {})
|
42
|
-
@root =
|
44
|
+
@root = Path[root]
|
43
45
|
@options = options
|
44
46
|
|
45
47
|
@targets = {}
|
46
48
|
@generators = {}
|
47
49
|
@configurations = {}
|
48
50
|
@projects = {}
|
51
|
+
@rules = Rulebook.new
|
49
52
|
|
50
53
|
@dependencies = []
|
51
54
|
@selection = Set.new
|
52
55
|
|
53
56
|
@loaded = {}
|
54
57
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# Find the default configuration, if it exists:
|
59
|
-
@default_configuration = defined.default_configuration
|
60
|
-
|
61
|
-
if options[:configuration]
|
62
|
-
@configuration = @configurations[options[:configuration]]
|
63
|
-
else
|
64
|
-
@configuration = @default_configuration
|
58
|
+
unless options[:fake]
|
59
|
+
load_root_package(options)
|
65
60
|
end
|
66
|
-
|
67
|
-
# Materialize the configuration:
|
68
|
-
@configuration.materialize if @configuration
|
69
61
|
end
|
70
62
|
|
71
63
|
attr :root
|
@@ -78,6 +70,8 @@ module Teapot
|
|
78
70
|
# All public configurations.
|
79
71
|
attr :configurations
|
80
72
|
|
73
|
+
attr :rules
|
74
|
+
|
81
75
|
# The context's primary configuration.
|
82
76
|
attr :configuration
|
83
77
|
|
@@ -111,6 +105,7 @@ module Teapot
|
|
111
105
|
end.compact
|
112
106
|
end
|
113
107
|
|
108
|
+
# Add a definition to the current context.
|
114
109
|
def << definition
|
115
110
|
case definition
|
116
111
|
when Target
|
@@ -139,6 +134,10 @@ module Teapot
|
|
139
134
|
@project ||= definition
|
140
135
|
|
141
136
|
@projects[definition.name] = definition
|
137
|
+
when Rule
|
138
|
+
AlreadyDefinedError.check(definition, @rules)
|
139
|
+
|
140
|
+
@rules << definition
|
142
141
|
end
|
143
142
|
end
|
144
143
|
|
@@ -146,9 +145,9 @@ module Teapot
|
|
146
145
|
# In certain cases, a package record might be loaded twice. This typically occurs when multiple configurations are loaded in the same context, or if a package has already been loaded (as is typical with the root package).
|
147
146
|
@loaded.fetch(package) do
|
148
147
|
loader = Loader.new(self, package)
|
149
|
-
|
148
|
+
|
150
149
|
loader.load(TEAPOT_FILE)
|
151
|
-
|
150
|
+
|
152
151
|
# Load the definitions into the current context:
|
153
152
|
loader.defined.each do |definition|
|
154
153
|
self << definition
|
@@ -173,12 +172,29 @@ module Teapot
|
|
173
172
|
|
174
173
|
return failed_to_load
|
175
174
|
end
|
176
|
-
|
177
|
-
private
|
178
|
-
|
175
|
+
|
179
176
|
# The root package is a special package which is used to load definitions from a given root path.
|
180
177
|
def root_package
|
181
178
|
@root_package ||= Package.new(@root, "root")
|
182
179
|
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def load_root_package(options)
|
184
|
+
# Load the root package:
|
185
|
+
defined = load(root_package)
|
186
|
+
|
187
|
+
# Find the default configuration, if it exists:
|
188
|
+
@default_configuration = defined.default_configuration
|
189
|
+
|
190
|
+
if options[:configuration]
|
191
|
+
@configuration = @configurations[options[:configuration]]
|
192
|
+
else
|
193
|
+
@configuration = @default_configuration
|
194
|
+
end
|
195
|
+
|
196
|
+
# Materialize the configuration:
|
197
|
+
@configuration.materialize if @configuration
|
198
|
+
end
|
183
199
|
end
|
184
200
|
end
|
@@ -19,6 +19,7 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require 'teapot/controller'
|
22
|
+
require 'teapot/build'
|
22
23
|
|
23
24
|
module Teapot
|
24
25
|
class Controller
|
@@ -30,16 +31,32 @@ module Teapot
|
|
30
31
|
if @options[:only]
|
31
32
|
ordered = context.direct_targets(ordered)
|
32
33
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
|
35
|
+
controller = Teapot::Build::Controller.new do |controller|
|
36
|
+
ordered.each do |(target, dependency)|
|
37
|
+
environment = target.environment_for_configuration(context.configuration)
|
37
38
|
|
38
|
-
target.build
|
39
|
+
if target.build
|
40
|
+
controller.add_target(target, environment.flatten)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
controller.run do
|
46
|
+
# The graph has been dirtied because files have changed, traverse and update it:
|
47
|
+
controller.update_with_log
|
48
|
+
|
49
|
+
# Only run once is asked:
|
50
|
+
unless @options[:continuous]
|
51
|
+
break
|
52
|
+
end
|
53
|
+
|
54
|
+
if $TEAPOT_DEBUG_GRAPH
|
55
|
+
controller.nodes.each do |key, node|
|
56
|
+
puts "#{node.status} #{node.inspect}"# unless node.clean?
|
57
|
+
end
|
39
58
|
end
|
40
59
|
end
|
41
|
-
|
42
|
-
log "Completed build successfully.".color(:green)
|
43
60
|
|
44
61
|
return chain, ordered
|
45
62
|
end
|
@@ -28,7 +28,7 @@ module Teapot
|
|
28
28
|
configuration = context.configuration
|
29
29
|
unresolved = context.unresolved(configuration.packages)
|
30
30
|
tries = 0
|
31
|
-
|
31
|
+
|
32
32
|
while tries < @options[:maximum_fetch_depth]
|
33
33
|
configuration.packages.each do |package|
|
34
34
|
next if resolved.include? package
|
data/lib/teapot/controller.rb
CHANGED
data/lib/teapot/definition.rb
CHANGED
data/lib/teapot/dependency.rb
CHANGED
@@ -45,16 +45,12 @@ module Teapot
|
|
45
45
|
if String === name_or_aliases || Symbol === name_or_aliases
|
46
46
|
name = name_or_aliases
|
47
47
|
|
48
|
-
|
49
|
-
provisions[name] = Provision.new(block)
|
50
|
-
else
|
51
|
-
provisions[name] = Provision.new(nil)
|
52
|
-
end
|
48
|
+
provisions[name] = Provision.new(block)
|
53
49
|
else
|
54
50
|
aliases = name_or_aliases
|
55
51
|
|
56
52
|
aliases.each do |name, dependencies|
|
57
|
-
provisions[name] = Alias.new(Array
|
53
|
+
provisions[name] = Alias.new(Array(dependencies))
|
58
54
|
end
|
59
55
|
end
|
60
56
|
end
|
@@ -19,10 +19,10 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
module Teapot
|
22
|
-
# This is the basic environment data structure which is essentially a linked list of hashes. It is primarily used for organising build configurations across a wide range of different sub-systems, e.g. platform configuration, target configuration, local project configuration, etc.
|
22
|
+
# This is the basic environment data structure which is essentially a linked list of hashes. It is primarily used for organising build configurations across a wide range of different sub-systems, e.g. platform configuration, target configuration, local project configuration, etc.
|
23
23
|
class Environment
|
24
|
-
def initialize(parent = nil, values =
|
25
|
-
@values = (values || {}).
|
24
|
+
def initialize(parent = nil, values = nil, &block)
|
25
|
+
@values = (values || {}).to_h
|
26
26
|
@parent = parent
|
27
27
|
|
28
28
|
if block_given?
|
@@ -30,6 +30,10 @@ module Teapot
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
def self.hash(**values)
|
34
|
+
self.new(nil, values)
|
35
|
+
end
|
36
|
+
|
33
37
|
attr :values
|
34
38
|
attr :parent
|
35
39
|
|
@@ -51,20 +55,8 @@ module Teapot
|
|
51
55
|
@values[key] = value
|
52
56
|
end
|
53
57
|
|
54
|
-
def to_hash
|
55
|
-
@values
|
56
|
-
end
|
57
|
-
|
58
58
|
def to_s
|
59
59
|
"<#{self.class} #{self.values}>"
|
60
60
|
end
|
61
|
-
|
62
|
-
def inspect(output = $stdout, indent = "")
|
63
|
-
@values.each do |(key, value)|
|
64
|
-
output.puts "#{indent}#{key}: #{value}"
|
65
|
-
end
|
66
|
-
|
67
|
-
@parent.inspect(output, indent + "\t") if @parent
|
68
|
-
end
|
69
61
|
end
|
70
62
|
end
|
@@ -23,6 +23,20 @@ module Teapot
|
|
23
23
|
Default = Struct.new(:value)
|
24
24
|
Replace = Struct.new(:value)
|
25
25
|
|
26
|
+
class Define
|
27
|
+
def initialize(klass, &block)
|
28
|
+
@klass = klass
|
29
|
+
@block = block
|
30
|
+
end
|
31
|
+
|
32
|
+
attr :klass
|
33
|
+
attr :block
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"<#{@klass.name} #{@block.source_location.join(':')}>"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
26
40
|
class Constructor
|
27
41
|
def initialize(environment)
|
28
42
|
@environment = environment
|
@@ -44,14 +58,28 @@ module Teapot
|
|
44
58
|
|
45
59
|
def default(name)
|
46
60
|
@environment[name] = Default.new(@environment[name])
|
61
|
+
|
62
|
+
return name
|
47
63
|
end
|
48
64
|
|
49
65
|
def replace(name)
|
50
66
|
@environment[name] = Replace.new(@environment[name])
|
67
|
+
|
68
|
+
return name
|
51
69
|
end
|
52
70
|
|
53
71
|
def append(name)
|
54
72
|
@environment[name] = Array(@environment[name])
|
73
|
+
|
74
|
+
return name
|
75
|
+
end
|
76
|
+
|
77
|
+
def define(klass, name, &block)
|
78
|
+
abort "#{name} isn't a string when defining #{klass}" unless String === name
|
79
|
+
|
80
|
+
@environment[name] = Define.new(klass, &block)
|
81
|
+
|
82
|
+
return name
|
55
83
|
end
|
56
84
|
end
|
57
85
|
|
@@ -18,9 +18,15 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'digest/md5'
|
22
|
+
|
21
23
|
module Teapot
|
22
24
|
class Environment
|
23
|
-
def
|
25
|
+
def to_h
|
26
|
+
@values
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
24
30
|
hash = {}
|
25
31
|
|
26
32
|
# Flatten this chain of environments:
|
@@ -33,8 +39,43 @@ module Teapot
|
|
33
39
|
Hash[hash.map{|key, value| [key, evaluator.object_value(value)]}]
|
34
40
|
end
|
35
41
|
|
42
|
+
def flatten
|
43
|
+
self.class.new(nil, self.to_hash)
|
44
|
+
end
|
45
|
+
|
46
|
+
def defined
|
47
|
+
@values.select{|name,value| Define === value}
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect(output = $stdout, indent = "")
|
51
|
+
@values.each do |(key, value)|
|
52
|
+
output.puts "#{indent}#{key}: #{value}"
|
53
|
+
end
|
54
|
+
|
55
|
+
@parent.inspect(output, indent + "\t") if @parent
|
56
|
+
end
|
57
|
+
|
58
|
+
# This should be stable within environments that produce the same results.
|
59
|
+
def checksum
|
60
|
+
digester = Digest::MD5.new
|
61
|
+
|
62
|
+
checksum_recursively(digester)
|
63
|
+
|
64
|
+
return digester.hexdigest
|
65
|
+
end
|
66
|
+
|
36
67
|
protected
|
37
68
|
|
69
|
+
def checksum_recursively(digester)
|
70
|
+
@values.each do |(key, value)|
|
71
|
+
digester.update(key.to_s)
|
72
|
+
digester.update(value.to_s)
|
73
|
+
end
|
74
|
+
|
75
|
+
@parent.checksum_recursively(digester) if @parent
|
76
|
+
end
|
77
|
+
|
78
|
+
# We fold in the ancestors one at a time from oldest to youngest.
|
38
79
|
def flatten_to_hash(hash)
|
39
80
|
if @parent
|
40
81
|
@parent.flatten_to_hash(hash)
|