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 anOptions
object. So, the algorithm marks branch with the object to stop iteration during look-up.- PARAMETRIZED_METHODS
- PATH_REGEXP
- RECURSIVE_METHODS
Public Class Methods
# 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
# 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
# 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
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
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
# 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
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
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
# 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
# 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
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
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
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
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 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
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
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
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
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 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 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'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
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 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
# 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
# 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
# File lib/aws/templates/utils/default.rb, line 387 def raise_defaults_is_nil raise ArgumentError.new('Map should be specified') end