module Aws::Templates::Utils

Variable utility functions used through the code.

Defines auxiliary functions set and serves as the namespace for the framework modules.

Constants

DELETED_MARKER

Deletion marker

Since Options use lazy merging (effectively keeping all hashes passed during initialization immutable) and iterates through all of them during value look-up, we need a way of marking some branch as deleted when deletion operation is invoked on an Options object. So, the algorithm marks branch with the object to stop iteration during look-up.

PARAMETRIZED_METHODS
PATH_REGEXP
RECURSIVE_METHODS

Public Class Methods

_lookup_recursively(value, path) click to toggle source
# File lib/aws/templates/utils.rb, line 181
def self._lookup_recursively(value, path)
  current_key = path.shift

  # is there a value defined for the key in the current recursive structure?
  if value.include?(current_key)
    # yes - look-up the rest of the path
    return_value = lookup(value[current_key], path)
    # if value was still not found - resorting to wildcard path
    return_value.nil? ? lookup(value[:*], path) : return_value
  elsif value.include?(:*)
    # if there is no key but the recursive has a default value defined - dive into the
    # wildcard branch
    lookup(value[:*], path)
  end
end
_merge_back(result, b) click to toggle source
# File lib/aws/templates/utils.rb, line 119
def self._merge_back(result, b)
  b.keys
   .reject { |k| result.include?(k) }
   .each_with_object(result) { |k, res| res[k] = hashify(b[k]) }
end
_merge_forward(a, b, blk) click to toggle source
# File lib/aws/templates/utils.rb, line 113
def self._merge_forward(a, b, blk)
  a.keys.each_with_object({}) do |k, hsh|
    hsh[k] = b[k].nil? ? hashify(a[k]) : merge(a[k], b[k], &blk)
  end
end
deep_dup(original) click to toggle source

Duplicate hash recursively

Duplicate the hash and all nested hashes recursively

# File lib/aws/templates/utils.rb, line 73
def self.deep_dup(original)
  return original unless Utils.hashable?(original)

  duplicate = original.dup.to_hash
  duplicate.each_pair { |k, v| duplicate[k] = deep_dup(v) }

  duplicate
end
hashable?(obj) click to toggle source

If object is hashable

Checks if object can be transformed into Hash

# File lib/aws/templates/utils.rb, line 46
def self.hashable?(obj)
  obj.respond_to?(:to_hash)
end
hashify(v) click to toggle source
# File lib/aws/templates/utils.rb, line 125
def self.hashify(v)
  return v unless Utils.recursive?(v)
  v.keys.each_with_object({}) { |k, hsh| hsh[k] = hashify(v[k]) }
end
list?(obj) click to toggle source

If object is a list

Checks if object can be transformed into Array

# File lib/aws/templates/utils.rb, line 65
def self.list?(obj)
  obj.respond_to?(:to_ary)
end
lookup(value, path) click to toggle source

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
# File lib/aws/templates/utils.rb, line 168
def self.lookup(value, path)
  # we stop lookup and return nil if nil is encountered
  return if value.nil?
  # value was deleted in this layer
  raise Exception::OptionValueDeleted.new(path) if value == DELETED_MARKER
  # we reached our target! returning it
  return value if path.nil? || path.empty?
  # we still have some part of path to traverse but scalar was found
  raise Exception::OptionScalarOnTheWay.new(value, path) if Utils.scalar?(value)

  _lookup_recursively(value, path.dup)
end
lookup_module(str) click to toggle source
# File lib/aws/templates/utils.rb, line 240
def self.lookup_module(str)
  target = str.split(PATH_REGEXP)
              .inject(::Object.lazy) { |acc, elem| acc.const_get(elem.modulize) }
              .reduce

  raise "#{str} == #{target} which is not a module" unless target.is_a?(Module)

  target
end
merge(a, b, &blk) click to toggle source

Merges two nested hashes

