Class: Aws::Templates::Utils::Options

Inherits:
Object
  • Object
show all
Includes:
Memoized
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

Methods included from Memoized

#dirty!, #memoize, #memoized

Constructor Details

#initialize(*structures) ⇒ Options

Initialize Options with list of recursive structures (See Options#recursive?)



211
212
213
214
215
216
217
218
219
220
221
# File 'lib/aws/templates/utils/options.rb', line 211

def initialize(*structures)
  @structures = structures.map do |container|
    if Utils.recursive?(container)
      container
    elsif Utils.hashable?(container)
      container.to_hash
    else
      raise OptionShouldBeRecursive.new(container)
    end
  end
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 nested

    hash
    

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


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/aws/templates/utils/options.rb', line 53

def [](*path)
  @structures.reverse_each.inject(nil) do |memo, container|
    ret = begin
      Utils.lookup(container, path.dup)
    rescue OptionValueDeleted, 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 nested

    hash
    
  • 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 }


112
113
114
115
116
117
# File 'lib/aws/templates/utils/options.rb', line 112

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



241
242
243
244
# File 'lib/aws/templates/utils/options.rb', line 241

def compact!
  @structures = [to_hash]
  self
end

#cow!Object



258
259
260
261
262
263
264
265
# File 'lib/aws/templates/utils/options.rb', line 258

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



125
126
127
# File 'lib/aws/templates/utils/options.rb', line 125

def delete(*path)
  self[*path] = DELETED_MARKER
end

#dependenciesObject



79
80
81
82
83
84
# File 'lib/aws/templates/utils/options.rb', line 79

def dependencies
  memoize(:dependencies) do
    select_recursively(&:dependency?)
      .inject(Set.new) { |acc, elem| acc.merge(elem.dependencies) }
  end
end

#dependency?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/aws/templates/utils/options.rb', line 75

def dependency?
  !dependencies.empty?
end

#dupObject

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.



228
229
230
# File 'lib/aws/templates/utils/options.rb', line 228

def dup
  Options.new(*@structures)
end

#filterObject

Filter options

Filter options with provided Proc. The proc should accept one parameter satisfying “recursive” contract. See Utils.recursive



205
206
207
# File 'lib/aws/templates/utils/options.rb', line 205

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.

Returns:

  • (Boolean)


173
174
175
176
# File 'lib/aws/templates/utils/options.rb', line 173

def include?(k)
  found = @structures.reverse_each.find { |container| container.include?(k) }
  !found.nil? && (found[k] != DELETED_MARKER)
end

#keysObject

Top-level keys

Produces a list of top-level keys from all layers. Deleted branches are not included.



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/aws/templates/utils/options.rb', line 157

def keys
  memoize(:keys) do
    @structures
      .each_with_object(Set.new) do |container, keyset|
        container.keys.each do |k|
          container[k] == 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



184
185
186
# File 'lib/aws/templates/utils/options.rb', line 184

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



194
195
196
197
198
# File 'lib/aws/templates/utils/options.rb', line 194

def merge!(other)
  raise OptionShouldBeRecursive.new(other) unless Utils.recursive?(other)
  @structures << other
  dirty!
end

#select_recursively(&blk) ⇒ Object



86
87
88
# File 'lib/aws/templates/utils/options.rb', line 86

def select_recursively(&blk)
  Utils.select_recursively(self, &blk)
end

#to_filterObject

Create filter

Gets hash representstion of the Options instance and transforms it to filter



149
150
151
# File 'lib/aws/templates/utils/options.rb', line 149

def to_filter
  to_hash.to_filter
end

#to_hashObject

Transforms to hash object

Produces a hash out of Options object merging COW layers iteratively and calculating them recursively.



134
135
136
137
138
# File 'lib/aws/templates/utils/options.rb', line 134

def to_hash
  memoize(:to_hash) do
    _process_hashed(@structures.inject({}) { |acc, elem| Utils.merge(acc, elem) })
  end
end

#to_recursiveObject

The class already supports recursive concept so return self



141
142
143
# File 'lib/aws/templates/utils/options.rb', line 141

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.



252
253
254
255
256
# File 'lib/aws/templates/utils/options.rb', line 252

def unshift_layer!(layer)
  raise OptionShouldBeRecursive.new(layer) unless Utils.recursive?(layer)
  @structures.unshift(layer)
  dirty!
end