Class: Aws::Templates::Utils::Options
- Inherits:
-
Object
- Object
- Aws::Templates::Utils::Options
- Defined in:
- lib/aws/templates/utils/options.rb
Overview
Options hash-like class
Implements the core mechanism of hash lookup, merge, and transformation.
It supports nested hash lookup with index function and wildcard hash definitions on any level of nested hierarchy so you can define fallback values right in the input hash. The algorithm will try to select the best combination if it doesn’t see the exact match during lookup.
Instance Method Summary collapse
-
#[](*path) ⇒ Object
Get a parameter from resulting hash or any nested part of it.
-
#[]=(*path_and_value) ⇒ Object
Set the parameter with the path to the value.
-
#compact! ⇒ Object
Squash all layers into one.
- #cow! ⇒ Object
-
#delete(*path) ⇒ Object
Delete a branch.
- #dependencies ⇒ Object
- #dependency? ⇒ Boolean
-
#dup ⇒ Object
Duplicate the options.
-
#filter ⇒ Object
Filter options.
-
#include?(k) ⇒ Boolean
If top-level key exists.
-
#initialize(*structures) ⇒ Options
constructor
Initialize Options with list of recursive structures (See Options#recursive?).
-
#keys ⇒ Object
Top-level keys.
-
#merge(other) ⇒ Object
Merge Options with object.
-
#merge!(other) ⇒ Object
Merge Options with object in-place.
- #select_recursively(&blk) ⇒ Object
- #structures ⇒ Object
-
#to_filter ⇒ Object
Create filter.
-
#to_hash ⇒ Object
Transforms to hash object.
-
#to_recursive ⇒ Object
The class already supports recursive concept so return self.
-
#unshift_layer!(layer) ⇒ Object
Put the layer to the bottom of the stack.
Methods included from Memoized
Constructor Details
#initialize(*structures) ⇒ Options
Initialize Options with list of recursive structures (See Options#recursive?)
225 226 227 |
# File 'lib/aws/templates/utils/options.rb', line 225 def initialize(*structures) structures.reject(&:nil?).each { |container| merge!(container) } end |
Instance Method Details
#[](*path) ⇒ Object
Get a parameter from resulting hash or any nested part of it
The method can access resulting hash as a tree performing traverse as needed. Also, it handles nil-pointer situations correctly so you will get no exception but just ‘nil’ even when the whole branch you’re trying to access don’t exist or contains non-hash value somewhere in the middle. Also, the method recognizes asterisk (*) hash records which is an analog of match-all or default values for some sub-branch.
-
path
- an array representing path of the value in the nestedhash
Example
opts = Options.new(
'a' => {
'b' => 'c',
'*' => { '*' => 2 }
},
'd' => 1
)
opts.to_hash # => { 'a' => { 'b' => 'c', '*' => { '*' => 2 } }, 'd' => 1 }
opts['a'] # => Options.new('b' => 'c', '*' => { '*' => 2 })
opts['a', 'b'] # => 'c'
opts['d', 'e'] # => nil
# multi-level wildcard match
opts['a', 'z', 'r'] # => 2
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/aws/templates/utils/options.rb', line 58 def [](*path) structures.reverse_each.inject(nil) do |memo, container| ret = begin Utils.lookup(container, path.dup) rescue Exception::OptionValueDeleted, Exception::OptionScalarOnTheWay # we discovered that this layer either have value deleted or parent was overriden # by a scalar. Either way we just return what we have in the memo break memo end # if current container doesn't have this value - let's go to the next iteration next memo if ret.nil? # if found value is a scalar then either we return it as is or return memo # if memo is not nil it means that we've found hierarchical objects before break(memo.nil? ? ret : memo) unless Utils.recursive?(ret) # value is not a scalar. it means we need to keep merging them memo.nil? ? Options.new(ret) : memo.unshift_layer!(ret) end end |
#[]=(*path_and_value) ⇒ Object
Set the parameter with the path to the value
The method can access resulting hash as a tree performing traverse as needed. When stubled uponAlso, it handles non-existent keys correctly creating sub-branches as necessary. If a non-hash and non-nil value discovered in the middle of the path, an exception will be thrown. The method doesn’t give any special meaning to wildcards keys so you can set wildcard parameters also.
-
path
- an array representing path of the value in the nestedhash
-
value
- value to set the parameter to
Example
opts = Options.new({})
opts.to_hash # => {}
opts['a', 'b'] = 'c'
opts['a', '*', '*'] = 2
opts['d'] = 1
opts.to_hash # => { 'a' => { 'b' => 'c', '*' => { '*' => 2 } }, 'd' => 1 }
120 121 122 123 124 125 |
# File 'lib/aws/templates/utils/options.rb', line 120 def []=(*path_and_value) value = path_and_value.pop path = path_and_value dirty!.cow! # mooo Utils.set_recursively(structures.last, value, path) end |
#compact! ⇒ Object
Squash all layers into one
Options is designed with very specific goal to be memory-friendly and re-use merged objects as immutable layers. However, after some particular threshold of layer’s stack size, performance of index operations can suffer significantly. To mitigate this user can use the method to squash all layers into one aggregated hash.
The method performs in-place stack modification
247 248 249 250 |
# File 'lib/aws/templates/utils/options.rb', line 247 def compact! @structures = [to_hash] self end |
#cow! ⇒ Object
270 271 272 273 274 275 276 277 |
# File 'lib/aws/templates/utils/options.rb', line 270 def cow! unless @is_cowed structures << {} @is_cowed = true end self end |
#delete(*path) ⇒ Object
Delete a branch
Delete a branch in the options. Rather than deleting it from hash, the path is assigned with special marker that it was deleted. It helps avoid hash recalculation leading to memory thrashing simultaneously maintaining semantics close to Hash#delete
133 134 135 |
# File 'lib/aws/templates/utils/options.rb', line 133 def delete(*path) self[*path] = Utils::DELETED_MARKER end |
#dependencies ⇒ Object
84 85 86 87 88 89 90 91 92 |
# File 'lib/aws/templates/utils/options.rb', line 84 def dependencies # rubocop:disable Style/SymbolProc # Refinements don't support dynamic dispatch yet. So, symbolic methods don't work memoize(:dependencies) do select_recursively { |obj| obj.dependency? } .inject(::Set.new) { |acc, elem| acc.merge(elem.dependencies) } end # rubocop:enable Style/SymbolProc end |
#dependency? ⇒ Boolean
80 81 82 |
# File 'lib/aws/templates/utils/options.rb', line 80 def dependency? !dependencies.empty? end |
#dup ⇒ Object
Duplicate the options
Duplicates the object itself and puts another layer of hash map. All original hash maps are not touched if the duplicate is modified.
234 235 236 |
# File 'lib/aws/templates/utils/options.rb', line 234 def dup Options.new(*structures) end |
#filter ⇒ Object
Filter options
Filter options with provided Proc. The proc should accept one parameter satisfying “recursive” contract. See Utils.recursive
219 220 221 |
# File 'lib/aws/templates/utils/options.rb', line 219 def filter Options.new(yield self) end |
#include?(k) ⇒ Boolean
If top-level key exists
Checks if top-level key exists. Deleted branches are excluded.
181 182 183 184 |
# File 'lib/aws/templates/utils/options.rb', line 181 def include?(k) found = structures.reverse_each.find { |container| container.include?(k) } !found.nil? && (found[k] != Utils::DELETED_MARKER) end |
#keys ⇒ Object
Top-level keys
Produces a list of top-level keys from all layers. Deleted branches are not included.
165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/aws/templates/utils/options.rb', line 165 def keys memoize(:keys) do structures .each_with_object(::Set.new) do |container, keyset| container.keys.each do |k| container[k] == Utils::DELETED_MARKER ? keyset.delete(k) : keyset.add(k) end end .to_a end end |
#merge(other) ⇒ Object
Merge Options with object
Create new Options object which is a merge of the target Options instance with an object. The object must be “recusrsive” meaning it should satisfy minimum contract for “recursive”. See Utils::recursive? for details
192 193 194 |
# File 'lib/aws/templates/utils/options.rb', line 192 def merge(other) self.class.new(*structures, other) end |
#merge!(other) ⇒ Object
Merge Options with object in-place
Put the passed object as the top layer of the current instance. The object must be “recursive” meaning it should satisfy minimum contract for “recursive”. See Utils::recursive? for details
202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/aws/templates/utils/options.rb', line 202 def merge!(other) raise Exception::OptionShouldBeRecursive.new(other) unless Utils.recursive?(other) if other.is_a?(self.class) structures.concat(other.structures) else structures << other end dirty! end |
#select_recursively(&blk) ⇒ Object
94 95 96 |
# File 'lib/aws/templates/utils/options.rb', line 94 def select_recursively(&blk) Utils.select_recursively(self, &blk) end |
#structures ⇒ Object
25 26 27 |
# File 'lib/aws/templates/utils/options.rb', line 25 def structures @structures ||= [] end |
#to_filter ⇒ Object
Create filter
Gets hash representstion of the Options instance and transforms it to filter
157 158 159 |
# File 'lib/aws/templates/utils/options.rb', line 157 def to_filter to_hash.to_filter end |
#to_hash ⇒ Object
Transforms to hash object
Produces a hash out of Options object merging COW layers iteratively and calculating them recursively.
142 143 144 145 146 |
# File 'lib/aws/templates/utils/options.rb', line 142 def to_hash memoize(:to_hash) do _process_hashed(structures.inject({}) { |acc, elem| Utils.merge(acc, elem) }) end end |
#to_recursive ⇒ Object
The class already supports recursive concept so return self
149 150 151 |
# File 'lib/aws/templates/utils/options.rb', line 149 def to_recursive self end |
#unshift_layer!(layer) ⇒ Object
Put the layer to the bottom of the stack
However it doesn’t resemble exact semantics, the method is similar to reverse_merge! from ActiveSupport. It puts the “recursive” object passed to the bottom of the layer stack of the Options instance effectively making it the least prioritized layer.
258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/aws/templates/utils/options.rb', line 258 def unshift_layer!(layer) raise Exception::OptionShouldBeRecursive.new(layer) unless Utils.recursive?(layer) if layer.is_a?(self.class) layer.structures.dup.concat(structures) else structures.unshift(layer) end dirty! end |