sparkle_formation 0.4.0 → 1.0.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.
- 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
|
+

|
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
|