cloud-templates 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +29 -0
- data/.simplecov +6 -0
- data/Gemfile +2 -0
- data/LICENSE +201 -0
- data/NOTICE +13 -0
- data/README.md +124 -0
- data/Rakefile +27 -0
- data/cloud-templates.gemspec +27 -0
- data/examples/lib/user_directory/artifacts/catalogized.rb +11 -0
- data/examples/lib/user_directory/artifacts/group.rb +37 -0
- data/examples/lib/user_directory/artifacts/ided.rb +11 -0
- data/examples/lib/user_directory/artifacts/organization.rb +17 -0
- data/examples/lib/user_directory/artifacts/pathed.rb +22 -0
- data/examples/lib/user_directory/artifacts/person.rb +20 -0
- data/examples/lib/user_directory/artifacts/team.rb +31 -0
- data/examples/lib/user_directory/artifacts/unit.rb +24 -0
- data/examples/lib/user_directory/artifacts/user.rb +29 -0
- data/examples/lib/user_directory/render/etc/artifact_view.rb +15 -0
- data/examples/lib/user_directory/render/etc/composite_view.rb +26 -0
- data/examples/lib/user_directory/render/etc/group_view.rb +23 -0
- data/examples/lib/user_directory/render/etc/person_view.rb +19 -0
- data/examples/lib/user_directory/render/etc/registry.rb +33 -0
- data/examples/lib/user_directory/render/etc/user_view.rb +35 -0
- data/examples/lib/user_directory/render/etc.rb +3 -0
- data/examples/lib/user_directory/render/ldap/artifact_view.rb +27 -0
- data/examples/lib/user_directory/render/ldap/composite_view.rb +32 -0
- data/examples/lib/user_directory/render/ldap/group_view.rb +28 -0
- data/examples/lib/user_directory/render/ldap/organization_view.rb +26 -0
- data/examples/lib/user_directory/render/ldap/person_view.rb +39 -0
- data/examples/lib/user_directory/render/ldap/registry.rb +16 -0
- data/examples/lib/user_directory/render/ldap/unit_view.rb +26 -0
- data/examples/lib/user_directory/render/ldap/user_view.rb +39 -0
- data/examples/lib/user_directory/render/ldap.rb +3 -0
- data/examples/lib/user_directory/utils.rb +18 -0
- data/examples/lib/user_directory.rb +23 -0
- data/examples/lib_path.rb +2 -0
- data/examples/spec/spec_helper.rb +1 -0
- data/examples/spec/user_directory_spec.rb +568 -0
- data/lib/aws/templates/artifact.rb +140 -0
- data/lib/aws/templates/composite.rb +178 -0
- data/lib/aws/templates/exceptions.rb +221 -0
- data/lib/aws/templates/render/registry.rb +60 -0
- data/lib/aws/templates/render/utils/base_type_views.rb +131 -0
- data/lib/aws/templates/render/view.rb +127 -0
- data/lib/aws/templates/render.rb +72 -0
- data/lib/aws/templates/utils/artifact_storage.rb +141 -0
- data/lib/aws/templates/utils/contextualized/filters.rb +437 -0
- data/lib/aws/templates/utils/contextualized/hash.rb +13 -0
- data/lib/aws/templates/utils/contextualized/nil.rb +13 -0
- data/lib/aws/templates/utils/contextualized/proc.rb +13 -0
- data/lib/aws/templates/utils/contextualized.rb +113 -0
- data/lib/aws/templates/utils/default.rb +185 -0
- data/lib/aws/templates/utils/dependency/enumerable.rb +13 -0
- data/lib/aws/templates/utils/dependency/object.rb +46 -0
- data/lib/aws/templates/utils/dependency.rb +121 -0
- data/lib/aws/templates/utils/dependent.rb +28 -0
- data/lib/aws/templates/utils/inheritable.rb +52 -0
- data/lib/aws/templates/utils/late_bound.rb +89 -0
- data/lib/aws/templates/utils/memoized.rb +27 -0
- data/lib/aws/templates/utils/named.rb +19 -0
- data/lib/aws/templates/utils/options.rb +279 -0
- data/lib/aws/templates/utils/parametrized/constraints.rb +423 -0
- data/lib/aws/templates/utils/parametrized/getters.rb +293 -0
- data/lib/aws/templates/utils/parametrized/guarded.rb +32 -0
- data/lib/aws/templates/utils/parametrized/mapper.rb +73 -0
- data/lib/aws/templates/utils/parametrized/nested.rb +72 -0
- data/lib/aws/templates/utils/parametrized/transformations.rb +660 -0
- data/lib/aws/templates/utils/parametrized.rb +240 -0
- data/lib/aws/templates/utils.rb +219 -0
- data/lib/aws/templates.rb +16 -0
- data/spec/aws/templates/artifact_spec.rb +161 -0
- data/spec/aws/templates/composite_spec.rb +361 -0
- data/spec/aws/templates/render/utils/base_type_views_spec.rb +104 -0
- data/spec/aws/templates/render_spec.rb +62 -0
- data/spec/aws/templates/utils/as_named_spec.rb +31 -0
- data/spec/aws/templates/utils/contextualized/filters_spec.rb +108 -0
- data/spec/aws/templates/utils/contextualized_spec.rb +115 -0
- data/spec/aws/templates/utils/late_bound_spec.rb +52 -0
- data/spec/aws/templates/utils/options_spec.rb +67 -0
- data/spec/aws/templates/utils/parametrized/constraint_spec.rb +175 -0
- data/spec/aws/templates/utils/parametrized/getters_spec.rb +139 -0
- data/spec/aws/templates/utils/parametrized/transformation_spec.rb +314 -0
- data/spec/aws/templates/utils/parametrized_spec.rb +241 -0
- data/spec/spec_helper.rb +6 -0
- metadata +244 -0
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'aws/templates/exceptions'
|
2
|
+
require 'aws/templates/utils/parametrized/guarded'
|
3
|
+
require 'aws/templates/utils/inheritable'
|
4
|
+
require 'aws/templates/utils/dependent'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
module Aws
|
8
|
+
module Templates
|
9
|
+
module Utils
|
10
|
+
##
|
11
|
+
# 'parameter' DSL to specify artifact parameters checks
|
12
|
+
#
|
13
|
+
# The module provides the basic class-level methods to define
|
14
|
+
# so-called parameters. A parameter is a reader-like method of
|
15
|
+
# value extraction, constraints checking and transformation. Essentially,
|
16
|
+
# it's domain-specific extended implementation of attr_reader.
|
17
|
+
module Parametrized
|
18
|
+
include Guarded
|
19
|
+
include Inheritable
|
20
|
+
include Dependent
|
21
|
+
|
22
|
+
##
|
23
|
+
# Parameter object
|
24
|
+
#
|
25
|
+
# The object incorporates parameter specification and basic logic for
|
26
|
+
# value extraction, checking and transformation. Parameter objects
|
27
|
+
# are created at each parameter description.
|
28
|
+
class Parameter
|
29
|
+
attr_reader :name
|
30
|
+
attr_accessor :description
|
31
|
+
|
32
|
+
def getter(instance = nil)
|
33
|
+
@getter || (
|
34
|
+
instance &&
|
35
|
+
(
|
36
|
+
(instance.respond_to?(:getter) && instance.getter) ||
|
37
|
+
(instance.class.respond_to?(:getter) && instance.class.getter)
|
38
|
+
)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_accessor :transform
|
43
|
+
attr_accessor :constraint
|
44
|
+
attr_accessor :klass
|
45
|
+
|
46
|
+
##
|
47
|
+
# Create a parameter object with given specification
|
48
|
+
#
|
49
|
+
# * +name+ - parameter name
|
50
|
+
# * +enclosing_class+ - the class the parameter was declared at
|
51
|
+
# * +specification+ - parameter specification; it includes:
|
52
|
+
# ** +:description+ - human-readable parameter description
|
53
|
+
# ** +:getter+ - getter Proc which will be used for parameter extraction
|
54
|
+
# from input hash or plain value. The Proc shouldn't
|
55
|
+
# expect any arguments and it will be executed in
|
56
|
+
# the instance context. If a plain value is passed
|
57
|
+
# it will be used as is. If the argument is not specified
|
58
|
+
# then value will be extracted from the input hash by
|
59
|
+
# the parameter name (see Getter for more information)
|
60
|
+
# ** +:transform+ - transform Proc which will be used for transforming
|
61
|
+
# extracted value. It should expect single parameter
|
62
|
+
# and it will be executed in instance context.
|
63
|
+
# if not specified not transformation will be
|
64
|
+
# performed (see Transformation for more information)
|
65
|
+
# ** +:constraint+ - constraint Proc which will be used to check
|
66
|
+
# the value after transformation step. The Proc
|
67
|
+
# is expected to receive one arg and throw an
|
68
|
+
# exception if constraints are not met
|
69
|
+
# (see Constraint for more information)
|
70
|
+
def initialize(name, enclosing_class, specification = {})
|
71
|
+
@name = name
|
72
|
+
set_specification(enclosing_class, specification)
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Get the parameter value from the instance
|
77
|
+
#
|
78
|
+
# It is used internally in auto-generated accessors to get the value
|
79
|
+
# from input hash. The method extracts value from the hash and
|
80
|
+
# pushes it through transformation and constraint stages. Also,
|
81
|
+
# you can specify value as the optional parameter so getter even
|
82
|
+
# if present will be ignored. It relies on presence of options
|
83
|
+
# accessor in the instance.
|
84
|
+
# * +instance+ - instance to extract the parameter value from
|
85
|
+
def get(instance)
|
86
|
+
process_value(instance, extract_value(instance))
|
87
|
+
end
|
88
|
+
|
89
|
+
def process_value(instance, value)
|
90
|
+
value = instance.instance_exec(self, value, &transform) if transform
|
91
|
+
instance.instance_exec(self, value, &constraint) if constraint
|
92
|
+
value
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def extract_value(instance)
|
98
|
+
raise ParameterGetterIsNotDefined.new(self) unless getter(instance)
|
99
|
+
execute_getter(instance, getter(instance))
|
100
|
+
end
|
101
|
+
|
102
|
+
def execute_getter(instance, getter)
|
103
|
+
if getter.respond_to?(:to_hash)
|
104
|
+
getter
|
105
|
+
elsif getter.respond_to?(:to_proc)
|
106
|
+
instance.instance_exec(self, &getter)
|
107
|
+
else
|
108
|
+
getter
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
ALLOWED_SPECIFICATION_ENTRIES = Set.new %i[description getter transform constraint]
|
113
|
+
|
114
|
+
def set_specification(enclosing_class, specification) # :nodoc:
|
115
|
+
@klass = enclosing_class
|
116
|
+
|
117
|
+
wrong_options = specification.keys.reject do |option_name|
|
118
|
+
ALLOWED_SPECIFICATION_ENTRIES.include?(option_name)
|
119
|
+
end
|
120
|
+
|
121
|
+
raise_wrong_options(wrong_options) unless wrong_options.empty?
|
122
|
+
|
123
|
+
process_specification(specification)
|
124
|
+
end
|
125
|
+
|
126
|
+
def raise_wrong_options(wrong_options)
|
127
|
+
raise ParameterSpecificationIsInvalid.new(self, wrong_options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def process_specification(spec)
|
131
|
+
@description = spec[:description] if spec.key?(:description)
|
132
|
+
@getter = spec[:getter] if spec.key?(:getter)
|
133
|
+
@transform = spec[:transform] if spec.key?(:transform)
|
134
|
+
@constraint = spec[:constraint] if spec.key?(:constraint)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
instance_scope do
|
139
|
+
##
|
140
|
+
# Lazy initializer
|
141
|
+
def dependencies
|
142
|
+
if @dependencies.nil?
|
143
|
+
@dependencies = Set.new
|
144
|
+
depends_on(parameter_names.map { |name| send(name) })
|
145
|
+
end
|
146
|
+
|
147
|
+
@dependencies
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Parameter names list
|
152
|
+
#
|
153
|
+
# Instance-level alias for list_all_parameter_names
|
154
|
+
def parameter_names
|
155
|
+
self.class.list_all_parameter_names
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Validate all parameters
|
160
|
+
#
|
161
|
+
# Performs calculation of all specified parameters to check options validity
|
162
|
+
def validate
|
163
|
+
parameter_names.each { |name| send(name) }
|
164
|
+
end
|
165
|
+
|
166
|
+
attr_reader :getter
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Class-level mixins
|
171
|
+
#
|
172
|
+
# It's a DSL extension to declaratively define parameters.
|
173
|
+
class_scope do
|
174
|
+
##
|
175
|
+
# List all defined parameter names
|
176
|
+
#
|
177
|
+
# The list includes both the class parameters and all ancestor
|
178
|
+
# parameters.
|
179
|
+
def list_all_parameter_names
|
180
|
+
ancestors
|
181
|
+
.select { |mod| mod.include?(Parametrized) }
|
182
|
+
.inject(Set.new) do |parameter_collection, mod|
|
183
|
+
parameter_collection.merge(mod.parameters.keys)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Parameters defined in the class
|
189
|
+
#
|
190
|
+
# Returns map from parameter name to parameter object of the parameters
|
191
|
+
# defined only in the class.
|
192
|
+
def parameters
|
193
|
+
@parameters ||= {}
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
# Get parameter object by name
|
198
|
+
#
|
199
|
+
# Look-up by the parameter object name recursively through class
|
200
|
+
# inheritance hierarchy.
|
201
|
+
# * +parameter_alias+ - parameter name
|
202
|
+
def get_parameter(parameter_alias)
|
203
|
+
ancestor =
|
204
|
+
ancestors
|
205
|
+
.select { |mod| mod.include?(Parametrized) }
|
206
|
+
.find { |mod| mod.parameters.key?(parameter_alias) }
|
207
|
+
|
208
|
+
ancestor.parameters[parameter_alias] if ancestor
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Class-level parameter declaration method
|
213
|
+
#
|
214
|
+
# Being a part of parameter declaration DSL, it constructs
|
215
|
+
# parameter object, stores it in parameters class level registry
|
216
|
+
# and creates a reader method for it. It will throw exception
|
217
|
+
# if the parameter name is already occupied by a method or a parameter
|
218
|
+
# with the name already exists in the class or any ancestor
|
219
|
+
# * +parameter_alias+ - parameter name
|
220
|
+
# * +specification+ - parameter specification hash
|
221
|
+
def parameter(name, spec = {})
|
222
|
+
raise_already_exists(name) if method_defined?(name)
|
223
|
+
|
224
|
+
parameter_object = Parameter.new(name, self, spec)
|
225
|
+
parameters[name] = parameter_object
|
226
|
+
define_method(name) { guarded_get(self, parameter_object) }
|
227
|
+
end
|
228
|
+
|
229
|
+
def raise_already_exists(name)
|
230
|
+
parameter_object = get_parameter(name)
|
231
|
+
|
232
|
+
raise(ParameterAlreadyExist.new(parameter_object)) if parameter_object
|
233
|
+
|
234
|
+
raise ParameterMethodNameConflict.new(instance_method(name))
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'aws/templates/exceptions'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Templates
|
5
|
+
##
|
6
|
+
# Variable utility functions used through the code
|
7
|
+
module Utils
|
8
|
+
RECURSIVE_METHODS = %i[keys [] include?].freeze
|
9
|
+
|
10
|
+
##
|
11
|
+
# If the object is "recursive"
|
12
|
+
#
|
13
|
+
# Checks if object satisfies "recursive" concept. See RECURSIVE_METHODS for the list
|
14
|
+
# of methods
|
15
|
+
def self.recursive?(obj)
|
16
|
+
RECURSIVE_METHODS.all? { |m| obj.respond_to?(m) }
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# If the object is "scalar"
|
21
|
+
#
|
22
|
+
# Checks if object satisfies "scalar" concept.
|
23
|
+
def self.scalar?(obj)
|
24
|
+
!obj.respond_to?(:[])
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# If object is hashable
|
29
|
+
#
|
30
|
+
# Checks if object can be transformed into Hash
|
31
|
+
def self.hashable?(obj)
|
32
|
+
obj.respond_to?(:to_hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
PARAMETRIZED_METHODS = [:parameter_names].freeze
|
36
|
+
|
37
|
+
##
|
38
|
+
# If the object is "parametrized"
|
39
|
+
#
|
40
|
+
# Checks if object satisfies "parametrized" concept. See PARAMETRIZED_METHODS for the list
|
41
|
+
# of methods
|
42
|
+
def self.parametrized?(obj)
|
43
|
+
PARAMETRIZED_METHODS.all? { |m| obj.respond_to?(m) }
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# If object is a list
|
48
|
+
#
|
49
|
+
# Checks if object can be transformed into Array
|
50
|
+
def self.list?(obj)
|
51
|
+
obj.respond_to?(:to_ary)
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Duplicate hash recursively
|
56
|
+
#
|
57
|
+
# Duplicate the hash and all nested hashes recursively
|
58
|
+
def self.deep_dup(original)
|
59
|
+
return original unless Utils.hashable?(original)
|
60
|
+
|
61
|
+
duplicate = original.dup.to_hash
|
62
|
+
duplicate.each_pair { |k, v| duplicate[k] = deep_dup(v) }
|
63
|
+
|
64
|
+
duplicate
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Merges two nested hashes
|
69
|
+
#
|
70
|
+
# The core element of the whole framework. The principle is simple:
|
71
|
+
# both arguments are transformed to hashes if they support :to_hash
|
72
|
+
# method, the resulting hashes are merged with the standard method
|
73
|
+
# with creating a new hash. Second element takes preference if the
|
74
|
+
# function reached bottom level of recursion with only scalars left.
|
75
|
+
#
|
76
|
+
# Raises ArgumentError if a and b have incompatible types hence
|
77
|
+
# they can't be merged
|
78
|
+
#
|
79
|
+
# ==== Example
|
80
|
+
#
|
81
|
+
# Options.merge('a', 'b') # => 'b'
|
82
|
+
# Options.merge({'1'=>'2'}, {'3'=>'4'}) # => {'1'=>'2', '3'=>'4'}
|
83
|
+
# Options.merge(
|
84
|
+
# { '1' => { '2' => '3' } },
|
85
|
+
# { '1' => { '4' => '5' } }
|
86
|
+
# ) # => { '1' => { '2' => '3', '4'=>'5' } }
|
87
|
+
##
|
88
|
+
# Recursively merge two "recursive" objects
|
89
|
+
# PS: Yes I know that there is "merge" method for *hashes*.
|
90
|
+
def self.merge(a, b)
|
91
|
+
return hashify(b) unless Utils.recursive?(a) && Utils.recursive?(b)
|
92
|
+
_merge_back(_merge_forward(a, b), b)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self._merge_forward(a, b)
|
96
|
+
a.keys.each_with_object({}) do |k, hsh|
|
97
|
+
hsh[k] = b[k].nil? ? hashify(a[k]) : merge(a[k], b[k])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self._merge_back(result, b)
|
102
|
+
b.keys.reject { |k| result.include?(k) }.each_with_object(result) { |k, res| res[k] = b[k] }
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.hashify(v)
|
106
|
+
return v unless Utils.recursive?(v)
|
107
|
+
v.keys.each_with_object({}) { |k, hsh| hsh[k] = hashify(v[k]) }
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Deletion marker
|
112
|
+
#
|
113
|
+
# Since Options use lazy merging (effectively keeping all hashes passed during
|
114
|
+
# initialization immutable) and iterates through all of them during value look-up, we need
|
115
|
+
# a way of marking some branch as deleted when deletion operation is invoked on an Options
|
116
|
+
# object. So, the algorithm marks branch with the object to stop iteration during look-up.
|
117
|
+
DELETED_MARKER = Object.new
|
118
|
+
|
119
|
+
##
|
120
|
+
# Get a parameter from resulting hash or any nested part of it
|
121
|
+
#
|
122
|
+
# The method can access resulting hash as a tree performing
|
123
|
+
# traverse as needed. Also, it handles nil-pointer situations
|
124
|
+
# correctly so you will get no exception but just 'nil' even when
|
125
|
+
# the whole branch you're trying to access don't exist or contains
|
126
|
+
# non-hash value somewhere in the middle. Also, the method
|
127
|
+
# recognizes asterisk (*) hash records which is an analog of
|
128
|
+
# match-all or default values for some sub-branch.
|
129
|
+
#
|
130
|
+
# * +path+ - an array representing path of the value in the nested
|
131
|
+
# hash
|
132
|
+
#
|
133
|
+
# ==== Example
|
134
|
+
#
|
135
|
+
# opts = Options.new(
|
136
|
+
# 'a' => {
|
137
|
+
# 'b' => 'c',
|
138
|
+
# '*' => { '*' => 2 }
|
139
|
+
# },
|
140
|
+
# 'd' => 1
|
141
|
+
# )
|
142
|
+
# opts.to_hash # => { 'a' => { 'b' => 'c', '*' => { '*' => 2 } }, 'd' => 1 }
|
143
|
+
# opts['a'] # => Options.new('b' => 'c', '*' => { '*' => 2 })
|
144
|
+
# opts['a', 'b'] # => 'c'
|
145
|
+
# opts['d', 'e'] # => nil
|
146
|
+
# # multi-level wildcard match
|
147
|
+
# opts['a', 'z', 'r'] # => 2
|
148
|
+
def self.lookup(value, path)
|
149
|
+
# we stop lookup and return nil if nil is encountered
|
150
|
+
return if value.nil?
|
151
|
+
# value was deleted in this layer
|
152
|
+
raise OptionValueDeleted.new(path) if value == DELETED_MARKER
|
153
|
+
# we reached our target! returning it
|
154
|
+
return value if path.nil? || path.empty?
|
155
|
+
# we still have some part of path to traverse but scalar was found
|
156
|
+
raise OptionScalarOnTheWay.new(value, path) if Utils.scalar?(value)
|
157
|
+
|
158
|
+
_lookup_recursively(value, path.dup)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self._lookup_recursively(value, path)
|
162
|
+
current_key = path.shift
|
163
|
+
|
164
|
+
# is there a value defined for the key in the current recursive structure?
|
165
|
+
if value.include?(current_key)
|
166
|
+
# yes - look-up the rest of the path
|
167
|
+
return_value = lookup(value[current_key], path)
|
168
|
+
# if value was still not found - resorting to wildcard path
|
169
|
+
return_value.nil? ? lookup(value[:*], path) : return_value
|
170
|
+
elsif value.include?(:*)
|
171
|
+
# if there is no key but the recursive has a default value defined - dive into the
|
172
|
+
# wildcard branch
|
173
|
+
lookup(value[:*], path)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Sets a value in hierarchy
|
179
|
+
#
|
180
|
+
# Sets a path in a nested recursive hash structure to the specified value
|
181
|
+
#
|
182
|
+
# * +container+ - container with the specified path
|
183
|
+
# * +value+ - the value to set the path to
|
184
|
+
# * +path+ - path containing the target value
|
185
|
+
def self.set_recursively(container, value, path)
|
186
|
+
last_key = path.pop
|
187
|
+
|
188
|
+
last_branch = path.inject(container) do |obj, current_key|
|
189
|
+
raise OptionScalarOnTheWay.new(obj, path) unless Utils.recursive?(obj)
|
190
|
+
if obj.include?(current_key)
|
191
|
+
obj[current_key]
|
192
|
+
else
|
193
|
+
obj[current_key] = {}
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
last_branch[last_key] = value
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Select object recursively
|
202
|
+
#
|
203
|
+
# Selects objects recursively from the specified container with specified block predicate.
|
204
|
+
#
|
205
|
+
# * +container+ - container to recursively select items from
|
206
|
+
# * +blk+ - the predicate
|
207
|
+
def self.select_recursively(container, &blk)
|
208
|
+
container.keys.each_with_object([]) do |k, collection|
|
209
|
+
value = container[k]
|
210
|
+
if blk.call(value)
|
211
|
+
collection << value
|
212
|
+
elsif Utils.recursive?(value)
|
213
|
+
collection.concat(select_recursively(value, &blk))
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'aws/templates/artifact'
|
2
|
+
require 'aws/templates/composite'
|
3
|
+
require 'aws/templates/render'
|
4
|
+
require 'aws/templates/utils/named'
|
5
|
+
require 'aws/templates/utils/parametrized/constraints'
|
6
|
+
require 'aws/templates/utils/parametrized/transformations'
|
7
|
+
require 'aws/templates/utils/parametrized/getters'
|
8
|
+
|
9
|
+
##
|
10
|
+
# :main: README.md
|
11
|
+
module Aws
|
12
|
+
##
|
13
|
+
# Main namespace for the library-related functionality
|
14
|
+
module Templates
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'aws/templates/artifact'
|
3
|
+
|
4
|
+
module TestTest
|
5
|
+
module A; end
|
6
|
+
module B; end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Aws::Templates::Artifact do
|
10
|
+
let(:artifact_class) do
|
11
|
+
Class.new(Aws::Templates::Artifact) do
|
12
|
+
default a: 'b',
|
13
|
+
b: proc { options[:c].upcase }
|
14
|
+
|
15
|
+
parameter :c
|
16
|
+
parameter :d
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:dependency) do
|
21
|
+
Struct.new(:"dependency?", :dependencies)
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:just_object) do
|
25
|
+
Struct.new(:root)
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'featuring class is created' do
|
29
|
+
let(:featuring_class) { artifact_class.featuring(TestTest::A, TestTest::B) }
|
30
|
+
|
31
|
+
it 'returns correct class name' do
|
32
|
+
expect(featuring_class.to_s).to match(/Subclass.*TestTest..B.*TestTest..A/)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'instance created' do
|
37
|
+
let(:params) do
|
38
|
+
{
|
39
|
+
c: 'qwe',
|
40
|
+
d: {
|
41
|
+
e: {
|
42
|
+
f: { a: dependency.new(true, [just_object.new(2)]) },
|
43
|
+
g: { d: dependency.new(true, [just_object.new(1)]) }
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
let(:instance) { artifact_class.new(params) }
|
50
|
+
|
51
|
+
context 'label is not specified' do
|
52
|
+
it 'contains auto-generated label' do
|
53
|
+
expect(instance.label).not_to be_nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'root is not specified' do
|
58
|
+
it 'root is not empty' do
|
59
|
+
expect(instance.root).not_to be_nil
|
60
|
+
end
|
61
|
+
it 'doesn\'t have any dependencies' do
|
62
|
+
expect(instance.dependencies).to be_empty
|
63
|
+
end
|
64
|
+
end
|
65
|
+
context 'label is specified' do
|
66
|
+
before { params[:label] = 'b' }
|
67
|
+
it 'contains passed label' do
|
68
|
+
expect(instance.label).to be == 'b'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
context 'root is specified' do
|
72
|
+
before { params[:root] = 1 }
|
73
|
+
it 'contains one dependency' do
|
74
|
+
expect(instance.dependencies).to be == [just_object.new(1)].to_set
|
75
|
+
end
|
76
|
+
it 'contains passed root' do
|
77
|
+
expect(instance.root).to be == 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
context 'different root is specified' do
|
81
|
+
before { params[:root] = 2 }
|
82
|
+
it 'contains one dependency' do
|
83
|
+
expect(instance.dependencies).to be == [just_object.new(2)].to_set
|
84
|
+
end
|
85
|
+
end
|
86
|
+
context 'no overrides specified' do
|
87
|
+
before { params.merge!(root: 3, label: 'thing') }
|
88
|
+
|
89
|
+
let(:expected) do
|
90
|
+
{
|
91
|
+
label: 'thing',
|
92
|
+
a: 'b',
|
93
|
+
b: 'QWE',
|
94
|
+
c: 'qwe',
|
95
|
+
d: {
|
96
|
+
e: {
|
97
|
+
f: { a: dependency.new(true, [just_object.new(2)]) },
|
98
|
+
g: { d: dependency.new(true, [just_object.new(1)]) }
|
99
|
+
}
|
100
|
+
},
|
101
|
+
root: 3
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'calculates with defaults' do
|
106
|
+
expect(instance.options.to_hash).to be == expected
|
107
|
+
end
|
108
|
+
end
|
109
|
+
context 'override is present in input hash' do
|
110
|
+
before { params.merge!(root: 3, label: 'thing', a: 'rty') }
|
111
|
+
|
112
|
+
let(:expected) do
|
113
|
+
{
|
114
|
+
label: 'thing',
|
115
|
+
a: 'rty',
|
116
|
+
b: 'QWE',
|
117
|
+
c: 'qwe',
|
118
|
+
d: {
|
119
|
+
e: {
|
120
|
+
f: { a: dependency.new(true, [just_object.new(2)]) },
|
121
|
+
g: { d: dependency.new(true, [just_object.new(1)]) }
|
122
|
+
}
|
123
|
+
},
|
124
|
+
root: 3
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'calculates with defaults' do
|
129
|
+
expect(instance.options.to_hash).to be == expected
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'instance of child class created' do
|
135
|
+
let(:child_class) do
|
136
|
+
Class.new(artifact_class) do
|
137
|
+
default a: 'c',
|
138
|
+
c: proc { options[:d].tr(' ', '.') }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
let(:params) { { root: 3, label: 'thing', d: 'q w e' } }
|
143
|
+
|
144
|
+
let(:child_instance) { child_class.new(params) }
|
145
|
+
|
146
|
+
let(:expected) do
|
147
|
+
{
|
148
|
+
root: 3,
|
149
|
+
label: 'thing',
|
150
|
+
a: 'c',
|
151
|
+
b: 'Q.W.E',
|
152
|
+
c: 'q.w.e',
|
153
|
+
d: 'q w e'
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'overlays overrides' do
|
158
|
+
expect(child_instance.options.to_hash).to be == expected
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|