cloud-templates 0.1.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 +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
|