The core element of the whole framework. The principle is simple: both arguments are transformed to hashes if they support :to_hash method, the resulting hashes are merged with the standard method with creating a new hash. Second element takes preference if the function reached bottom level of recursion with only scalars left.

Raises ArgumentError if a and b have incompatible types hence they can't be merged

Example

Options.merge('a', 'b') # => 'b'
Options.merge({'1'=>'2'}, {'3'=>'4'}) # => {'1'=>'2', '3'=>'4'}
Options.merge(
  { '1' => { '2' => '3' } },
  { '1' => { '4' => '5' } }
) # => { '1' => { '2' => '3', '4'=>'5' } }

Recursively merge two “recursive” objects PS: Yes I know that there is “merge” method for hashes.

# File lib/aws/templates/utils.rb, line 105
def self.merge(a, b, &blk)
  unless Utils.recursive?(a) && Utils.recursive?(b)
    return hashify(blk.nil? ? b : blk.call(a, b))
  end

  _merge_back(_merge_forward(a, b, blk), b)
end
parametrized?(obj) click to toggle source

If the object is “parametrized”

Checks if object satisfies “parametrized” concept. See PARAMETRIZED_METHODS for the list of methods

# File lib/aws/templates/utils.rb, line 57
def self.parametrized?(obj)
  PARAMETRIZED_METHODS.all? { |m| obj.respond_to?(m) }
end
recursive?(obj) click to toggle source

If the object is “recursive”

Checks if object satisfies “recursive” concept. See RECURSIVE_METHODS for the list of methods

# File lib/aws/templates/utils.rb, line 30
def self.recursive?(obj)
  RECURSIVE_METHODS.all? { |m| obj.respond_to?(m) }
end
scalar?(obj) click to toggle source

If the object is “scalar”

Checks if object satisfies “scalar” concept.

# File lib/aws/templates/utils.rb, line 38
def self.scalar?(obj)
  !obj.respond_to?(:[])
end
select_recursively(container, &blk) click to toggle source

Select object recursively

Selects objects recursively from the specified container with specified block predicate.

  • container - container to recursively select items from

  • blk - the predicate

# File lib/aws/templates/utils.rb, line 227
def self.select_recursively(container, &blk)
  container.keys.each_with_object([]) do |k, collection|
    value = container[k]
    if blk.call(value)
      collection << value
    elsif Utils.recursive?(value)
      collection.concat(select_recursively(value, &blk))
    end
  end
end
set_recursively(container, value, path) click to toggle source

Sets a value in hierarchy

Sets a path in a nested recursive hash structure to the specified value

  • container - container with the specified path

  • value - the value to set the path to

  • path - path containing the target value

# File lib/aws/templates/utils.rb, line 205
def self.set_recursively(container, value, path)
  last_key = path.pop

  last_branch = path.inject(container) do |obj, current_key|
    raise Exception::OptionScalarOnTheWay.new(obj, path) unless Utils.recursive?(obj)
    if obj.include?(current_key)
      obj[current_key]
    else
      obj[current_key] = {}
    end
  end

  last_branch[last_key] = value
end

Public Instance Methods

context() click to toggle source

Module's context filter

Class-level accessor of a filter to be a part of context. The method returns aggregate filter include module's own filters concatenated with all ancestor's filters.

# File lib/aws/templates/utils/contextualized.rb, line 81
def context
  @context ||= ancestors_with(Contextualized).inject(Filter::Identity.new) do |acc, mod|
    acc & mod.module_context
  end
end
contextualize(fltr) click to toggle source

Add context filter

The class method is used to build hierarchical context filtering pipeline using language-provided features such as class inheritance and introspection. You can specify either a lamda, a hash or a filter functor.

If no parameters are passed at all, ArgumentError will be thrown.

# File lib/aws/templates/utils/contextualized.rb, line 96
def contextualize(fltr)
  raise ArgumentError.new('Proc should be specified') unless fltr
  @module_context = module_context & fltr
