sparkle_formation 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -5
- data/LICENSE +1 -1
- data/README.md +36 -181
- data/lib/sparkle_formation/error.rb +25 -0
- data/lib/sparkle_formation/sparkle.rb +344 -0
- data/lib/sparkle_formation/sparkle_attribute.rb +34 -19
- data/lib/sparkle_formation/sparkle_collection.rb +149 -0
- data/lib/sparkle_formation/sparkle_formation.rb +302 -174
- data/lib/sparkle_formation/sparkle_struct.rb +0 -17
- data/lib/sparkle_formation/utils.rb +0 -18
- data/lib/sparkle_formation/version.rb +1 -19
- data/lib/sparkle_formation.rb +4 -0
- data/sparkle_formation.gemspec +3 -2
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40ae36e7f30ce66c8f2866c246b873014effd6e1
|
4
|
+
data.tar.gz: c542ab9daceb4df19c8dfc4b50f1a1f8ac68090e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f75fa1dd08bc0438ffb2a92fa59a157c4d045a09423fd782ac33f92bdfaab3b10172b4697aa4bb31071d7901a7e99c86ea52789c238c3553b18b02278730e405
|
7
|
+
data.tar.gz: 2deadc43737f86a136dd7de4169d88cd7c5afefcf065685fb64c385b63835f379f6453c4d5a00993cd4f45aa50400b9fa99f6b98f64ba6fe14ec99c84df2b871
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
##
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
## v1.0.0
|
2
|
+
|
3
|
+
> NOTE: This is a major version release. It includes multiple
|
4
|
+
> implementation updates that may cause breakage.
|
5
|
+
|
6
|
+
* Add SparklePacks for isolation and distribution
|
7
|
+
* Add SparkleFormation.component method for defining components
|
8
|
+
* Support fully recursive nesting and parameter / output mapping
|
9
|
+
* Support previous nesting style (shallow) and new style (deep)
|
10
|
+
* Include support for in-line stack policy extraction
|
6
11
|
|
7
12
|
## v0.3.0
|
8
13
|
* Update `or!` helper method to take multiple arguments
|
data/LICENSE
CHANGED
@@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work.
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
187
187
|
identification within third-party archives.
|
188
188
|
|
189
|
-
Copyright
|
189
|
+
Copyright 2015 Chris Roberts
|
190
190
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
192
192
|
you may not use this file except in compliance with the License.
|
data/README.md
CHANGED
@@ -1,26 +1,40 @@
|
|
1
|
+
![SparkleFormation](img/sparkle-formation.png)
|
2
|
+
|
1
3
|
# SparkleFormation
|
2
4
|
|
3
|
-
|
5
|
+
Orchestration template building tools for Ruby.
|
4
6
|
|
5
7
|
## What's it do?
|
6
8
|
|
7
|
-
Provides a very loose DSL to describe
|
8
|
-
in Ruby.
|
9
|
+
Provides a very loose DSL to describe orchestration API templates
|
10
|
+
programmatically in Ruby.
|
9
11
|
|
10
12
|
## Is that it?
|
11
13
|
|
12
14
|
Yes. Well, kinda. It also has some extra features, like defining
|
13
|
-
|
15
|
+
building blocks to facilitate code reuse in template creation,
|
16
|
+
helper functions for commonly generated data structures, builtin
|
17
|
+
logic for handling template nesting, and most importantly:
|
14
18
|
conjouring magic (to get unicorns).
|
15
19
|
|
16
|
-
##
|
20
|
+
## Documentation
|
21
|
+
|
22
|
+
* [Library Documentation](https://sparkleformation.github.io/sparkle_formation)
|
23
|
+
* [User Documentation](https://sparkleformation.github.io/sparkle_formation/UserDocs)
|
24
|
+
|
25
|
+
## Overview
|
17
26
|
|
18
|
-
|
27
|
+
Many template orchestration APIs accept serialized templates defining
|
28
|
+
infrastructure resources and configurations. Interacting directly with
|
29
|
+
these services via data serialization formats (JSON, YAML, etc) can be
|
30
|
+
difficult for humans. SparkleFormation allows humans to programmatically
|
31
|
+
define templates in Ruby. These are then exported into the desired
|
32
|
+
serialization format, ready to send to the target orchestration API.
|
19
33
|
|
20
34
|
## What's it look like?
|
21
35
|
|
22
|
-
|
23
|
-
|
36
|
+
Below is a simple example of an AWS CloudFormation template defined within
|
37
|
+
SparkleFormation. It creates a single EC2 resource:
|
24
38
|
|
25
39
|
```ruby
|
26
40
|
SparkleFormation.new('ec2_example') do
|
@@ -44,7 +58,7 @@ SparkleFormation.new('ec2_example') do
|
|
44
58
|
dynamic!(:ec2_instance, :foobar) do
|
45
59
|
properties do
|
46
60
|
key_name ref!(:key_name)
|
47
|
-
image_id map!(:region_map,
|
61
|
+
image_id map!(:region_map, region!, :ami)
|
48
62
|
user_data base64!('80')
|
49
63
|
end
|
50
64
|
end
|
@@ -79,7 +93,7 @@ end
|
|
79
93
|
```
|
80
94
|
|
81
95
|
And once compiled we get a nice Hash that we can then convert to JSON which
|
82
|
-
is ready
|
96
|
+
is ready to send to the AWS CloudFormation API:
|
83
97
|
|
84
98
|
```ruby
|
85
99
|
require 'sparkle_formation'
|
@@ -92,184 +106,25 @@ puts JSON.pretty_generate(
|
|
92
106
|
|
93
107
|
Easy!
|
94
108
|
|
95
|
-
##
|
96
|
-
|
97
|
-
Because, who in their right mind would want to write all of that in JSON? Also,
|
98
|
-
we can start applying some of the underlying features in `SparkleFormation` to
|
99
|
-
make this easier to maintain.
|
100
|
-
|
101
|
-
# Components
|
102
|
-
|
103
|
-
Lets say we have a handful of CFN templates we want to maintain, and all of those
|
104
|
-
templates use the same AMI. Instead of copying that information into all the
|
105
|
-
templates, lets create an AMI component instead, and then load it into the actual
|
106
|
-
templates.
|
107
|
-
|
108
|
-
First, create the component (components/ami.rb):
|
109
|
-
|
110
|
-
```ruby
|
111
|
-
SparkleFormation.build do
|
112
|
-
|
113
|
-
mappings.region_map do
|
114
|
-
set!('us-east-1', :ami => 'ami-7f418316')
|
115
|
-
set!('us-east-1', :ami => 'ami-7f418316')
|
116
|
-
set!('us-west-1', :ami => 'ami-951945d0')
|
117
|
-
set!('us-west-2', :ami => 'ami-16fd7026')
|
118
|
-
set!('eu-west-1', :ami => 'ami-24506250')
|
119
|
-
set!('sa-east-1', :ami => 'ami-3e3be423')
|
120
|
-
set!('ap-southeast-1', :ami => 'ami-74dda626')
|
121
|
-
set!('ap-northeast-1', :ami => 'ami-dcfa4edd')
|
122
|
-
end
|
123
|
-
|
124
|
-
end
|
125
|
-
```
|
126
|
-
|
127
|
-
Now, we can modify our initial example to use this component (ec2_example.rb):
|
128
|
-
|
129
|
-
```ruby
|
130
|
-
SparkleFormation.new('ec2_example').load(:ami).overrides do
|
131
|
-
|
132
|
-
description "AWS CloudFormation Sample Template ..."
|
133
|
-
|
134
|
-
parameters.key_name do
|
135
|
-
description 'Name of EC2 key pair'
|
136
|
-
type 'String'
|
137
|
-
end
|
138
|
-
|
139
|
-
dynamic!(:ec2_instance, :foobar) do
|
140
|
-
properties do
|
141
|
-
key_name ref!(:key_name)
|
142
|
-
image_id map!(:region_map, 'AWS::Region', :ami)
|
143
|
-
user_data base64!('80')
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
outputs do
|
148
|
-
instance_id do
|
149
|
-
description 'InstanceId of the newly created EC2 instance'
|
150
|
-
value ref!(:foobar_ec2_instance)
|
151
|
-
end
|
152
|
-
az do
|
153
|
-
description 'Availability Zone of the newly created EC2 instance'
|
154
|
-
value attr!(:foobar_ec2_instance, :availability_zone)
|
155
|
-
end
|
156
|
-
public_ip do
|
157
|
-
description 'Public IP address of the newly created EC2 instance'
|
158
|
-
value attr!(:foobar_ec2_instance, :public_ip)
|
159
|
-
end
|
160
|
-
private_ip do
|
161
|
-
description 'Private IP address of the newly created EC2 instance'
|
162
|
-
value attr!(:foobar_ec2_instance, :private_ip)
|
163
|
-
end
|
164
|
-
public_dns do
|
165
|
-
description 'Public DNSName of the newly created EC2 instance'
|
166
|
-
value attr!(:foobar_ec2_instance, :public_dns_name)
|
167
|
-
end
|
168
|
-
private_dns do
|
169
|
-
description 'Private DNSName of the newly created EC2 instance'
|
170
|
-
value attr!(:foobar_ec2_instance, :private_dns_name)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
174
|
-
```
|
175
|
-
|
176
|
-
Now a few things have changed. Instead of passing a block directly to the
|
177
|
-
instance instantiation, we are loading a component (the `ami` component)
|
178
|
-
into the formation, and then applying an override block on top of the `ami`
|
179
|
-
component. The result is the same as the initial example, but now we have
|
180
|
-
a DRY component to use. Great!
|
109
|
+
## Reusability features
|
181
110
|
|
182
|
-
|
111
|
+
SparkleFormation provides a number of features facilitating code reuse and
|
112
|
+
logical structuring. These features help aid developers in applying DRY
|
113
|
+
concepts to infrastructure codebases easing maintainability.
|
183
114
|
|
184
|
-
|
185
|
-
resource and outputs, renaming where required. This would get ugly quick,
|
186
|
-
especially as more instances are added. Making a component for the ec2 resource
|
187
|
-
won't really help since components are static, used to apply the same common
|
188
|
-
parts to multiple templates. So what do we use?
|
115
|
+
> [Learn more!](https://sparkleformation.github.io/sparkle_formation/UserDocs/building-blocks.html)
|
189
116
|
|
190
|
-
|
191
|
-
being merged, they allow passing of arguments which makes them reusable to create
|
192
|
-
unique resources. So, from our last example, lets move the ec2 related items
|
193
|
-
into a dynamic (dynamics/node.rb):
|
117
|
+
## SparkleFormation Implementations
|
194
118
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
:type => 'String',
|
200
|
-
:description => 'Optionally make keypair static'
|
201
|
-
}
|
202
|
-
}
|
203
|
-
) do |_name, _config|
|
204
|
-
|
205
|
-
if(_config[:key_name])
|
206
|
-
parameters.set!("#{_name}_key_name".to_sym) do
|
207
|
-
description 'Name of EC2 key pair'
|
208
|
-
type 'String'
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
dynamic!(:ec2_instance, _name) do
|
213
|
-
properties do
|
214
|
-
key_name _config.fetch(:key_name, ref!("#{_name}_key_name".to_sym))
|
215
|
-
image_id map!(:region_map, 'AWS::Region', :ami)
|
216
|
-
user_data baes64!('80')
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
outputs("#{_name}_instance_id".to_sym) do
|
221
|
-
description 'InstanceId of the newly created EC2 instance'
|
222
|
-
value ref!("#{_name}_ec2_instance".to_sym)
|
223
|
-
end
|
224
|
-
outputs("#{_name}_az".to_sym) do
|
225
|
-
description 'Availability Zone of the newly created EC2 instance'
|
226
|
-
value attr!("#{_name}_ec2_instance".to_sym, :availability_zone)
|
227
|
-
end
|
228
|
-
outputs("#{_name}_public_ip".to_sym) do
|
229
|
-
description 'Public IP address of the newly created EC2 instance'
|
230
|
-
value attr!("#{_name}_ec2_instance".to_sym, :public_ip)
|
231
|
-
end
|
232
|
-
outputs("#{_name}_private_ip".to_sym) do
|
233
|
-
description 'Private IP address of the newly created EC2 instance'
|
234
|
-
value attr!("#{_name}_ec2_instance".to_sym, :private_ip)
|
235
|
-
end
|
236
|
-
outputs("#{_name}_public_dns".to_sym) do
|
237
|
-
description 'Public DNSName of the newly created EC2 instance'
|
238
|
-
value attr!("#{_name}_ec2_instance".to_sym, :public_dns_name)
|
239
|
-
end
|
240
|
-
outputs("#{_name}_private_dns".to_sym) do
|
241
|
-
description 'Private DNSName of the newly created EC2 instance'
|
242
|
-
value attr!("#{_name}_ec2_instance".to_sym, :private_dns_name)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
```
|
246
|
-
|
247
|
-
Now we can put all of these together, and create multiple ec2 instance
|
248
|
-
resource easily:
|
249
|
-
|
250
|
-
```ruby
|
251
|
-
SparkleFormation.new('ec2_example').load(:ami).overrides do
|
252
|
-
|
253
|
-
description "AWS CloudFormation Sample Template ..."
|
254
|
-
|
255
|
-
%w(node1 node2 node3).each do |_node_name|
|
256
|
-
dynamic!(:node, _node_name)
|
257
|
-
end
|
258
|
-
|
259
|
-
# and include one with predefined keypair
|
260
|
-
|
261
|
-
dynamic!(:node, 'snowflake', :key_pair => 'snowkeys')
|
262
|
-
end
|
263
|
-
```
|
119
|
+
SparkleFormation itself is simply a library for building complex template
|
120
|
+
documents in Ruby. It does not provide any integrations with remote API
|
121
|
+
services. For interacting with remote services using the SparkleFormation
|
122
|
+
library, see the SparkleFormation CLI application:
|
264
123
|
|
265
|
-
|
266
|
-
* Add information about symbol importance
|
267
|
-
* Add examples of camel case control
|
268
|
-
* Add examples of complex merge strategies
|
269
|
-
* Add examples of accessing parent hash elements
|
124
|
+
* [SparkleFormation CLI (sfn)](https://github.com/sparkleformation/sfn)
|
270
125
|
|
271
126
|
# Infos
|
272
127
|
* Documentation: http://sparkleformation.github.io/sparkle_formation
|
273
128
|
* User Documentation: http://sparkleformation.github.io/sparkle_formation/UserDocs/README.html
|
274
129
|
* Repository: https://github.com/sparkleformation/sparkle_formation
|
275
|
-
* IRC: Freenode @ #
|
130
|
+
* IRC: Freenode @ #sparkleformation
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'sparkle_formation'
|
2
|
+
|
3
|
+
class SparkleFormation
|
4
|
+
|
5
|
+
class Error < StandardError
|
6
|
+
|
7
|
+
class NotFound < KeyError
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
opts = args.detect{|o| o.is_a?(Hash)}
|
12
|
+
args.delete(opts) if opts
|
13
|
+
super(args)
|
14
|
+
@name = opts[:name] if opts
|
15
|
+
end
|
16
|
+
|
17
|
+
class Dynamic < NotFound; end
|
18
|
+
class Component < NotFound; end
|
19
|
+
class Registry < NotFound; end
|
20
|
+
class Template < NotFound; end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,344 @@
|
|
1
|
+
require 'sparkle_formation'
|
2
|
+
|
3
|
+
class SparkleFormation
|
4
|
+
class Sparkle
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
@@_pack_registry = Smash.new
|
9
|
+
|
10
|
+
# Register a SparklePack for short name access
|
11
|
+
#
|
12
|
+
# @param name [String, Symbol] name of pack
|
13
|
+
# @param path [String] path to pack
|
14
|
+
# @return [Array<String:name, String:path>]
|
15
|
+
def register!(name=nil, path=nil)
|
16
|
+
unless(path)
|
17
|
+
idx = caller.index do |item|
|
18
|
+
item.end_with?("`register!'")
|
19
|
+
end
|
20
|
+
if(idx)
|
21
|
+
file = caller[idx.next].split(':', 2).first
|
22
|
+
path = File.join(File.dirname(file), 'sparkleformation')
|
23
|
+
unless(File.directory?(path))
|
24
|
+
path = nil
|
25
|
+
end
|
26
|
+
unless(name)
|
27
|
+
name = File.basename(caller[idx.next].split(':', 2).first)
|
28
|
+
name.sub!(File.extname(name), '')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
unless(name)
|
33
|
+
if(path)
|
34
|
+
name = path.split(File::PATH_SEPARATOR)[-3].to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
unless(path)
|
38
|
+
raise ArgumentError.new('No SparklePack path provided and failed to auto-detect!')
|
39
|
+
end
|
40
|
+
unless(name)
|
41
|
+
raise ArgumentError.new('No SparklePack name provided and failed to auto-detect!')
|
42
|
+
end
|
43
|
+
@@_pack_registry[name] = path
|
44
|
+
[name, path]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the path to the SparkePack registered with the given
|
48
|
+
# name
|
49
|
+
#
|
50
|
+
# @param name [String, Symbol] name of pack
|
51
|
+
# @return [String] path
|
52
|
+
def path(name)
|
53
|
+
if(@@_pack_registry[name])
|
54
|
+
@@_pack_registry[name]
|
55
|
+
else
|
56
|
+
raise KeyError.new "No pack registered with requested name: #{name}!"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# Wrapper for evaluating sfn files to store within sparkle
|
63
|
+
# container and remove global application
|
64
|
+
def eval_wrapper
|
65
|
+
klass = Class.new(BasicObject)
|
66
|
+
klass.class_eval(<<-EOS
|
67
|
+
def require(*args)
|
68
|
+
::Kernel.require *args
|
69
|
+
end
|
70
|
+
|
71
|
+
class SparkleFormation
|
72
|
+
|
73
|
+
attr_accessor :sparkle_path
|
74
|
+
|
75
|
+
class << self
|
76
|
+
|
77
|
+
def part_data(data=nil)
|
78
|
+
if(data)
|
79
|
+
@data = data
|
80
|
+
else
|
81
|
+
@data
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def dynamic(name, args={}, &block)
|
86
|
+
part_data[:dynamic].push(
|
87
|
+
::Smash.new(
|
88
|
+
:name => name,
|
89
|
+
:block => block,
|
90
|
+
:args => Smash[
|
91
|
+
args.map(&:to_a)
|
92
|
+
],
|
93
|
+
:type => :dynamic
|
94
|
+
)
|
95
|
+
).last
|
96
|
+
end
|
97
|
+
|
98
|
+
def build(&block)
|
99
|
+
part_data[:component].push(
|
100
|
+
::Smash.new(
|
101
|
+
:block => block,
|
102
|
+
:type => :component
|
103
|
+
)
|
104
|
+
).last
|
105
|
+
end
|
106
|
+
|
107
|
+
def component(name, &block)
|
108
|
+
part_data[:component].push(
|
109
|
+
::Smash.new(
|
110
|
+
:name => name,
|
111
|
+
:block => block,
|
112
|
+
:type => :component
|
113
|
+
)
|
114
|
+
).last
|
115
|
+
end
|
116
|
+
|
117
|
+
def dynamic_info(*args)
|
118
|
+
Smash.new(:metadata => {}, :args => {})
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
def initialize(*args)
|
124
|
+
SparkleFormation.part_data[:template].push(
|
125
|
+
::Smash.new(
|
126
|
+
:name => args.first
|
127
|
+
)
|
128
|
+
)
|
129
|
+
raise TypeError
|
130
|
+
end
|
131
|
+
|
132
|
+
class Registry
|
133
|
+
|
134
|
+
def self.register(name, &block)
|
135
|
+
SparkleFormation.part_data[:registry].push(
|
136
|
+
::Smash.new(
|
137
|
+
:name => name,
|
138
|
+
:block => block,
|
139
|
+
:type => :registry
|
140
|
+
)
|
141
|
+
).last
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
SfnRegistry = Registry
|
146
|
+
|
147
|
+
end
|
148
|
+
::Object.constants.each do |const|
|
149
|
+
unless(self.const_defined?(const))
|
150
|
+
next if const == :Config # prevent warning output
|
151
|
+
self.const_set(const, ::Object.const_get(const))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def part_data(arg)
|
156
|
+
SparkleFormation.part_data(arg)
|
157
|
+
end
|
158
|
+
EOS
|
159
|
+
)
|
160
|
+
klass
|
161
|
+
end
|
162
|
+
|
163
|
+
include Bogo::Memoization
|
164
|
+
|
165
|
+
# Valid directories from cwd to set as root
|
166
|
+
VALID_ROOT_DIRS = [
|
167
|
+
'sparkleformation',
|
168
|
+
'sfn',
|
169
|
+
'cloudformation',
|
170
|
+
'cfn',
|
171
|
+
'.'
|
172
|
+
]
|
173
|
+
|
174
|
+
# Reserved directories
|
175
|
+
DIRS = [
|
176
|
+
'components',
|
177
|
+
'registry',
|
178
|
+
'dynamics'
|
179
|
+
]
|
180
|
+
|
181
|
+
# Valid types
|
182
|
+
TYPES = Smash.new(
|
183
|
+
'component' => 'components',
|
184
|
+
'registry' => 'registries',
|
185
|
+
'dynamic' => 'dynamics',
|
186
|
+
'template' => 'templates'
|
187
|
+
)
|
188
|
+
|
189
|
+
# @return [String] path to sparkle directories
|
190
|
+
attr_reader :root
|
191
|
+
# @return [Smash] raw part data
|
192
|
+
attr_reader :raw_data
|
193
|
+
|
194
|
+
# Create new sparkle instance
|
195
|
+
#
|
196
|
+
# @param args [Hash]
|
197
|
+
# @option args [String] :root path to sparkle directories
|
198
|
+
# @option args [String, Symbol] :name registered pack name
|
199
|
+
# @return [self]
|
200
|
+
def initialize(args={})
|
201
|
+
if(args[:name])
|
202
|
+
@root = self.class.path(args[:name])
|
203
|
+
else
|
204
|
+
@root = args.fetch(:root, locate_root)
|
205
|
+
end
|
206
|
+
unless(File.directory?(@root))
|
207
|
+
raise Errno::ENOENT.new("No such directory - #{@root}")
|
208
|
+
end
|
209
|
+
@raw_data = Smash.new(
|
210
|
+
:dynamic => [],
|
211
|
+
:component => [],
|
212
|
+
:registry => []
|
213
|
+
)
|
214
|
+
@wrapper = eval_wrapper.new
|
215
|
+
wrapper.part_data(raw_data)
|
216
|
+
load_parts!
|
217
|
+
end
|
218
|
+
|
219
|
+
# @return [Smash<name:block>]
|
220
|
+
def components
|
221
|
+
memoize(:components) do
|
222
|
+
Smash.new
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [Smash<name:block>]
|
227
|
+
def dynamics
|
228
|
+
memoize(:dynamics) do
|
229
|
+
Smash.new
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# @return [Smash<name:block>]
|
234
|
+
def registries
|
235
|
+
memoize(:registries) do
|
236
|
+
Smash.new
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# @return [Smash<name:path>]
|
241
|
+
def templates
|
242
|
+
memoize(:templates) do
|
243
|
+
Smash.new.tap do |hash|
|
244
|
+
Dir.glob(File.join(root, '**', '**', '*.{json,rb}')) do |path|
|
245
|
+
slim_path = path.sub("#{root}/", '')
|
246
|
+
next if DIRS.include?(slim_path.split('/').first)
|
247
|
+
data = Smash.new(:template => [])
|
248
|
+
t_wrap = eval_wrapper.new
|
249
|
+
t_wrap.part_data(data)
|
250
|
+
begin
|
251
|
+
t_wrap.instance_eval(IO.read(path), path, 1)
|
252
|
+
rescue TypeError
|
253
|
+
end
|
254
|
+
data = data[:template].first
|
255
|
+
unless(data[:name])
|
256
|
+
data[:name] = slim_path.tr('/', '__')
|
257
|
+
end
|
258
|
+
hash[data[:name]] = data.merge(
|
259
|
+
Smash.new(
|
260
|
+
:type => :template,
|
261
|
+
:path => path
|
262
|
+
)
|
263
|
+
)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Request item from the store
|
270
|
+
#
|
271
|
+
# @param type [String, Symbol] item type (see: TYPES)
|
272
|
+
# @param name [String, Symbol] name of item
|
273
|
+
# @return [Smash] requested item
|
274
|
+
# @raises [NameError, Error::NotFound]
|
275
|
+
def get(type, name)
|
276
|
+
unless(TYPES.keys.include?(type.to_s))
|
277
|
+
raise NameError.new "Invalid type requested (#{type})! Valid types: #{TYPES.join(', ')}"
|
278
|
+
end
|
279
|
+
result = send(TYPES[type])[name]
|
280
|
+
if(result.nil? && TYPES[type] == 'templates')
|
281
|
+
result = (
|
282
|
+
send(TYPES[type]).detect{|k,v|
|
283
|
+
name = name.to_s
|
284
|
+
short_name = v[:path].sub(/#{Regexp.escape(root)}\/?/, '')
|
285
|
+
v[:path] == name ||
|
286
|
+
short_name == name ||
|
287
|
+
short_name.sub('.rb', '').gsub(File::SEPARATOR, '__').tr('-', '_') == name ||
|
288
|
+
v[:path].end_with?(name)
|
289
|
+
} || []
|
290
|
+
).last
|
291
|
+
end
|
292
|
+
unless(result)
|
293
|
+
klass = Error::NotFound.const_get(type.capitalize)
|
294
|
+
raise klass.new("No #{type} registered with requested name (#{name})!", :name => name)
|
295
|
+
end
|
296
|
+
result
|
297
|
+
end
|
298
|
+
|
299
|
+
# @return [String]
|
300
|
+
def inspect
|
301
|
+
"<SparkleFormation::Sparkle [root: #{root.inspect}]>"
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
attr_reader :wrapper
|
307
|
+
|
308
|
+
# Locate root directory. Defaults to current working directory if
|
309
|
+
# valid sub directory is not located
|
310
|
+
#
|
311
|
+
# @return [String] root path
|
312
|
+
def locate_root
|
313
|
+
VALID_ROOT_DIRS.map do |part|
|
314
|
+
path = File.expand_path(File.join(Dir.pwd, part))
|
315
|
+
if(File.exists?(path))
|
316
|
+
path
|
317
|
+
end
|
318
|
+
end.compact.first
|
319
|
+
end
|
320
|
+
|
321
|
+
# Load all sparkle parts
|
322
|
+
def load_parts!
|
323
|
+
memoize(:load_parts) do
|
324
|
+
Dir.glob(File.join(root, "{#{DIRS.join(',')}}", '*.rb')).each do |file|
|
325
|
+
wrapper.instance_eval(IO.read(file), file, 1)
|
326
|
+
end
|
327
|
+
raw_data.each do |key, items|
|
328
|
+
items.each do |item|
|
329
|
+
if(item[:name])
|
330
|
+
send(TYPES[key])[item.delete(:name)] = item
|
331
|
+
else
|
332
|
+
path = item[:block].source_location.first.sub('.rb', '').split(File::SEPARATOR)
|
333
|
+
type, name = path.slice(path.size - 2, 2)
|
334
|
+
send(type)[name] = item
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
# Alias for interfacing naming
|
343
|
+
SparklePack = Sparkle
|
344
|
+
end
|