sparkle_formation 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbb4f29d2f9a0a89eab9ce978f32c653324714e5
4
- data.tar.gz: 8cd5b31636c808b98590f80f94611d1bad9b1095
3
+ metadata.gz: 7ed2f1e3bec855db611f4a8fcd7c3e3c509aa209
4
+ data.tar.gz: d6dc132ef582d52c55080c4a4e80d60fbb28c702
5
5
  SHA512:
6
- metadata.gz: 19eeadefa0b8119e8fe5dd9422950c5a18a707edad34ea4b7e867f322570ab1c5e12aea13e0a04f4cc1c1ac5640fe3030b89096a87fbd6b2ec247cca92ff7881
7
- data.tar.gz: c6a22c34f4f5de2cb35e039d538a7880205fbe3882c470e26c136713ebf6f5cbc4bbaa4789497230960dee1dc74d69bd7af83d380d3045477cb5f96791171e5b
6
+ metadata.gz: 09bd692653d5f7816a332fc2d7f772760bac7e13e34e8314518f99ec48604146a5c8bdf5ce3a321a4599307dcbf6b5673800979a9497c75e708722772882c4c0
7
+ data.tar.gz: e4958183360a572b03aff5ebeab2a9c2ea6db984f8db91bb146b0d26b59665290096a43a0d05e425ecdcfd6c096bb080205a25c3e44f0ce798f34632a9affe4e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v2.1.0
2
+ * Add template inheritance support (#145)
3
+ * Add layer merging support for templates, components, and dynamics (#145)
4
+ * Update allowed Ruby constraint to require version greater than or equal to 2.1
5
+
1
6
  # v2.0.2
2
7
  * Provide useful return value from `#dynamic!` (#137)
3
8
  * Implement proper Azure resource generator (#141)
@@ -205,7 +205,7 @@ the `dynamic!` call:
205
205
  ~~~ruby
206
206
  SparkleFormation.new(:with_namespace) do
207
207
  dynamic!(:aws_opsworks_instance, :foobar,
208
- :resource_suffix_name => :instance
208
+ :resource_name_suffix => :instance
209
209
  )
210
210
  end
211
211
  ~~~
@@ -0,0 +1,159 @@
1
+ ---
2
+ title: "Inheritance and Merging"
3
+ category: "dsl"
4
+ weight: 8
5
+ anchors:
6
+ - title: "Template Inheritance"
7
+ url: "#template-inheritance"
8
+ - title: "Template Merging"
9
+ url: "#template-merging"
10
+ - title: "Component and Dynamic Merging"
11
+ url: "#component-and-dynamic-merging"
12
+ - title: "Registry Items"
13
+ url: "#registry-items"
14
+ ---
15
+
16
+ ## Inheritance and Merging
17
+
18
+ SparkleFormation includes functionality allowing SparklePacks to
19
+ interact with previous layers when a file is requested from the
20
+ collection. This functionality makes it easy to slightly modify
21
+ files provided within previously registered SparklePacks without
22
+ having to recreate the entire contents of the file. It also allows
23
+ for creation of new templates based on existing templates.
24
+
25
+ ### Template Inheritance
26
+
27
+ Template inheritance allows a new template to inherit the data structures
28
+ of an existing template. For example, lets start with a simple template:
29
+
30
+ ~~~ruby
31
+ SparkleFormation.new(:simple) do
32
+ simple_template true
33
+ end
34
+ ~~~
35
+
36
+ The dumped result of this template is:
37
+
38
+ ~~~json
39
+ {
40
+ "SimpleTemplate": true
41
+ }
42
+ ~~~
43
+
44
+ Suppose that we wanted create a new template that was composed of everything
45
+ described in the `simple` template, but had an extra addition. Instead of
46
+ re-creating the contents in a new file, or attempting to break out components
47
+ or dynamics that may not be ideal, we can inherit the template instead:
48
+
49
+ ~~~ruby
50
+ SparkleFormation.new(:advanced, :inherit => :simple) do
51
+ advanced.item 'added'
52
+ end
53
+ ~~~
54
+
55
+ When this template is dumped, it will inherit the `simple` template and
56
+ add on to the resultant data structure giving the result:
57
+
58
+ ~~~json
59
+ {
60
+ "SimpleTemplate": true,
61
+ "Advanced": {
62
+ "Item": "added"
63
+ }
64
+ }
65
+ ~~~
66
+
67
+ Inheritance is not restricted to templates at a common level. A new
68
+ template can inherit templates provided by SparklePacks.
69
+
70
+ ### Template Merging
71
+
72
+ Template merging is only applicable when SparklePacks are in use. Template
73
+ merging allows direct modification of a specific named template. This strategy
74
+ is extremely useful when packs may be layering functionality into a template,
75
+ or the root pack wants to make a specific adjustment without creating a new
76
+ template.
77
+
78
+ The default behavior of SparkleFormation when a SparklePack provides a template with
79
+ the same name as a previously loaded SparklePack, it will overwrite the original
80
+ template. In some cases, this may not be the desired effect. If a pack would like
81
+ to make a simple adjustment to the template, this would require duplicating the
82
+ contents of the original template and appending the customization. Merging allows
83
+ adding customization without recreation.
84
+
85
+ Lets assume we have a core SparklePack loaded and it provides us with the `simple`
86
+ template:
87
+
88
+ ~~~ruby
89
+ SparkleFormation.new(:simple) do
90
+ simple_template true
91
+ end
92
+ ~~~
93
+
94
+ Now in our root SparklePack we want to make an adjustment to the `simple` template
95
+ directly. We can do this by merging:
96
+
97
+ ~~~ruby
98
+ SparkleFormation.new(:simple, :layering => :merge) do
99
+ modifier 'spox'
100
+ end
101
+ ~~~
102
+
103
+ When we dump the `simple` template now we get:
104
+
105
+ ~~~json
106
+ {
107
+ "SimpleTemplate": true,
108
+ "Modifier": "spox"
109
+ }
110
+ ~~~
111
+
112
+ This sort of merging make two strategies possible:
113
+
114
+ 1. Modifying the final result of a template for a specific need
115
+ 2. Modifying at serveral layers to allow SparklePacks to be additive
116
+
117
+ ### Component and Dynamic Merging
118
+
119
+ Merging behavior for components and dynamics follows the same pattern as templates.
120
+ Adding the `:layering => :merge` option will cause the item to be merged instead
121
+ of overwritten.
122
+
123
+ For components:
124
+
125
+ ~~~ruby
126
+ SparkleFormation.component(:name, :layering => :merge)
127
+ ~~~
128
+
129
+ and dynamics:
130
+
131
+ ~~~ruby
132
+ SparkleFormation.dynamic(:name, :layering => :merge)
133
+ ~~~
134
+
135
+ #### Dynamic return value
136
+
137
+ The return value of a dynamic cannot be inferred. This is due to the freeform nature
138
+ of dynamics leaving the resulting return value up to the author of the dynamic. When
139
+ merging dynamics it can be easy to return the wrong value. The library _could_ take
140
+ care of this and always return the context provided by the initial dynamic, but this
141
+ behavior is problematic: merges could remove the original context, or may want to
142
+ change the return context completely. Returning the proper value is left to the
143
+ author, but SparkleFormation makes it easy to return the previous context.
144
+
145
+ When the dynamics are merged, SparkleFormation will include an extra key in the
146
+ options `Hash` for the dynamic: `:previous_layer_result`. Now a dynamic can merge
147
+ in extra changes, and ensure the correct value is returned from the call:
148
+
149
+ ~~~ruby
150
+ SparkleFormation.dynamic(:name, :layering => :merge) do |name, args={}|
151
+ new.things.added 'here'
152
+ args[:previous_layer_result]
153
+ end
154
+ ~~~
155
+
156
+ ### Registry Items
157
+
158
+ Registry items are exempt from the merging behavior described above. Registry items
159
+ are always overwritten when redefined in higher level SparklePacks.
@@ -5,6 +5,9 @@ class SparkleFormation
5
5
  # SparkleFormation specific errors
6
6
  class Error < StandardError
7
7
 
8
+ # Inheritance error
9
+ class CircularInheritance < Error; end
10
+
8
11
  # File not found error
9
12
  class NotFound < KeyError
10
13
  attr_reader :name
@@ -110,11 +110,14 @@ class SparkleFormation
110
110
  ).last
111
111
  end
112
112
 
113
- def component(name, &block)
113
+ def component(name, args={}, &block)
114
114
  part_data[:component].push(
115
115
  ::Smash.new(
116
116
  :name => name,
117
117
  :block => block,
118
+ :args => Smash[
119
+ args.map(&:to_a)
120
+ ],
118
121
  :type => :component
119
122
  )
120
123
  ).last
@@ -6,6 +6,8 @@ class SparkleFormation
6
6
  # leak on long running processes with long lasting collections
7
7
  class SparkleCollection < Sparkle
8
8
 
9
+ autoload :Rainbow, 'sparkle_formation/sparkle_collection/rainbow'
10
+
9
11
  # Create a new collection of sparkles
10
12
  #
11
13
  # @return [self]
@@ -68,7 +70,10 @@ class SparkleFormation
68
70
  memoize("components_#{checksum}") do
69
71
  Smash.new.tap do |hsh|
70
72
  sparkles.each do |sprkl|
71
- hsh.merge!(sprkl.components)
73
+ sprkl.components.each_pair do |c_name, c_value|
74
+ hsh[c_name] ||= Rainbow.new(c_name, :component)
75
+ hsh[c_name].add_layer(c_value)
76
+ end
72
77
  end
73
78
  end
74
79
  end
@@ -79,7 +84,10 @@ class SparkleFormation
79
84
  memoize("dynamics_#{checksum}") do
80
85
  Smash.new.tap do |hsh|
81
86
  sparkles.each do |sprkl|
82
- hsh.merge!(sprkl.dynamics)
87
+ sprkl.dynamics.each_pair do |c_name, c_value|
88
+ hsh[c_name] ||= Rainbow.new(c_name, :dynamic)
89
+ hsh[c_name].add_layer(c_value)
90
+ end
83
91
  end
84
92
  end
85
93
  end
@@ -101,7 +109,10 @@ class SparkleFormation
101
109
  memoize("templates_#{checksum}") do
102
110
  Smash.new.tap do |hsh|
103
111
  sparkles.each do |sprkl|
104
- hsh.merge!(sprkl.templates)
112
+ sprkl.templates.each_pair do |c_name, c_value|
113
+ hsh[c_name] ||= Rainbow.new(c_name, :template)
114
+ hsh[c_name].add_layer(c_value)
115
+ end
105
116
  end
106
117
  end
107
118
  end
@@ -113,20 +124,21 @@ class SparkleFormation
113
124
  # @param name [String, Symbol] name of item
114
125
  # @return [Smash] requested item
115
126
  # @raises [NameError, Error::NotFound]
116
- def get(*args)
127
+ def get(type, name)
128
+ type_name = Sparkle::TYPES[type.to_s]
129
+ unless(type_name)
130
+ raise ArgumentError.new "Unknown file type requested from collection `#{type}`"
131
+ end
117
132
  result = nil
118
133
  error = nil
119
- sparkles.each do |sprkl|
120
- begin
121
- result = sprkl.get(*args)
122
- rescue Error::NotFound => error
123
- end
124
- end
125
- if(result)
126
- result
127
- else
128
- raise error
134
+ result = send(type_name)[name]
135
+ unless(result)
136
+ error_klass = Error::NotFound.const_get(
137
+ Bogo::Utility.camel(type)
138
+ )
139
+ raise error_klass.new(:name => name)
129
140
  end
141
+ result
130
142
  end
131
143
 
132
144
  protected
@@ -0,0 +1,92 @@
1
+ require 'sparkle_formation'
2
+ require 'forwardable'
3
+
4
+ class SparkleFormation
5
+
6
+ class SparkleCollection
7
+
8
+ # Contains a layered number of a specific item defined via
9
+ # a Sparkle. Items higher in the spectrum (greater layer index)
10
+ # have higher precedence than those below. This can be used for
11
+ # properly generating the end result based on merging or knockout rules.
12
+ class Rainbow
13
+
14
+ # Valid types for a rainbow
15
+ VALID_TYPES = [
16
+ :template,
17
+ :component,
18
+ :dynamic
19
+ ].freeze
20
+
21
+ extend Forwardable
22
+ def_delegators :top, *(Smash.public_instance_methods - Object.public_instance_methods)
23
+
24
+ # @return [String]
25
+ attr_reader :name
26
+ # @return [Symbol]
27
+ attr_reader :type
28
+ # @return [Array<Hash>]
29
+ attr_reader :spectrum
30
+
31
+ # Create a new rainbow
32
+ #
33
+ # @param name [String, Symbol] name of item
34
+ # @param type [String, Symbol] type of item
35
+ # @return [self]
36
+ def initialize(name, type)
37
+ unless(VALID_TYPES.include?(type.to_sym))
38
+ raise ArgumentError.new "Invalid type provdied for Rainbow instance `#{type}`"
39
+ end
40
+ @name = name.to_s
41
+ @type = type.to_sym
42
+ @spectrum = []
43
+ end
44
+
45
+ # Add a new layer to the top of the spectrum
46
+ #
47
+ # @param item [Hash]
48
+ # @return [self]
49
+ def add_layer(item)
50
+ unless(item.is_a?(Hash))
51
+ raise TypeError.new "Expecting type `Hash` but received type `#{item.class}`"
52
+ end
53
+ spectrum << item.to_smash
54
+ self
55
+ end
56
+
57
+ # Fetch item defined at given layer
58
+ #
59
+ # @param idx [Integer]
60
+ # @return [Hash]
61
+ def layer_at(idx)
62
+ if(idx <= spectrum.size)
63
+ spectrum.at(idx)
64
+ else
65
+ raise KeyError.new "Invalid layer requested for #{type} - #{name} (index: #{idx})"
66
+ end
67
+ end
68
+
69
+ # Generates a list of items to be processed in
70
+ # order to achieve the correct result based on
71
+ # expected merging behavior
72
+ #
73
+ # @return [Array<Hash>]
74
+ def monochrome
75
+ Array.new.tap do |result|
76
+ spectrum.each do |item|
77
+ unless(item.get(:args, :layering).to_s == 'merge')
78
+ result.clear
79
+ end
80
+ result << item
81
+ end
82
+ end
83
+ end
84
+
85
+ # @return [Hash]
86
+ def top
87
+ spectrum.last || {}
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -94,11 +94,14 @@ class SparkleFormation
94
94
  # @return [Hashish, SparkleStruct]
95
95
  def compile(path, *args)
96
96
  opts = args.detect{|i| i.is_a?(Hash) } || {}
97
- if(spath = (opts.delete(:sparkle_path) || SparkleFormation.sparkle_path))
98
- container = Sparkle.new(:root => spath)
99
- path = container.get(:template, path)[:path]
97
+ unless(path.is_a?(String) && File.file?(path.to_s))
98
+ if(spath = (opts.delete(:sparkle_path) || SparkleFormation.sparkle_path))
99
+ container = Sparkle.new(:root => spath)
100
+ path = container.get(:template, path)[:path]
101
+ end
100
102
  end
101
103
  formation = instance_eval(IO.read(path), path, 1)
104
+ formation.template_path = path
102
105
  if(args.delete(:sparkle))
103
106
  formation
104
107
  else
@@ -213,7 +216,17 @@ class SparkleFormation
213
216
  begin
214
217
  dyn = struct._self.sparkle.get(:dynamic, dynamic_name)
215
218
  raise dyn if dyn.is_a?(Exception)
216
- result = struct.instance_exec(*args, &dyn[:block])
219
+ dyn.monochrome.each do |dynamic_item|
220
+ if(result)
221
+ opts = args.detect{|i| i.is_a?(Hash)}
222
+ if(opts)
223
+ opts[:previous_layer_result] = result
224
+ else
225
+ args.push(:previous_layer_result => result)
226
+ end
227
+ end
228
+ result = struct.instance_exec(*args, &dynamic_item[:block])
229
+ end
217
230
  if(block_given?)
218
231
  result.instance_exec(&block)
219
232
  end
@@ -364,6 +377,10 @@ class SparkleFormation
364
377
  attr_reader :provider
365
378
  # @return [Class] Provider resources
366
379
  attr_reader :provider_resources
380
+ # @return [String] local path to template
381
+ attr_accessor :template_path
382
+ # @return [Array<String>] black listed templates
383
+ attr_reader :blacklisted_templates
367
384
 
368
385
  # Create new instance
369
386
  #
@@ -379,19 +396,23 @@ class SparkleFormation
379
396
  def initialize(name, options={}, &block)
380
397
  @name = name.to_sym
381
398
  @component_paths = []
382
- @sparkle = SparkleCollection.new
383
- @sparkle.set_root(
384
- Sparkle.new(
385
- Smash.new.tap{|h|
386
- s_path = options.fetch(:sparkle_path,
387
- self.class.custom_paths[:sparkle_path]
388
- )
389
- if(s_path)
390
- h[:root] = s_path
391
- end
392
- }
399
+ if(options[:sparkle_collection])
400
+ @sparkle = options[:sparkle_collection]
401
+ else
402
+ @sparkle = SparkleCollection.new
403
+ @sparkle.set_root(
404
+ Sparkle.new(
405
+ Smash.new.tap{|h|
406
+ s_path = options.fetch(:sparkle_path,
407
+ self.class.custom_paths[:sparkle_path]
408
+ )
409
+ if(s_path)
410
+ h[:root] = s_path
411
+ end
412
+ }
413
+ )
393
414
  )
394
- )
415
+ end
395
416
  self.provider = options.fetch(:provider, @parent ? @parent.provider : :aws)
396
417
  if(provider == :aws || !options[:disable_aws_builtins])
397
418
  require 'sparkle_formation/aws'
@@ -405,16 +426,105 @@ class SparkleFormation
405
426
  stack_resource_type,
406
427
  *options.fetch(:stack_resource_types, [])
407
428
  ].compact.uniq
429
+ @blacklisted_templates = [name]
408
430
  @components = Smash.new
409
431
  @load_order = []
410
432
  @overrides = []
411
433
  @parent = options[:parent]
434
+ @seed = Smash.new(
435
+ :inherit => options[:inherit],
436
+ :layering => options[:layering]
437
+ )
412
438
  if(block)
413
439
  load_block(block)
414
440
  end
415
441
  @compiled = nil
416
442
  end
417
443
 
444
+ # @return [Array<Proc>]
445
+ def raw_overrides
446
+ @overrides
447
+ end
448
+
449
+ # Update underlying data structures based on inherit
450
+ # or layering behavior if defined for this template
451
+ #
452
+ # @param options [Hash]
453
+ # @return [self]
454
+ def seed_self
455
+ memoize(:seed) do
456
+ options = @seed
457
+ if(options[:inherit] && options[:layering].to_s == 'merge')
458
+ raise ArgumentError.new 'Cannot merge and inherit!'
459
+ end
460
+ if(options[:inherit])
461
+ inherit_from(options[:inherit])
462
+ elsif(options[:layering].to_s == 'merge')
463
+ merge_previous!
464
+ end
465
+ true
466
+ end
467
+ self
468
+ end
469
+
470
+ # Merge previous layer template structure
471
+ #
472
+ # @return [self]
473
+ def merge_previous!
474
+ my_index = sparkle.get(:template, name).spectrum.find_index do |item|
475
+ item[:path] == template_path
476
+ end
477
+ template = self.class.compile(sparkle.get(:template, name).layer_at(my_index - 1)[:path], :sparkle)
478
+ extract_template_data(template)
479
+ end
480
+
481
+ # Inhert template structure
482
+ #
483
+ # @param template_name [String] name of template to inherit
484
+ # @return [self]
485
+ def inherit_from(template_name)
486
+ if(blacklisted_templates.map(&:to_s).include?(template_name.to_s))
487
+ raise Error::CircularInheritance.new "Circular inheritance detected between templates `#{template_name}` and `#{name}`" # rubocop:disable Metrics/LineLength
488
+ end
489
+ template = self.class.compile(sparkle.get(:template, template_name)[:path], :sparkle)
490
+ template.blacklisted_templates.replace(
491
+ (template.blacklisted_templates + blacklisted_templates).map(&:to_s).uniq
492
+ )
493
+ extract_template_data(template)
494
+ end
495
+
496
+ # Extract information from given template and integrate with current instance
497
+ #
498
+ # @param template [SparkleFormation]
499
+ # @return [self]
500
+ def extract_template_data(template)
501
+ if(provider != template.provider)
502
+ raise TypeError.new "This template `#{name}` cannot inherit template `#{template.name}`! Provider mismatch: `#{provider}` != `#{template.provider}`" # rubocop:disable Metrics/LineLength
503
+ end
504
+ sparkle.size.times do |idx|
505
+ template.sparkle.add_sparkle(sparkle.sparkle_at(idx))
506
+ end
507
+ template.seed_self
508
+ blacklisted_templates.replace(
509
+ (blacklisted_templates + template.blacklisted_templates).map(&:to_s).uniq
510
+ )
511
+ @parameters = template.parameters
512
+ @overrides = template.raw_overrides + raw_overrides
513
+ new_components = template.components
514
+ new_load_order = template.load_order
515
+ load_order.each do |comp_key|
516
+ if(components[comp_key])
517
+ original_key = comp_key
518
+ comp_key = "#{comp_key}_#{Smash.new(:name => name, :path => template_path).checksum}_child"
519
+ new_components[comp_key] = components[original_key]
520
+ end
521
+ new_load_order << comp_key
522
+ end
523
+ @components = new_components
524
+ @load_order = new_load_order
525
+ self
526
+ end
527
+
418
528
  # @return [String] provider stack resource type
419
529
  def stack_resource_type
420
530
  DEFAULT_STACK_RESOURCE
@@ -536,11 +646,13 @@ class SparkleFormation
536
646
  args.each do |thing|
537
647
  key = File.basename(thing.to_s).sub('.rb', '')
538
648
  if(thing.is_a?(String))
649
+ # NOTE: This needs to be deprecated and removed
650
+ # TODO: deprecate
539
651
  components[key] = self.class.load_component(thing)
652
+ @load_order << key
540
653
  else
541
- components[key] = sparkle.get(:component, thing)[:block]
654
+ @load_order << thing
542
655
  end
543
- @load_order << key
544
656
  end
545
657
  self
546
658
  end
@@ -565,6 +677,9 @@ class SparkleFormation
565
677
  unmemoize(:compile)
566
678
  end
567
679
  memoize(:compile) do
680
+ # NOTE: this is where we inject inherit or layering
681
+ seed_self
682
+
568
683
  set_compile_time_parameters!
569
684
  if(provider && SparkleAttribute.const_defined?(camel(provider)))
570
685
  const = SparkleAttribute.const_get(camel(provider))
@@ -593,7 +708,13 @@ class SparkleFormation
593
708
  compiled.set_state!(compile_state)
594
709
  end
595
710
  @load_order.each do |key|
596
- self.class.build(compiled, &components[key])
711
+ if(components[key])
712
+ self.class.build(compiled, &components[key])
713
+ else
714
+ sparkle.get(:component, key).monochrome.each do |component_block|
715
+ self.class.build(compiled, &component_block[:block])
716
+ end
717
+ end
597
718
  end
598
719
  @overrides.each do |override|
599
720
  if(override[:args] && !override[:args].empty?)
@@ -1,5 +1,5 @@
1
1
  # Unicorns and rainbows
2
2
  class SparkleFormation
3
3
  # Current library version
4
- VERSION = Gem::Version.new('2.0.2')
4
+ VERSION = Gem::Version.new('2.1.0')
5
5
  end
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.description = 'Ruby DSL for programmatic orchestration API template generation'
11
11
  s.license = 'Apache-2.0'
12
12
  s.require_path = 'lib'
13
- s.required_ruby_version = '>= 2.0'
13
+ s.required_ruby_version = '>= 2.1'
14
14
  s.add_runtime_dependency 'attribute_struct', '>= 0.3.0', '< 0.5'
15
15
  s.add_runtime_dependency 'multi_json'
16
16
  s.add_runtime_dependency 'bogo'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparkle_formation
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-28 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: attribute_struct
@@ -158,6 +158,7 @@ files:
158
158
  - docs/building-blocks.md
159
159
  - docs/compile-time-parameters.md
160
160
  - docs/helper-methods.md
161
+ - docs/inheritance-merging.md
161
162
  - docs/nested-stacks.md
162
163
  - docs/overview.md
163
164
  - docs/sparkle-packs.md
@@ -195,6 +196,7 @@ files:
195
196
  - lib/sparkle_formation/sparkle_attribute/heat.rb
196
197
  - lib/sparkle_formation/sparkle_attribute/rackspace.rb
197
198
  - lib/sparkle_formation/sparkle_collection.rb
199
+ - lib/sparkle_formation/sparkle_collection/rainbow.rb
198
200
  - lib/sparkle_formation/sparkle_formation.rb
199
201
  - lib/sparkle_formation/sparkle_struct.rb
200
202
  - lib/sparkle_formation/translation.rb
@@ -215,7 +217,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
215
217
  requirements:
216
218
  - - ">="
217
219
  - !ruby/object:Gem::Version
218
- version: '2.0'
220
+ version: '2.1'
219
221
  required_rubygems_version: !ruby/object:Gem::Requirement
220
222
  requirements:
221
223
  - - ">="