end
default(param) click to toggle source

Put an default/calculation for the input hash

The class method is the main knob which is used to build hierarchical hash mutation pipeline using language-provided features such as class inheritance and introspection. You can specify either hash (or an object which has :to_hash method) or a lambda/Proc as a parameter.

If you specify a hash then it will be merged with the current value of default where the hash passed will take preference during the merge.

If you specify a lambda it will be added to calculations stack

If the parameter you passed is neither a hash nor callable or no parameters are passed at all, ArgumentError will be thrown.

# File lib/aws/templates/utils/default.rb, line 380
def default(param)
  raise_defaults_is_nil unless param
  raise_default_type_mismatch(param) if param.override?

  @module_defaults_definition = module_defaults_definition.to_definition.merge(param)
end
defaults_definition() click to toggle source

Defaults for the input hash

Class-level accessor of a definition of defaults which will be merged into input parameters hash. The definition can't be changed directly or set to another value. Only incremental changes are allowed with default method which is a part of the framework DSL. The method returns accumulated defaults of all ancestors of the module in one single definition object

# File lib/aws/templates/utils/default.rb, line 355
def defaults_definition
  return @defaults if @defaults
  @defaults = ancestors_with(Default)
              .inject(Definition.empty) do |acc, elem|
                acc.merge(elem.module_defaults_definition)
              end
end
get_parameter(parameter_alias) click to toggle source

Get parameter object by name

Look-up by the parameter object name recursively through class inheritance hierarchy.

  • parameter_alias - parameter name

# File lib/aws/templates/utils/parametrized.rb, line 255
def get_parameter(parameter_alias)
  ancestor = ancestors_with(Parametrized)
             .find { |mod| mod.parameters.key?(parameter_alias) }

  ancestor.parameters[parameter_alias] if ancestor
end
module_defaults_definition() click to toggle source

Module's specific defaults

The defaults defined in this module and not its' ancestors.

# File lib/aws/templates/utils/default.rb, line 343
def module_defaults_definition
  @module_defaults_definition || Definition.empty
end
parameter(name, spec = {}) click to toggle source

Class-level parameter declaration method

Being a part of parameter declaration DSL, it constructs parameter object, stores it in parameters class level registry and creates a reader method for it. It will throw exception if the parameter name is already occupied by a method or a parameter with the name already exists in the class or any ancestor

  • parameter_alias - parameter name

  • specification - parameter specification hash

# File lib/aws/templates/utils/parametrized.rb, line 272
def parameter(name, spec = {})
  raise_already_exists(name) if method_defined?(name)

  parameter_object = Parameter.new(name, self, spec)
  parameters[name] = parameter_object
  define_method(name) { guarded_get(self, parameter_object) }
end
parameters() click to toggle source

Parameters defined in the class

Returns map from parameter name to parameter object of the parameters defined only in the class.

# File lib/aws/templates/utils/parametrized.rb, line 245
def parameters
  @parameters ||= {}
end
raise_already_exists(name) click to toggle source
# File lib/aws/templates/utils/parametrized.rb, line 280
def raise_already_exists(name)
  parameter_object = get_parameter(name)

  if parameter_object
    raise(
      Templates::Exception::ParameterAlreadyExist.new(parameter_object)
    )
  end

  raise Aws::Templates::Exception::ParameterMethodNameConflict.new(instance_method(name))
end
raise_default_type_mismatch(defaults_hash) click to toggle source
# File lib/aws/templates/utils/default.rb, line 391
def raise_default_type_mismatch(defaults_hash)
  raise ArgumentError.new("#{defaults_hash.inspect} is not recursive or proc")
end
raise_defaults_is_nil() click to toggle source
# File lib/aws/templates/utils/default.rb, line 387
def raise_defaults_is_nil
  raise ArgumentError.new('Map should be specified')
end