Page MenuHomePhorge

No OneTemporary

diff --git a/lib/puppet/pops/binder/binder.rb b/lib/puppet/pops/binder/binder.rb
index c0a6bd6d3..866e02cbd 100644
--- a/lib/puppet/pops/binder/binder.rb
+++ b/lib/puppet/pops/binder/binder.rb
@@ -1,404 +1,407 @@
# The Binder is responsible for processing layered bindings that can be used to setup an Injector.
#
# An instance should be created, and calls should then be made to #define_categories to define the available categories, and
# their precedence. This should be followed by a call to #define_layers which will match the layered bindings against the
# effective categories (filtering out everything that does not apply, handle overrides, abstract entries etc.).
# The constructed hash with key => InjectorEntry mappings is obtained as #injector_entries, and is used to initialize an Injector.
#
# @api public
#
class Puppet::Pops::Binder::Binder
# This limits the number of available categorizations, including "common".
# @api private
PRECEDENCE_MAX = 1000
# @api private
attr_reader :category_precedences
# @api private
attr_reader :category_values
# @api private
attr_reader :injector_entries
# @api private
attr_reader :key_factory
# Whether the binder is fully configured or not
# @api public
#
attr_reader :configured
# @api public
def initialize
@category_precedences = {}
@category_values = {}
@key_factory = Puppet::Pops::Binder::KeyFactory.new()
# Resulting hash of all key -> binding
@injector_entries = {}
# Not configured until the fat lady sings
@configured = false
@next_anonymous_key = 0
end
# Answers the question 'is this binder configured?' to the point it can be used to instantiate an Injector
# @api public
def configured?()
configured()
end
# Defines the effective categories in precedence order (highest precedence first).
# The 'common' (lowest precedence) category should not be included in the list.
# A sanity check is made that there are no more than 1000 categorizations (which is pretty wild).
#
# The term 'effective categories' refers to the evaluated list of tuples (categorization, category-value) represented with
# an instance of Puppet::Pops::Binder::Bindings::EffectiveCategories.
#
# @param effective_categories [Puppet::Pops::Binder::Bindings::EffectiveCategories] effective categories (i.e. with evaluated values)
# @raises ArgumentError if this binder is already configured
# @raises ArgumentError if the argument is not an EffectiveCategories
# @raises ArgumentError if there is an attempt to redefine a category (non unique, or 'common').
# @return [Puppet::Pops::Binder::Binder] self
# @api public
#
def define_categories(effective_categories)
raise ArgumentError, "This categories are already defined. Cannot redefine." unless @category_precedences.empty?
# Note: a model instance is used since a Hash does not have a defined order in all Rubies.
unless effective_categories.is_a?(Puppet::Pops::Binder::Bindings::EffectiveCategories)
raise ArgumentError, "Expected Puppet::Pops::Binder::Bindings::EffectiveCategories, but got a: #{effective_categories.class}"
end
categories = effective_categories.categories
raise ArgumentError, "Category limit (#{PRECEDENCE_MAX}) exceeded" unless categories.size <= PRECEDENCE_MAX
# Automatically add the 'common' category with lowest precedence
@category_precedences['common'] = 0
# if categories contains "common", it should be last - simply drop it if present
if last = categories[-1]
if last.categorization == 'common'
categories.delete_at(-1)
end
end
# Process the given categories (highest precedence is first in the list)
categories.each_with_index do |c, index|
cname = c.categorization
raise ArgumentError, "Attempt to redefine categorization: #{cname}" if @category_precedences[cname]
@category_precedences[cname] = PRECEDENCE_MAX - index
@category_values[cname] = c.value
end
self
end
# Binds layers from highest to lowest as defined by the given LayeredBindings.
# @note
# Categories must be set with #define_categories before calling this method. The model should have been
# validated to get better error messages if the model is invalid. This implementation expects the model
# to be valid, and any errors raised will be more technical runtime errors.
#
# @param layered_bindings [Puppet::Pops::Binder::Bindings::LayeredBindings] the named and ordered layers
# @raises ArgumentError if categories have not been defined
# @raises ArgumentError if this binder is already configured
# @raises ArgumentError if bindings with unresolved 'override' surfaces as an effective binding
# @raises ArgumentError if the given argument has the wrong type, or if model is invalid in some way
# @return [Puppet::Pops::Binder::Binder] self
# @api public
#
def define_layers(layered_bindings)
raise ArgumentError, "This binder is already configured. Cannot redefine its content." if configured?()
raise ArgumentError, "Categories must be defined first" if @category_precedences.empty?
LayerProcessor.new(self, key_factory).bind(layered_bindings)
injector_entries.each do |k,v|
unless key_factory.is_contributions_key?(k) || v.is_resolved?()
raise ArgumentError, "Binding with unresolved 'override' detected: #{k}"
end
end
# and the fat lady has sung
@configured = true
self
end
# @api private
def next_anonymous_key
tmp = @next_anonymous_key
@next_anonymous_key += 1
tmp
end
# Processes the information in a layer, aggregating it to the injector_entries hash in its parent binder.
# A LayerProcessor holds the intermediate state required while processing one layer.
#
# @api private
#
class LayerProcessor
attr :effective_prec
attr :prec_stack
attr :bindings
attr :binder
attr :key_factory
attr :contributions
def initialize(binder, key_factory)
@binder = binder
@key_factory = key_factory
@prec_stack = []
@effective_prec = nil
@bindings = []
@contributions = []
@@bind_visitor ||= Puppet::Pops::Visitor.new(nil,"bind",0,0)
end
# Add the binding to the list of potentially effective bindings from this layer
# @api private
#
def add(b)
bindings << Puppet::Pops::Binder::InjectorEntry.new(effective_prec, b)
end
# Add a multibind contribution
# @api private
#
def add_contribution(b)
contributions << Puppet::Pops::Binder::InjectorEntry.new(effective_prec, b)
end
# Bind given abstract binding
# @api private
#
def bind(binding)
@@bind_visitor.visit_this(self, binding)
end
# @returns [Puppet::Pops::Binder::InjectorEntry] the entry with the highest (category) precedence
# @api private
def highest(b1, b2)
case b1.precedence <=> b2.precedence
when 1
b1
when -1
b2
when 0
# TODO: This is too crude for conflict errors
raise_conflicting_binding(b1, b2)
end
end
# Raises a conflicting bindings error given two InjectorEntry's with same precedence in the same layer
# (if they are in different layers, something is seriously wrong)
def raise_conflicting_binding(b1, b2)
formatter = lambda {|layer, name| }
b1_layer_name, b1_bindings_name = get_named_binding_layer_and_name(b1.binding)
b2_layer_name, b2_bindings_name = get_named_binding_layer_and_name(b2.binding)
# The resolution is per layer, and if they differ something is serious wrong as a higher layer
# overrides a lower; so no such conflict should be possible:
unless b1_layer_name == b2_layer_name
raise ArgumentError, [
'Internal Error: Conflicting binding for',
"'#{b1.binding.name}'",
'being resolved across layers',
"'#{b1_layer_name}' and",
"'#{b2_layer_name}'"
].join(' ')
end
# Conflicting bindings made from the same source
if b1_bindings_name == b2_bindings_name
raise ArgumentError, [
'Conflicting binding for name:',
"'#{b1.binding.name}'",
'in layer:',
"'#{b1_layer_name}', ",
'both from:',
"'#{b1_bindings_name}'"
].join(' ')
end
# Conflicting bindings from different sources
raise ArgumentError, [
'Conflicting binding for name:',
"'#{b1.binding.name}'",
'in layer:',
"'#{b1_layer_name}',",
'from:',
"'#{b1_bindings_name}', and",
"'#{b2_bindings_name}'"
].join(' ')
end
def format_contribution_source(b)
layer_name, bindings_name = get_named_binding_layer_and_name(b)
"(layer: #{layer_name}, bindings: #{bindings_name})"
end
def get_named_binding_layer_and_name(b)
return ['<unknown>', '<unknown>'] if b.nil?
return [get_named_layer(b), b.name] if b.is_a?(Puppet::Pops::Binder::Bindings::NamedBindings)
get_named_binding_layer_and_name(b.eContainer)
end
def get_named_layer(b)
return '<unknown>' if b.nil?
return b.name if b.is_a?(Puppet::Pops::Binder::Bindings::NamedLayer)
get_named_layer(b.eContainer)
end
# Produces the key for the given Binding.
- # @param binding [Puppet::Pops::Binder::Bindings::Binding] they binding to get a key for
+ # @param binding [Puppet::Pops::Binder::Bindings::Binding] the binding to get a key for
# @returns [Object] an opaque key
# @api private
#
def key(binding)
- k = unless binding.is_a?(Puppet::Pops::Binder::Bindings::MultibindContribution)
- key_factory.binding_key(binding)
- else
- # contributions get a unique (sequencial) key
+ k = if is_contribution?(binding)
+ # contributions get a unique (sequential) key
binder.next_anonymous_key()
+ else
+ key_factory.binding_key(binding)
end
end
+ # @api private
+ def is_contribution?(binding)
+ ! binding.multibind_id.nil?
+ end
+
# @api private
def push_precedences(precedences)
prec_stack.push(precedences)
@effective_prec = nil # clear cache
end
# @api private
def pop_precedences()
prec_stack.pop()
@effective_prec = nil # clear cache
end
# Returns the effective precedence as an array with highest precedence first.
# Internally the precedence is an array with the highest precedence first.
#
# @api private
#
def effective_prec()
unless @effective_prec
@effective_prec = prec_stack.flatten.uniq.sort.reverse
if @effective_prec.size == 0
@effective_prec = [ 0 ] # i.e. "common"
end
end
@effective_prec
end
# @api private
def bind_Binding(o)
- add(o)
+ if is_contribution?(o)
+ add_contribution(o)
+ else
+ add(o)
+ end
end
# @api private
def bind_Bindings(o)
o.bindings.each {|b| bind(b) }
end
# @api private
def bind_NamedBindings(o)
# Name is ignored here, it should be introspected when needed (in case of errors)
o.bindings.each {|b| bind(b) }
end
# Process CategorizedBindings by calculating precedence, and then if satisfying the predicates, process the contained
# bindings.
# @api private
#
def bind_CategorizedBindings(o)
precedences = o.predicates.collect do |p|
prec = binder.category_precedences[p.categorization]
# Skip bindings if the categorization is not present, or
# if the category value is not the effective value for the categorization
# Ignore the value for the common category (it is not possible to state common 'false' etc.)
#
return unless prec
return unless binder.category_values[p.categorization] == p.value.downcase || p.categorization == 'common'
prec
end
push_precedences(precedences)
o.bindings.each {|b| bind(b) }
pop_precedences()
end
# Process layered bindings from highest to lowest layer
# @api private
#
def bind_LayeredBindings(o)
o.layers.each do |layer|
processor = LayerProcessor.new(binder, key_factory)
# All except abstract (==error) are transfered to injector_entries
processor.bind(layer).each do |k, v|
entry = binder.injector_entries[k]
unless key_factory.is_contributions_key?(k)
raise ArgumentError, "The abstract binding TODO: was not overridden" unless !v.is_abstract?()
raise ArgumentError, "Internal Error - redefinition of key: #{k}, (should never happen)" if entry
binder.injector_entries[k] = v
else
entry ? entry << v : binder.injector_entries[k] = v
end
end
end
end
- # @api private
- #
- def bind_MultibindContribution(o)
- add_contribution(o)
- end
-
# Processes one named ("top level") layer consisting of a list of NamedBindings
# @api private
#
def bind_NamedLayer(o)
o.bindings.each {|b| bind(b) }
this_layer = {}
# process regular bindings
bindings.each do |b|
bkey = key(b.binding)
# ignore if a higher layer defined it, but ensure override gets resolved
if x = binder.injector_entries[bkey]
x.mark_override_resolved()
next
end
# if already found in this layer, one wins (and resolves override), or it is an error
existing = this_layer[bkey]
winner = existing ? highest(existing, b) : b
this_layer[bkey] = winner
if existing
winner.mark_override_resolved()
end
end
# Process contributions
# - organize map multibind_id to bindings with this id
# - for each id, create an array with the unique anonymous keys to the contributed bindings
# - bind the index to a special multibind contributions key (these are aggregated)
#
c_hash = Hash.new {|hash, key| hash[ key ] = [] }
contributions.each {|b| c_hash[ b.binding.multibind_id ] << b }
# - for each id
c_hash.each do |k, v|
index = v.collect do |b|
bkey = key(b.binding)
this_layer[bkey] = b
bkey
end
contributions_key = key_factory.multibind_contributions(k)
unless this_layer[contributions_key]
this_layer[contributions_key] = []
end
this_layer[contributions_key] += index
end
this_layer
end
end
end
diff --git a/lib/puppet/pops/binder/bindings_factory.rb b/lib/puppet/pops/binder/bindings_factory.rb
index 65edcd804..0c3f52bba 100644
--- a/lib/puppet/pops/binder/bindings_factory.rb
+++ b/lib/puppet/pops/binder/bindings_factory.rb
@@ -1,608 +1,602 @@
# A helper class that makes it easier to construct a Bindings model
#
# @example Usage of the factory
# result = Puppet::Pops::Binder::BindingsFactory.named_bindings("mymodule::mybindings")
# result.bind().name("foo").to(42)
# result.when_in_category("node", "kermit.example.com").bind().name("foo").to(43)
# result.bind().string().name("site url").to("http://www.example.com")
# result.model()
#
# @api public
#
module Puppet::Pops::Binder::BindingsFactory
class AbstractBuilder
+ # The built model object.
+ attr_reader :model
+
+ # @api public
+ def initialize(binding)
+ @model = binding
+ end
+
# Provides convenient access to the Bindings Factory class methods. The intent is to provide access to the
# methods that return producers for the purpose of composing more elaborate things that the convenient methods
# directly supports.
#
def method_missing(meth, *args, &block)
- Puppet::Pops::Binder::BindingsFactory.send(meth, *args, &block)
+ factory = Puppet::Pops::Binder::BindingsFactory
+ if factory.respond_to?(meth)
+ factory.send(meth, *args, &block)
+ else
+ super
+ end
end
end
# @api public
class BindingsContainerBuilder < AbstractBuilder
- # The built model object.
- attr_reader :model
-
- # @api public
- def initialize(binding)
- @model = binding
- end
-
-
# Adds an empty binding to the container, and returns a builder for it for further detailing.
# @api public
#
def bind(&block)
binding = Puppet::Pops::Binder::Bindings::Binding.new()
model.addBindings(binding)
builder = BindingsBuilder.new(binding)
builder.instance_eval(&block) if block_given?
builder
end
# Binds an (almost) empty multibind where later, the looked up result contains all contributions to this key
# @param id [String] the multibind's id used when adding contributions
# @api public
#
def multibind(id, &block)
binding = Puppet::Pops::Binder::Bindings::Multibinding.new()
binding.id = id
model.addBindings(binding)
builder = MultibindingsBuilder.new(binding)
builder.instance_eval(&block) if block_given?
builder
end
- # Binds a type/name key in a multibind given by id.
- # @param type [Puppet::Pops::Types::PObjectType] the type (must be compatible with the multibind type argument)
- # @param name [String] the name of the binding (appears as key in a Hash multibind, ignored in an Array multibind
- # @param id [String] the multibind id of the multibind where this binding should be made
- # @api public
- #
- def bind_in_multibind(id, &block)
- binding = Puppet::Pops::Binder::Bindings::MultibindContribution.new()
- binding.multibind_id = id
- model.addBindings(binding)
- builder = BindingsBuilder.new(binding)
- builder.instance_eval(&block) if block_given?
- builder
- end
-
# Adds a categorized bindings to this container. Returns a BindingsContainerBuilder to allow adding
# bindings in that container.
# @param categorixation [String] the name of the categorization e.g. 'node'
# @param category_vale [String] the calue in that category e.g. 'kermit.example.com'
# @api public
#
def when_in_category(categorization, category_value, &block)
when_in_categories({categorization => category_value}, &block)
end
# Adds a categorized bindings to this container. Returns a BindingsContainerBuilder to allow adding
# bindings in that container. The result is that a processed request must be in all the listed categorizations
# with the given values.
# @param categories_hash Hash[String, String] a hash with categorization and categorization value entries
# @api public
#
def when_in_categories(categories_hash, &block)
binding = Puppet::Pops::Binder::Bindings::CategorizedBindings.new()
categories_hash.each do |k,v|
pred = Puppet::Pops::Binder::Bindings::Category.new()
pred.categorization = k
pred.value = v
binding.addPredicates(pred)
end
model.addBindings(binding)
builder = BindingsContainerBuilder.new(binding)
builder.instance_eval(&block) if block_given?
builder
end
end
# Builds a Binding via convenience methods.
#
# @api public
#
class BindingsBuilder < AbstractBuilder
- attr_reader :model
# @api public
def initialize(binding)
- @model = binding
+ super binding
data()
end
# Sets the name of the binding.
# @param name [String] the name to bind.
# @api public
def name(name)
- @model.name = name
+ model.name = name
+ self
+ end
+
+ # Makes the binding a multibind contribution to the given multibind id
+ # @api public
+ def in_multibind(id)
+ model.multibind_id = id
self
end
# (#name)
# @api public
def named(name)
- @model.name = name
+ model.name = name
self
end
# @api public
def type(type)
- @model.type = type
+ model.type = type
self
end
# @api public
def integer()
type(type_factory.integer())
end
# @api public
def float()
type(type_factory.float())
end
# @api public
def boolean()
type(type_factory.boolean())
end
# @api public
def string()
type(type_factory.string())
end
# @api public
def pattern()
type(type_factory.pattern())
end
# @api public
def literal()
type(type_factory.literal())
end
# @api public
def data()
type(type_factory.data())
end
# @api public
def array_of_data()
type(type_factory.array_of_data())
end
# @api public
def array_of(t)
type(type_factory.array_of(t))
end
# @api public
def hash_of_data()
type(type_factory.hash_of_data())
end
# Sets type of binding to `Hash[Literal, t]`. To limit the key type, use {#type} and give it a fully specified
# hash using {#type_factory} and then `hash_of(value_type, key_type)`.
# @api public
def hash_of(t)
type(type_factory.hash_of(t))
end
# @api public
def instance_of(t)
type(type_factory.type_of(t))
end
# Provides convenient access to the type factory.
# This is intended to be used when methods taking a type as argument i.e. {#type}, #{array_of}, {#hash_of}, and {#instance_of}.
#
# @api public
def type_factory
Puppet::Pops::Types::TypeFactory
end
# to a singleton producer, if producer is a value, a literal producer is created for it
# @overload to(a_literal)
# a constant producer
# @overload to(a_class, *args)
# Instantiating producer
# @overload to(a_producer_descriptor)
# a given producer
# @api public
#
def to(producer, *args)
case producer
when Class
producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args)
when Puppet::Pops::Model::Expression
producer = Puppet::Pops::Binder::BindingsFactory.evaluating_producer(producer)
when Puppet::Pops::Binder::Bindings::ProducerDescriptor
else
# If given producer is not a producer, create a literal producer
producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer)
end
- @model.producer = producer
+ model.producer = producer
self
end
# To a producer of an instance of given class (a String class name, or a Class instance)
# @overload to_instance(class_name, *args)
# @param class_name [String] the name of the class to instantiate
# @param args [Object] optional arguments to the constructor
# @overload to_instance(a_class)
# @param a_class [Class] the class to instantiate
# @param args [Object] optional arguments to the constructor
#
def to_instance(type, *args)
class_name = case type
when Class
type.name
when String
type
else
raise ArgumentError, "to_instance accepts String (a class name), or a Class.*args got: #{type.class}."
end
- @model.producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(class_name, *args)
+ model.producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(class_name, *args)
end
# to a singleton producer
# @overload to_producer(a_producer)
# @param a_producer [Puppet::Pops::Binder::Producers::Producer] an instantiated producer, not serializeable !
#
# @overload to_producer(a_class, *args)
# @param a_class [Class] the class to create an instance of
# @param args [Object] the arguments to the given class' new
#
# @overload to_producer(a_producer_descriptor)
# @param a_producer_descriptor [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a descriptor
# producing Puppet::Pops::Binder::Producers::Producer
#
# @api public
#
def to_producer(producer, *args)
case producer
when Class
producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args)
when Puppet::Pops::Binder::Bindings::ProducerDescriptor
when Puppet::Pops::Binder::Producers::Producer
# a custom producer instance
producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer)
else
raise ArgumentError, "Given producer argument is neither a producer descriptor, a class, nor a producer"
end
metaproducer = Puppet::Pops::Binder::BindingsFactory.producer_producer(producer)
- @model.producer = metaproducer
+ model.producer = metaproducer
self
end
# to a series of producers
# @overload to_producer(a_producer)
# @param a_producer [Puppet::Pops::Binder::Producers::Producer] an instantiated producer, not serializeable !
#
# @overload to_producer(a_class, *args)
# @param a_class [Class] the class to create an instance of
# @param args [Object] the arguments to the given class' new
#
# @overload to_producer(a_producer_descriptor)
# @param a_producer_descriptor [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a descriptor
# producing Puppet::Pops::Binder::Producers::Producer
#
# @api public
#
def to_producer_series(producer, *args)
case producer
when Class
producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args)
when Puppet::Pops::Binder::Bindings::ProducerDescriptor
when Puppet::Pops::Binder::Producers::Producer
# a custom producer instance
producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer)
else
raise ArgumentError, "Given producer argument is neither a producer descriptor, a class, nor a producer"
end
non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new()
non_caching.producer = producer
metaproducer = Puppet::Pops::Binder::BindingsFactory.producer_producer(non_caching)
non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new()
non_caching.producer = metaproducer
- @model.producer = non_caching
+ model.producer = non_caching
self
end
# to a "non singleton" producer (each produce produces a new copy).
# @overload to_series_of(a_literal)
# a constant producer
# @overload toto_series_of(a_class, *args)
# Instantiating producer
# @overload toto_series_of(a_producer_descriptor)
# a given producer
#
# @api public
#
def to_series_of(producer)
case producer
when Class
producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args)
when Puppet::Pops::Binder::Bindings::ProducerDescriptor
else
# If given producer is not a producer, create a literal producer
producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer)
end
non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new()
non_caching.producer = producer
- @model.producer = non_caching
+ model.producer = non_caching
self
end
-
# to a lookup of another key
# @overload to_lookup_of(type, name)
# @overload to_lookup_of(name)
# @api public
#
def to_lookup_of(type, name=nil)
unless name
name = type
type = Puppet::Pops::Types::TypeFactory.data()
end
- @model.producer = Puppet::Pops::Binder::BindingsFactory.lookup_producer(type, name)
+ model.producer = Puppet::Pops::Binder::BindingsFactory.lookup_producer(type, name)
self
end
# to a lookup of another key
# @overload to_lookup_of(type, name)
# @overload to_lookup_of(name)
# @api public
#
def to_hash_lookup_of(type, name, key)
- @model.producer = Puppet::Pops::Binder::BindingsFactory.hash_lookup_producer(type, name, key)
+ model.producer = Puppet::Pops::Binder::BindingsFactory.hash_lookup_producer(type, name, key)
self
end
# to first found lookup of another key
# @param list_of_lookups [Array] array of arrays [type name], or just name (implies data)
# @example
# binder.bind().name('foo').to_first_found(['fee', 'fum', 'extended-bar'])
# binder.bind().name('foo').to_first_found([
# [TypeFactory.ruby(ThisClass), 'fee'],
# [TypeFactory.ruby(ThatClass), 'fum'],
# 'extended-bar'])
# @api public
#
def to_first_found(list_of_lookups)
producers = list_of_lookups.collect do |entry|
if entry.is_a?(Array)
case entry.size
when 2
Puppet::Pops::Binder::BindingsFactory.lookup_producer(entry[0], entry[1])
when 1
Puppet::Pops::Binder::BindingsFactory.lookup_producer(Puppet::Pops::Types::TypeFactory.data(), entry[0])
else
raise ArgumentError, "Not an array of [type, name], name, or [name]"
end
else
Puppet::Pops::Binder::BindingsFactory.lookup_producer(Puppet::Pops::Types::TypeFactory.data(), entry)
end
end
- @model.producer = Puppet::Pops::Binder::BindingsFactory.first_found_producer(producers)
+ model.producer = Puppet::Pops::Binder::BindingsFactory.first_found_producer(producers)
self
end
# @api public
def producer_options(options)
options.each do |k, v|
arg = Puppet::Pops::Binder::Bindings::NamedArgument.new()
arg.name = k.to_s
arg.value = v
- @model.addProducer_args(arg)
+ model.addProducer_args(arg)
end
self
end
end
# @api public
class MultibindingsBuilder < BindingsBuilder
# Constraints type to be one of {Puppet::Pops::Types::PArrayType PArrayType}, or {Puppet::Pops::Types:PHashType PHashType}.
# @raise [ArgumentError] if type constraint is not met.
# @api public
def type(type)
unless type.class == Puppet::Pops::Types::PArrayType || type.class == Puppet::Pops::Types::PHashType
raise ArgumentError, "Wrong type; only PArrayType, or PHashType allowed, got '#{type.to_s}'"
end
- @model.type = type
+ model.type = type
self
end
# Overrides the default implementation that will raise an exception as a multibind requires a hash type.
# Thus, if nothing else is requested, a multibind will be configured as Hash[Data].
#
def data()
hash_of_data()
end
end
# Produces a ContributedBindings
# @param name [String] the name of the contributed bindings (for human use in messages/logs only)
# @param named_bindings [Puppet::Pops::Binder::Bindings::NamedBindings, Array<Puppet::Pops::Binder::Bindings::NamedBindings>] the
# named bindings to include
# @parm effective_categories [Puppet::Pops::Binder::Bindings::EffectiveCategories] the contributors opinion about categorization
# this is used to ensure consistent use of categories.
#
def self.contributed_bindings(name, named_bindings, effective_categories)
cb = Puppet::Pops::Binder::Bindings::ContributedBindings.new()
cb.name = name
named_bindings = [named_bindings] unless named_bindings.is_a?(Array)
named_bindings.each {|b| cb.addBindings(b) }
cb.effective_categories = effective_categories
cb
end
# Creates a named binding container, the top bindings model object.
# The created container is wrapped in a BindingsContainerBuilder for further detailing.
# Unwrap the built result when done.
# @api public
#
def self.named_bindings(name, &block)
binding = Puppet::Pops::Binder::Bindings::NamedBindings.new()
binding.name = name
builder = BindingsContainerBuilder.new(binding)
builder.instance_eval(&block) if block_given?
builder
end
# This variant of named_binding evaluates the given block as a method on an anonymous class,
# thus, if the block defines methods or do something with the class itself, this does not pollute
# the base class (BindingsContainerBuilder).
#
def self.safe_named_bindings(name, scope, &block)
binding = Puppet::Pops::Binder::Bindings::NamedBindings.new()
binding.name = name
anon = Class.new(BindingsContainerBuilder) do
def initialize(b)
super b
end
end
anon.send(:define_method, :_produce, block)
builder = anon.new(binding)
case block.arity
when 0
builder._produce()
when 1
builder._produce(scope)
end
builder
end
# Creates a literal producer
# @api public
#
def self.literal_producer(value)
producer = Puppet::Pops::Binder::Bindings::ConstantProducerDescriptor.new()
producer.value = value
producer
end
# Creates a literal producer
# @api public
#
def self.non_caching_producer(producer)
p = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new()
p.producer = producer
p
end
# Creates a producer producer
# @api public
#
def self.producer_producer(producer)
p = Puppet::Pops::Binder::Bindings::ProducerProducerDescriptor.new()
p.producer = producer
p
end
# @api public
#
def self.instance_producer(class_name, *args)
p = Puppet::Pops::Binder::Bindings::InstanceProducerDescriptor.new()
p.class_name = class_name
args.each {|a| p.addArguments(a) }
p
end
# @api public
def self.lookup_producer(type, name)
p = Puppet::Pops::Binder::Bindings::LookupProducerDescriptor.new()
p.type = type
p.name = name
p
end
# @api public
def self.hash_lookup_producer(type, name, key)
p = Puppet::Pops::Binder::Bindings::HashLookupProducerDescriptor.new()
p.type = type
p.name = name
p.key = key
p
end
# @api public
def self.first_found_producer(producers)
p = Puppet::Pops::Binder::Bindings::FirstFoundProducerDescriptor.new()
producers.each {|p2| p.addProducers(p2) }
p
end
# @api public
def self.evaluating_producer(expression)
p = Puppet::Pops::Binder::Bindings::EvaluatingProducerDescriptor.new()
p.expression = expression
p
end
# Creates an EffectiveCategories from a list of tuples `[categorizxation category ...]`, or ´[[categorization category] ...]`
# @api public
#
def self.categories(tuple_array)
result = Puppet::Pops::Binder::Bindings::EffectiveCategories.new()
tuple_array.flatten.each_slice(2) do |c|
cat = Puppet::Pops::Binder::Bindings::Category.new()
cat.categorization = c[0]
cat.value = c[1]
result.addCategories(cat)
end
result
end
# @api public
def self.named_layer(name, *bindings)
result = Puppet::Pops::Binder::Bindings::NamedLayer.new()
result.name = name
bindings.each { |b| result.addBindings(b) }
result
end
# @api public
def self.layered_bindings(*named_layers)
result = Puppet::Pops::Binder::Bindings::LayeredBindings.new()
named_layers.each {|b| result.addLayers(b) }
result
end
def self.parser
@parser ||= Puppet::Pops::Parser::EvaluatingParser.new()
end
# Parses and produces a puppet expression from the given string.
# @param string [String] puppet source e.g. "1 + 2"
# @param source_file [String] the source location, typically `__File__`
# @return [Puppet::Pops::Model::Expression] an expression (that can be bound)
# @api public
#
def self.puppet_expression(string, source_file)
parser.parse_string(string, source_file).current
end
# Parses and produces a puppet string expression from the given string.
# The string will automatically be quoted and special characters escaped.
# As an example if given the (ruby) string "Hi\nMary" it is transformed to
# the puppet string (illustrated with a ruby string) "\"Hi\\nMary\”" before being
# parsed.
#
# @param string [String] puppet source e.g. "On node ${fqdn}"
# @param source_file [String] the source location, typically `__File__`
# @return [Puppet::Pops::Model::Expression] an expression (that can be bound)
# @api public
#
def self.puppet_string(string, source_file)
parser.parse_string(parser.quote(string), source_file).current
end
end
diff --git a/lib/puppet/pops/binder/bindings_label_provider.rb b/lib/puppet/pops/binder/bindings_label_provider.rb
index ae38643eb..f7555468a 100644
--- a/lib/puppet/pops/binder/bindings_label_provider.rb
+++ b/lib/puppet/pops/binder/bindings_label_provider.rb
@@ -1,42 +1,46 @@
# A provider of labels for bindings model object, producing a human name for the model object.
# @api private
#
class Puppet::Pops::Binder::BindingsLabelProvider < Puppet::Pops::LabelProvider
def initialize
@@label_visitor ||= Puppet::Pops::Visitor.new(self,"label",0,0)
end
# Produces a label for the given object without article.
# @return [String] a human readable label
#
def label o
@@label_visitor.visit(o)
end
def label_PObjectType o ; "#{Puppet::Pops::Types::TypeFactory.label(o)}" end
def label_ProducerDescriptor o ; "Producer" end
def label_NonCachingProducerDescriptor o ; "Non Caching Producer" end
def label_ConstantProducerDescriptor o ; "Producer['#{o.value}']" end
def label_EvaluatingProducerDescriptor o ; "Evaluating Producer" end
def label_InstanceProducerDescriptor o ; "Producer[#{o.class_name}]" end
def label_LookupProducerDescriptor o ; "Lookup Producer[#{o.name}]" end
def label_HashLookupProducerDescriptor o ; "Hash Lookup Producer[#{o.name}][#{o.key}]" end
def label_FirstFoundProducerDescriptor o ; "First Found Producer" end
def label_ProducerProducerDescriptor o ; "Producer[Producer]" end
def label_MultibindProducerDescriptor o ; "Multibind Producer" end
def label_ArrayMultibindProducerDescriptor o ; "Array Multibind Producer" end
def label_HashMultibindProducerDescriptor o ; "Hash Multibind Producer" end
- def label_Binding o ; "Binding" end
- def label_Multibinding o ; "Multibinding" end
- def label_MultibindContribution o ; "Multibind Contribution" end
def label_Bindings o ; "Bindings" end
def label_NamedBindings o ; "Named Bindings" end
def label_Category o ; "Category '#{o.categorization}/#{o.value}'" end
def label_CategorizedBindings o ; "Categorized Bindings" end
def label_LayeredBindings o ; "Layered Bindings" end
def label_NamedLayer o ; "Layer '#{o.name}'" end
def label_EffectiveCategories o ; "Effective Categories" end
def label_ContributedBindings o ; "Contributed Bindings" end
def label_NamedArgument o ; "Named Argument" end
+ def label_Binding(o)
+ 'Binding' + (o.multibind_id.nil? ? '' : ' In Multibind')
+ end
+ def label_Multibinding(o)
+ 'Multibinding' + (o.multibind_id.nil? ? '' : ' In Multibind')
+ end
+
end
diff --git a/lib/puppet/pops/binder/bindings_model.rb b/lib/puppet/pops/binder/bindings_model.rb
index fb7b3808c..92e13df7e 100644
--- a/lib/puppet/pops/binder/bindings_model.rb
+++ b/lib/puppet/pops/binder/bindings_model.rb
@@ -1,220 +1,215 @@
require 'rgen/metamodel_builder'
# The Bindings model is a model of Key to Producer mappings (bindings).
# The central concept is that a Bindings is a nested structure of bindings.
# A top level Bindings should be a NamedBindings (the name is used primarily
# in error messages). A Key is a Type/Name combination.
#
# TODO: In this version, references to "any object" uses the class Object.
# this is only temporarily. The intent is to use specific Puppet Objects
# that are typed using the Puppet Type System. (This to enable serialization)
#
# @see Puppet::Pops::Binder::BindingsFactory The BindingsFactory for more details on how to create model instances.
# @api public
module Puppet::Pops::Binder::Bindings
# @abstract
# @api public
#
class AbstractBinding < Puppet::Pops::Model::PopsObject
abstract
end
# An abstract producer
# @abstract
# @api public
#
class ProducerDescriptor < Puppet::Pops::Model::PopsObject
abstract
contains_one_uni 'transformer', Puppet::Pops::Model::LambdaExpression
end
# All producers are singleton producers unless wrapped in a non caching producer
# where each lookup produces a new instance. It is an error to have a nesting level > 1
# and to nest a NonCachingProducerDescriptor.
#
# @api public
#
class NonCachingProducerDescriptor < ProducerDescriptor
contains_one_uni 'producer', ProducerDescriptor
end
# Produces a constant value (i.e. something of {Puppet::Pops::Types::PDataType PDataType})
# @api public
#
class ConstantProducerDescriptor < ProducerDescriptor
# TODO: This should be a typed Puppet Object
has_attr 'value', Object
end
# Produces a value by evaluating a Puppet DSL expression
# @api public
#
class EvaluatingProducerDescriptor < ProducerDescriptor
contains_one_uni 'expression', Puppet::Pops::Model::Expression
end
# An InstanceProducer creates an instance of the given class
# Arguments are passed to the class' `new` operator in the order they are given.
# @api public
#
class InstanceProducerDescriptor < ProducerDescriptor
# TODO: This should be a typed Puppet Object ??
has_many_attr 'arguments', Object, :upperBound => -1
has_attr 'class_name', String
end
# A ProducerProducerDescriptor, describes that the produced instance is itself a Producer
# that should be used to produce the value.
# @api public
#
class ProducerProducerDescriptor < ProducerDescriptor
contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 1
end
# Produces a value by looking up another key (type/name)
# @api public
#
class LookupProducerDescriptor < ProducerDescriptor
contains_one_uni 'type', Puppet::Pops::Types::PObjectType
has_attr 'name', String
end
# Produces a value by looking up another multibound key, and then looking up
# the detail using a detail_key.
# This is used to produce a specific service of a given type (such as a SyntaxChecker for the syntax "json").
# @api public
#
class HashLookupProducerDescriptor < LookupProducerDescriptor
has_attr 'key', String
end
# Produces a value by looking up each producer in turn. The first existing producer wins.
# @api public
#
class FirstFoundProducerDescriptor < ProducerDescriptor
contains_many_uni 'producers', LookupProducerDescriptor
end
# @api public
# @abstract
class MultibindProducerDescriptor < ProducerDescriptor
abstract
end
# Used in a Multibind of Array type unless it has a producer. May explicitly be used as well.
# @api public
#
class ArrayMultibindProducerDescriptor < MultibindProducerDescriptor
end
# Used in a Multibind of Hash type unless it has a producer. May explicitly be used as well.
# @api public
#
class HashMultibindProducerDescriptor < MultibindProducerDescriptor
end
# Plays the role of "Hash[String, Object] entry" but with keys in defined order.
#
# @api public
#
class NamedArgument < Puppet::Pops::Model::PopsObject
has_attr 'name', String, :lowerBound => 1
has_attr 'value', Object, :lowerBound => 1
end
# Binds a type/name combination to a producer. Optionally marking the bindidng as being abstract, or being an
# override of another binding. Optionally, the binding defines producer arguments passed to the producer when
# it is created.
#
# @api public
class Binding < AbstractBinding
contains_one_uni 'type', Puppet::Pops::Types::PObjectType
has_attr 'name', String
has_attr 'override', Boolean
has_attr 'abstract', Boolean
+ # If set is a contribution in a multibind
+ has_attr 'multibind_id', String, :lowerBound => 0
# Invariant: Only multibinds may have lowerBound 0, all regular Binding must have a producer.
contains_one_uni 'producer', ProducerDescriptor, :lowerBound => 0
contains_many_uni 'producer_args', NamedArgument, :lowerBound => 0
end
# A multibinding is a binding other bindings can contribute to.
#
# @api public
class Multibinding < Binding
has_attr 'id', String
end
- # A binding in a multibind
- # @api public
- #
- class MultibindContribution < Binding
- has_attr 'multibind_id', String, :lowerBound => 1
- end
-
# A container of Binding instances
# @api public
#
class Bindings < AbstractBinding
contains_many_uni 'bindings', AbstractBinding
end
# The top level container of bindings can have a name (for error messages, logging, tracing).
# May be nested.
# @api public
#
class NamedBindings < Bindings
has_attr 'name', String
end
# A category predicate (the request has to be in this category).
# @api public
#
class Category < Puppet::Pops::Model::PopsObject
has_attr 'categorization', String, :lowerBound => 1
has_attr 'value', String, :lowerBound => 1
end
# A container of Binding instances that are in effect when the
# predicates (min one) evaluates to true. Multiple predicates are handles as an 'and'.
# Note that 'or' semantics are handled by repeating the same rules.
# @api public
#
class CategorizedBindings < Bindings
contains_many_uni 'predicates', Category, :lowerBound => 1
end
# A named layer of bindings having the same priority.
# @api public
class NamedLayer < Puppet::Pops::Model::PopsObject
has_attr 'name', String, :lowerBound => 1
contains_many_uni 'bindings', NamedBindings
end
# A list of layers with bindings in descending priority order.
# @api public
#
class LayeredBindings < Puppet::Pops::Model::PopsObject
contains_many_uni 'layers', NamedLayer
end
# A list of categories consisting of categroization name and category value (i.e. the *state of the request*)
# @api public
#
class EffectiveCategories < Puppet::Pops::Model::PopsObject
# The order is from highest precedence to lowest
contains_many_uni 'categories', Category
end
# ContributedBindings is a named container of one or more NamedBindings.
# The intent is that a bindings producer returns a ContributedBindings which in addition to the bindings
# may optionally contain provider's opinion about the precedence of categories, and their category values.
# This enables merging of bindings, and validation of consistency.
#
# @api public
#
class ContributedBindings < NamedLayer
contains_one_uni 'effective_categories', EffectiveCategories
end
end
diff --git a/lib/puppet/pops/binder/bindings_model_dumper.rb b/lib/puppet/pops/binder/bindings_model_dumper.rb
index dbfa1f9a5..8416dc651 100644
--- a/lib/puppet/pops/binder/bindings_model_dumper.rb
+++ b/lib/puppet/pops/binder/bindings_model_dumper.rb
@@ -1,226 +1,205 @@
# Dumps a Pops::Binder::Bindings model in reverse polish notation; i.e. LISP style
# The intention is to use this for debugging output
# TODO: BAD NAME - A DUMP is a Ruby Serialization
# NOTE: use :break, :indent, :dedent in lists to do just that
#
class Puppet::Pops::Binder::BindingsModelDumper < Puppet::Pops::Model::TreeDumper
Bindings = Puppet::Pops::Binder::Bindings
attr_reader :type_calculator
+ attr_reader :expression_dumper
def initialize
super
@type_calculator = Puppet::Pops::Types::TypeCalculator.new()
+ @expression_dumper = Puppet::Pops::model::ModelTreeDumper.new()
end
def dump_BindingsFactory o
do_dump(o.model)
end
def dump_BindingsBuilder o
do_dump(o.model)
end
def dump_BindingsContainerBuilder o
do_dump(o.model)
end
def dump_NamedLayer o
result = ['named-layer', (o.name.nil? ? '<no-name>': o.name), :indent]
if o.bindings
o.bindings.each do |b|
result << :break
result << do_dump(b)
end
end
result << :dedent
result
end
def dump_Array o
o.collect {|e| do_dump(e) }
end
def dump_ASTArray o
["[]"] + o.children.collect {|x| do_dump(x)}
end
def dump_ASTHash o
["{}"] + o.value.sort_by{|k,v| k.to_s}.collect {|x| [do_dump(x[0]), do_dump(x[1])]}
end
def dump_Integer o
o.to_s
end
# Dump a Ruby String in single quotes unless it is a number.
def dump_String o
"'#{o}'"
end
def dump_NilClass o
"()"
end
-# def dump_Hostclass o
-# # ok, this is kind of crazy stuff in the AST, information in a context instead of in AST, and
-# # parameters are in a Ruby Array with each parameter being an Array...
-# #
-# context = o.context
-# args = context[:arguments]
-# parent = context[:parent]
-# result = ["class", o.name]
-# result << ["inherits", parent] if parent
-# result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0
-# if is_nop?(o.code)
-# result << []
-# else
-# result << do_dump(o.code)
-# end
-# result
-# end
-
-
-
def dump_Object o
['dev-error-no-polymorph-dump-for:', o.class.to_s, o.to_s]
end
def is_nop? o
o.nil? || o.is_a?(Model::Nop) || o.is_a?(AST::Nop)
end
def dump_ProducerDescriptor o
- # TODO: delegate to Pops Model Tree dumper and dump the transformer if it exists
- # o.transformer
- [o.class.name ]
+ result = [o.class.name]
+ result << expression_dumper.dump(o.transformer) if o.transformer
+ result
end
def dump_NonCachingProducerDescriptor o
dump_ProducerDescriptor(o) + do_dump(o.producer)
end
def dump_ConstantProducerDescriptor o
['constant', do_dump(o.value)]
end
def dump_EvaluatingProducerDescriptor o
- # TODO: puppet pops model transformer dump o.expression
- dump_ProducerDescriptor(o)
+ result = dump_ProducerDescriptor(o)
+ result << expression_dumper.dump(o.expression)
end
def dump_InstanceProducerDescriptor
# TODO: o.arguments, o. transformer
['instance', o.class_name]
end
def dump_ProducerProducerDescriptor o
# skip the transformer lambda...
- ['producer-producer', do_dump(o.producer)]
+ result = ['producer-producer', do_dump(o.producer)]
+ result << expression_dumper.dump(o.transformer) if o.transformer
+ result
end
def dump_LookupProducerDescriptor o
['lookup', do_dump(o.type), o.name]
end
def dump_PObjectType o
type_calculator.string(o)
end
def dump_HashLookupProducerDescriptor o
# TODO: transformer lambda
- ['hash-lookup', do_dump(o.type), o.name, "[#{do_dump(o.key)}]"]
+ result = ['hash-lookup', do_dump(o.type), o.name, "[#{do_dump(o.key)}]"]
+ result << expression_dumper.dump(o.transformer) if o.transformer
+ result
end
def dump_FirstFoundProducerDescriptor o
# TODO: transformer lambda
['first-found', do_dump(o.producers)]
end
def dump_ArrayMultibindProducerDescriptor o
['multibind-array']
end
def dump_HashMultibindProducerDescriptor o
['multibind-hash']
end
def dump_NamedArgument o
"#{o.name} => #{do_dump(o.value)}"
end
def dump_Binding o
result = ['bind']
result << 'override' if o.override
result << 'abstract' if o.abstract
result.concat([do_dump(o.type), o.name])
+ result << "(in #{o.multibind_id})" if o.multibind_id
result << ['to', do_dump(o.producer)] + do_dump(o.producer_args)
result
end
def dump_Multibinding o
result = ['multibind', o.id]
result << 'override' if o.override
result << 'abstract' if o.abstract
result.concat([do_dump(o.type), o.name])
- result << ['to', do_dump(o.producer)] + do_dump(o.producer_args)
- result
- end
-
- def dump_MultibindContribution o
- result = ['contribute-to', o.multibind_id]
- result << 'override' if o.override
- result << 'abstract' if o.abstract
- result.concat([do_dump(o.type), o.name])
+ result << "(in #{o.multibind_id})" if o.multibind_id
result << ['to', do_dump(o.producer)] + do_dump(o.producer_args)
result
end
def dump_Bindings o
do_dump(o.bindings)
end
def dump_NamedBindings o
result = ['named-bindings', o.name, :indent]
o.bindings.each do |b|
result << :break
result << do_dump(b)
end
result << :dedent
result
end
def dump_Category o
['category', o.categorization, do_dump(o.value)]
end
def dump_CategorizedBindings o
result = ['when', do_dump(o.predicates), :indent]
o.bindings.each do |b|
result << :break
result << do_dump(b)
end
result << :dedent
result
end
def dump_LayeredBindings o
result = ['layers', :indent]
o.layers.each do |layer|
result << :break
result << do_dump(layer)
end
result << :dedent
result
end
def dump_EffectiveCategories o
['categories', do_dump(o.categories)]
end
def dump_ContributedBindings o
['contributed', o.name, do_dump(o.effective_categories), do_dump(o.bindings)]
end
end
diff --git a/lib/puppetx.rb b/lib/puppetx.rb
index cc1df16e8..3945fe34f 100644
--- a/lib/puppetx.rb
+++ b/lib/puppetx.rb
@@ -1,45 +1,46 @@
# The Puppet Extensions Module
# Submodules of this module should be named after the publisher (e.g. 'user' part of a Puppet Module name).
# The submodule `Puppetx::Puppet` contains the puppet extension points.
#
module Puppetx
SYNTAX_CHECKERS = 'puppetx::puppet::syntaxcheckers'
SYNTAX_CHECKERS_TYPE = 'Puppetx::Puppet::SyntaxChecker'
module Puppet
# Extension-points are registered here:
# - If in a Ruby submodule it is best to create it here
# - The class does not have to be required; it will be auto required when the binder
# needs it.
# - If the extension is a multibind, it can be registered here; either with a required
# class or a class reference in string form.
# Register extension points
# -------------------------
system_bindings = ::Puppet::Pops::Binder::SystemBindings
extensions = system_bindings.extensions()
extensions.multibind(SYNTAX_CHECKERS).name(SYNTAX_CHECKERS).hash_of(SYNTAX_CHECKERS_TYPE)
end
# Module with implementations of various extensions
module Puppetlabs
# Default extensions delivered in Puppet Core are included here
module SyntaxCheckers
# Classes in this name-space are lazily loaded as they may be overridden and/or never used
# (Lazy loading is done by binding to the name of a class instead of a Class instance).
# Register extensions
# -------------------
system_bindings = ::Puppet::Pops::Binder::SystemBindings
bindings = system_bindings.default_bindings()
- bindings.
- bind_in_multibind(SYNTAX_CHECKERS ).name('json').
- instance_of(SYNTAX_CHECKERS_TYPE).
- to_instance('Puppetx::Puppetlabs::SyntaxCheckers::Json')
+ bindings.bind.
+ name('json').
+ instance_of(SYNTAX_CHECKERS_TYPE).
+ in_multibind(SYNTAX_CHECKERS).
+ to_instance('Puppetx::Puppetlabs::SyntaxCheckers::Json')
end
end
end
\ No newline at end of file
diff --git a/spec/unit/pops/binder/injector_spec.rb b/spec/unit/pops/binder/injector_spec.rb
index db54b0d37..e9cad905c 100644
--- a/spec/unit/pops/binder/injector_spec.rb
+++ b/spec/unit/pops/binder/injector_spec.rb
@@ -1,735 +1,751 @@
require 'spec_helper'
require 'puppet/pops'
module InjectorSpecModule
def injector(binder)
Puppet::Pops::Binder::Injector.new(binder)
end
def factory
Puppet::Pops::Binder::BindingsFactory
end
def test_layer_with_empty_bindings
factory.named_layer('test-layer', factory.named_bindings('test').model)
end
def test_layer_with_bindings(*bindings)
factory.named_layer('test-layer', *bindings)
end
def null_scope()
nil
end
def type_calculator
Puppet::Pops::Types::TypeCalculator
end
def type_factory
Puppet::Pops::Types::TypeFactory
end
# Returns a binder with the effective categories highest/test, node/kermit, environment/dev (and implicit 'common')
#
def binder_with_categories
b = Puppet::Pops::Binder::Binder.new()
b.define_categories(factory.categories(['highest', 'test', 'node', 'kermit', 'environment','dev']))
b
end
class TestDuck
end
class Daffy < TestDuck
end
class AngryDuck < TestDuck
# Supports assisted inject, returning a Donald duck as the default impl of Duck
def self.inject(injector, scope, binding, *args)
Donald.new()
end
end
class Donald < AngryDuck
end
class ArneAnka < AngryDuck
attr_reader :label
def initialize()
@label = 'A Swedish angry cartoon duck'
end
end
class ScroogeMcDuck < TestDuck
attr_reader :fortune
# Supports assisted inject, returning an ScroogeMcDuck with 1$ fortune or first arg in args
# Note that when injected (via instance producer, or implict assisted inject, the inject method
# always wins.
def self.inject(injector, scope, binding, *args)
self.new(args[0].nil? ? 1 : args[0])
end
def initialize(fortune)
@fortune = fortune
end
end
class NamedDuck < TestDuck
attr_reader :name
def initialize(name)
@name = name
end
end
# Test custom producer that on each produce returns a duck that is twice as rich as its predecessor
class ScroogeProducer < Puppet::Pops::Binder::Producers::Producer
attr_reader :next_capital
def initialize
@next_capital = 100
end
def produce(scope)
ScroogeMcDuck.new(@next_capital *= 2)
end
end
end
describe 'Injector' do
include InjectorSpecModule
let(:bindings) { factory.named_bindings('test') }
let(:scope) { null_scope()}
let(:duck_type) { type_factory.ruby(InjectorSpecModule::TestDuck) }
let(:binder) { Puppet::Pops::Binder::Binder.new()}
let(:cbinder) do
b = Puppet::Pops::Binder::Binder.new()
b.define_categories(factory.categories([]))
b
end
let(:lbinder) do
cbinder.define_layers(layered_bindings)
end
let(:layered_bindings) { factory.layered_bindings(test_layer_with_bindings(bindings.model)) }
let(:xinjector) { Puppet::Pops::Binder::Injector.new(lbinder) }
context 'When created' do
it 'should raise an error when given binder is not configured at all' do
expect { Puppet::Pops::Binder::Injector.new(binder()) }.to raise_error(/Given Binder is not configured/)
end
it 'should raise an error if binder has categories, but is not completely configured' do
expect { Puppet::Pops::Binder::Injector.new(cbinder) }.to raise_error(/Given Binder is not configured/)
end
it 'should not raise an error if binder is configured' do
lbinder.configured?().should == true # of something is very wrong
expect { injector(lbinder) }.to_not raise_error
end
it 'should create an empty injector given an empty binder' do
expect { cbinder.define_layers(layered_bindings) }.to_not raise_exception
end
it "should be possible to reference the TypeCalculator" do
injector(lbinder).type_calculator.is_a?(Puppet::Pops::Types::TypeCalculator).should == true
end
it "should be possible to reference the KeyFactory" do
injector(lbinder).key_factory.is_a?(Puppet::Pops::Binder::KeyFactory).should == true
end
end
context "When looking up objects" do
it 'lookup(scope, name) finds bound object of type Data with given name' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup(scope, 'a_string').should == '42'
end
context 'a block transforming the result can be given' do
it 'that transform a found value given scope and value' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup(scope, 'a_string') {|zcope, val| val + '42' }.should == '4242'
end
it 'that transform a found value given only value' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup(scope, 'a_string') {|val| val + '42' }.should == '4242'
end
it 'that produces a default value when entry is missing' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup(scope, 'a_non_existing_string') {|val| val ? (raise Error, "Should not happen") : '4242' }.should == '4242'
end
end
context "and class is not bound" do
it "assisted inject kicks in for classes with zero args constructor" do
duck_type = type_factory.ruby(InjectorSpecModule::Daffy)
injector = injector(lbinder)
injector.lookup(scope, duck_type).is_a?(InjectorSpecModule::Daffy).should == true
injector.lookup_producer(scope, duck_type).produce(scope).is_a?(InjectorSpecModule::Daffy).should == true
end
it "assisted inject produces same instance on lookup but not on lookup producer" do
duck_type = type_factory.ruby(InjectorSpecModule::Daffy)
injector = injector(lbinder)
d1 = injector.lookup(scope, duck_type)
d2 = injector.lookup(scope, duck_type)
d1.equal?(d2).should == true
d1 = injector.lookup_producer(scope, duck_type).produce(scope)
d2 = injector.lookup_producer(scope, duck_type).produce(scope)
d1.equal?(d2).should == false
end
it "assisted inject kicks in for classes with a class inject method" do
duck_type = type_factory.ruby(InjectorSpecModule::ScroogeMcDuck)
injector = injector(lbinder)
# Do not pass any arguments, the ScroogeMcDuck :inject method should pick 1 by default
# This tests zero args passed
injector.lookup(scope, duck_type).fortune.should == 1
injector.lookup_producer(scope, duck_type).produce(scope).fortune.should == 1
end
it "assisted inject selects the inject method if it exists over a zero args constructor" do
injector = injector(lbinder)
duck_type = type_factory.ruby(InjectorSpecModule::AngryDuck)
injector.lookup(scope, duck_type).is_a?(InjectorSpecModule::Donald).should == true
injector.lookup_producer(scope, duck_type).produce(scope).is_a?(InjectorSpecModule::Donald).should == true
end
it "assisted inject selects the zero args constructor if injector is from a superclass" do
injector = injector(lbinder)
duck_type = type_factory.ruby(InjectorSpecModule::ArneAnka)
injector.lookup(scope, duck_type).is_a?(InjectorSpecModule::ArneAnka).should == true
injector.lookup_producer(scope, duck_type).produce(scope).is_a?(InjectorSpecModule::ArneAnka).should == true
end
end
context 'and conditionals are in use' do
let(:binder) { binder_with_categories()}
let(:lbinder) { binder.define_layers(layered_bindings) }
it "should be possible to shadow a bound value in a higher precedented category" do
bindings.bind().name('a_string').to('42')
bindings.when_in_category('environment', 'dev').bind().name('a_string').to('43')
bindings.when_in_category('node', 'kermit').bind().name('a_string').to('being green')
injector(lbinder).lookup(scope,'a_string').should == 'being green'
end
it "shadowing should not happen when not in a category" do
bindings.bind().name('a_string').to('42')
bindings.when_in_category('environment', 'dev').bind().name('a_string').to('43')
bindings.when_in_category('node', 'piggy').bind().name('a_string').to('being green')
injector(lbinder).lookup(scope,'a_string').should == '43'
end
it "multiple predicates makes binding more specific" do
bindings.bind().name('a_string').to('42')
bindings.when_in_category('environment', 'dev').bind().name('a_string').to('43')
bindings.when_in_category('node', 'kermit').bind().name('a_string').to('being green')
bindings.when_in_categories({'node'=>'kermit', 'environment'=>'dev'}).bind().name('a_string').to('being dev green')
injector(lbinder).lookup(scope,'a_string').should == 'being dev green'
end
it "multiple predicates makes binding more specific, but not more specific than higher precedence" do
bindings.bind().name('a_string').to('42')
bindings.when_in_category('environment', 'dev').bind().name('a_string').to('43')
bindings.when_in_category('node', 'kermit').bind().name('a_string').to('being green')
bindings.when_in_categories({'node'=>'kermit', 'environment'=>'dev'}).bind().name('a_string').to('being dev green')
bindings.when_in_category('highest', 'test').bind().name('a_string').to('bazinga')
injector(lbinder).lookup(scope,'a_string').should == 'bazinga'
end
end
context "and multiple layers are in use" do
let(:binder) { binder_with_categories()}
it "a higher layer shadows anything in a lower layer" do
bindings1 = factory.named_bindings('test1')
bindings1.when_in_category("highest", "test").bind().name('a_string').to('bad stuff')
lower_layer = factory.named_layer('lower-layer', bindings1.model)
bindings2 = factory.named_bindings('test2')
bindings2.bind().name('a_string').to('good stuff')
higher_layer = factory.named_layer('higher-layer', bindings2.model)
binder.define_layers(factory.layered_bindings(higher_layer, lower_layer))
injector = injector(binder)
injector.lookup(scope,'a_string').should == 'good stuff'
end
end
context "and dealing with Data types" do
let(:binder) { binder_with_categories()}
let(:lbinder) { binder.define_layers(layered_bindings) }
it "should treat all data as same type w.r.t. key" do
bindings.bind().name('a_string').to('42')
bindings.bind().name('an_int').to(43)
bindings.bind().name('a_float').to(3.14)
bindings.bind().name('a_boolean').to(true)
bindings.bind().name('an_array').to([1,2,3])
bindings.bind().name('a_hash').to({'a'=>1,'b'=>2,'c'=>3})
injector = injector(lbinder)
injector.lookup(scope,'a_string').should == '42'
injector.lookup(scope,'an_int').should == 43
injector.lookup(scope,'a_float').should == 3.14
injector.lookup(scope,'a_boolean').should == true
injector.lookup(scope,'an_array').should == [1,2,3]
injector.lookup(scope,'a_hash').should == {'a'=>1,'b'=>2,'c'=>3}
end
it "should provide type-safe lookup of given type/name" do
bindings.bind().string().name('a_string').to('42')
bindings.bind().integer().name('an_int').to(43)
bindings.bind().float().name('a_float').to(3.14)
bindings.bind().boolean().name('a_boolean').to(true)
bindings.bind().array_of_data().name('an_array').to([1,2,3])
bindings.bind().hash_of_data().name('a_hash').to({'a'=>1,'b'=>2,'c'=>3})
injector = injector(lbinder)
# Check lookup using implied Data type
injector.lookup(scope,'a_string').should == '42'
injector.lookup(scope,'an_int').should == 43
injector.lookup(scope,'a_float').should == 3.14
injector.lookup(scope,'a_boolean').should == true
injector.lookup(scope,'an_array').should == [1,2,3]
injector.lookup(scope,'a_hash').should == {'a'=>1,'b'=>2,'c'=>3}
# Check lookup using expected type
injector.lookup(scope,type_factory.string(), 'a_string').should == '42'
injector.lookup(scope,type_factory.integer(), 'an_int').should == 43
injector.lookup(scope,type_factory.float(),'a_float').should == 3.14
injector.lookup(scope,type_factory.boolean(),'a_boolean').should == true
injector.lookup(scope,type_factory.array_of_data(),'an_array').should == [1,2,3]
injector.lookup(scope,type_factory.hash_of_data(),'a_hash').should == {'a'=>1,'b'=>2,'c'=>3}
# Check lookup using wrong type
expect { injector.lookup(scope,type_factory.integer(), 'a_string')}.to raise_error(/Type error/)
expect { injector.lookup(scope,type_factory.string(), 'an_int')}.to raise_error(/Type error/)
expect { injector.lookup(scope,type_factory.string(),'a_float')}.to raise_error(/Type error/)
expect { injector.lookup(scope,type_factory.string(),'a_boolean')}.to raise_error(/Type error/)
expect { injector.lookup(scope,type_factory.string(),'an_array')}.to raise_error(/Type error/)
expect { injector.lookup(scope,type_factory.string(),'a_hash')}.to raise_error(/Type error/)
end
end
end
context "When looking up producer" do
it 'the value is produced by calling produce(scope)' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup_producer(scope, 'a_string').produce(scope).should == '42'
end
context 'a block transforming the result can be given' do
it 'that transform a found value given scope and producer' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup_producer(scope, 'a_string') {|zcope, p| p.produce(zcope) + '42' }.should == '4242'
end
it 'that transform a found value given only producer' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup_producer(scope, 'a_string') {|p| p.produce(scope) + '42' }.should == '4242'
end
it 'that can produce a default value when entry is not found' do
bindings.bind().name('a_string').to('42')
injector(lbinder).lookup_producer(scope, 'a_non_existing_string') {|p| p ? (raise Error,"Should not happen") : '4242' }.should == '4242'
end
end
end
context "When dealing with singleton vs. non singleton" do
it "should produce the same instance when producer is a singleton" do
bindings.bind().name('a_string').to('42')
injector = injector(lbinder)
a = injector.lookup(scope, 'a_string')
b = injector.lookup(scope, 'a_string')
a.equal?(b).should == true
end
it "should produce different instances when producer is a non singleton producer" do
bindings.bind().name('a_string').to_series_of('42')
injector = injector(lbinder)
a = injector.lookup(scope, 'a_string')
b = injector.lookup(scope, 'a_string')
a.should == '42'
b.should == '42'
a.equal?(b).should == false
end
end
context "When using the lookup producer" do
it "should lookup again to produce a value" do
bindings.bind().name('a_string').to_lookup_of('another_string')
bindings.bind().name('another_string').to('hello')
injector(lbinder).lookup(scope, 'a_string').should == 'hello'
end
it "should produce nil if looked up key does not exist" do
bindings.bind().name('a_string').to_lookup_of('non_existing')
injector(lbinder).lookup(scope, 'a_string').should == nil
end
it "should report an error if lookup loop is detected" do
bindings.bind().name('a_string').to_lookup_of('a_string')
expect { injector(lbinder).lookup(scope, 'a_string') }.to raise_error(/Lookup loop/)
end
end
context "When using the hash lookup producer" do
it "should lookup a key in looked up hash" do
data_hash = type_factory.hash_of_data()
bindings.bind().name('a_string').to_hash_lookup_of(data_hash, 'a_hash', 'huey')
bindings.bind().name('a_hash').to({'huey' => 'red', 'dewey' => 'blue', 'louie' => 'green'})
injector(lbinder).lookup(scope, 'a_string').should == 'red'
end
it "should produce nil if looked up entry does not exist" do
data_hash = type_factory.hash_of_data()
bindings.bind().name('a_string').to_hash_lookup_of(data_hash, 'non_existing_entry', 'huey')
bindings.bind().name('a_hash').to({'huey' => 'red', 'dewey' => 'blue', 'louie' => 'green'})
injector(lbinder).lookup(scope, 'a_string').should == nil
end
end
context "When using the first found producer" do
it "should lookup until it finds a value, but no further" do
bindings.bind().name('a_string').to_first_found(['b_string', 'c_string', 'g_string'])
bindings.bind().name('c_string').to('hello')
bindings.bind().name('g_string').to('Oh, mrs. Smith...')
injector(lbinder).lookup(scope, 'a_string').should == 'hello'
end
end
context "When producing instances" do
it "should lookup an instance of a class without arguments" do
bindings.bind().type(duck_type).name('the_duck').to(InjectorSpecModule::Daffy)
injector(lbinder).lookup(scope, duck_type, 'the_duck').is_a?(InjectorSpecModule::Daffy).should == true
end
it "should lookup an instance of a class with arguments" do
bindings.bind().type(duck_type).name('the_duck').to(InjectorSpecModule::ScroogeMcDuck, 1234)
injector = injector(lbinder)
the_duck = injector.lookup(scope, duck_type, 'the_duck')
the_duck.is_a?(InjectorSpecModule::ScroogeMcDuck).should == true
the_duck.fortune.should == 1234
end
it "singleton producer should not be recreated between lookups" do
bindings.bind().type(duck_type).name('the_duck').to_producer(InjectorSpecModule::ScroogeProducer)
injector = injector(lbinder)
the_duck = injector.lookup(scope, duck_type, 'the_duck')
the_duck.is_a?(InjectorSpecModule::ScroogeMcDuck).should == true
the_duck.fortune.should == 200
# singleton, do it again to get next value in series - it is the producer that is a singleton
# not the produced value
the_duck = injector.lookup(scope, duck_type, 'the_duck')
the_duck.is_a?(InjectorSpecModule::ScroogeMcDuck).should == true
the_duck.fortune.should == 400
duck_producer = injector.lookup_producer(scope, duck_type, 'the_duck')
duck_producer.produce(scope).fortune.should == 800
end
it "series of producers should recreate producer on each lookup and lookup_producer" do
bindings.bind().type(duck_type).name('the_duck').to_producer_series(InjectorSpecModule::ScroogeProducer)
injector = injector(lbinder)
duck_producer = injector.lookup_producer(scope, duck_type, 'the_duck')
duck_producer.produce(scope).fortune().should == 200
duck_producer.produce(scope).fortune().should == 400
# series, each lookup gets a new producer (initialized to produce 200)
duck_producer = injector.lookup_producer(scope, duck_type, 'the_duck')
duck_producer.produce(scope).fortune().should == 200
duck_producer.produce(scope).fortune().should == 400
injector.lookup(scope, duck_type, 'the_duck').fortune().should == 200
injector.lookup(scope, duck_type, 'the_duck').fortune().should == 200
end
end
context "When working with multibind" do
context "of hash kind" do
it "a multibind produces contributed items keyed by their bound key-name" do
hash_of_duck = type_factory.hash_of(duck_type)
multibind_id = "ducks"
bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews')
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('nephew1').to(InjectorSpecModule::NamedDuck, 'Huey')
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('nephew2').to(InjectorSpecModule::NamedDuck, 'Dewey')
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('nephew3').to(InjectorSpecModule::NamedDuck, 'Louie')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew1').to(InjectorSpecModule::NamedDuck, 'Huey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew2').to(InjectorSpecModule::NamedDuck, 'Dewey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew3').to(InjectorSpecModule::NamedDuck, 'Louie')
injector = injector(lbinder)
the_ducks = injector.lookup(scope, hash_of_duck, "donalds_nephews")
the_ducks.size.should == 3
the_ducks['nephew1'].name.should == 'Huey'
the_ducks['nephew2'].name.should == 'Dewey'
the_ducks['nephew3'].name.should == 'Louie'
end
it "is an error to not bind contribution with a name" do
hash_of_duck = type_factory.hash_of(duck_type)
multibind_id = "ducks"
bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews')
# missing name
- bindings.bind_in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Huey')
- bindings.bind_in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Dewey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Huey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Dewey')
expect {
the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews")
}.to raise_error(/must have a name/)
end
it "is an error to bind with duplicate key when using default (priority) conflict resolution" do
hash_of_duck = type_factory.hash_of(duck_type)
multibind_id = "ducks"
bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews')
# missing name
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Huey')
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Dewey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Huey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Dewey')
expect {
the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews")
}.to raise_error(/Duplicate key/)
end
it "is not an error to bind with duplicate key when using (ignore) conflict resolution" do
hash_of_duck = type_factory.hash_of(duck_type)
multibind_id = "ducks"
bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews').producer_options(:conflict_resolution => :ignore)
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Huey')
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Dewey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Huey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Dewey')
expect {
the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews")
}.to_not raise_error(/Duplicate key/)
end
it "should produce detailed type error message" do
hash_of_integer = type_factory.hash_of(type_factory.integer())
multibind_id = "ints"
mb = bindings.multibind(multibind_id).type(hash_of_integer).name('donalds_family')
- bindings.bind_in_multibind(multibind_id).name('nephew').to('Huey')
+ bindings.bind.in_multibind(multibind_id).name('nephew').to('Huey')
expect { ducks = injector(lbinder).lookup(scope, 'donalds_family')
}.to raise_error(%r{expected: Integer, got: String})
end
it "should be possible to combine hash multibind contributions with append on conflict" do
# This case uses a multibind of individual strings, but combines them
# into an array bound to a hash key
# (There are other ways to do this - e.g. have the multibind lookup a multibind
# of array type to which nephews are contributed).
#
hash_of_data = type_factory.hash_of_data()
multibind_id = "ducks"
mb = bindings.multibind(multibind_id).type(hash_of_data).name('donalds_family')
mb.producer_options(:conflict_resolution => :append)
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Huey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Dewey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Louie')
- bindings.bind_in_multibind(multibind_id).name('uncles').to('Scrooge McDuck')
- bindings.bind_in_multibind(multibind_id).name('uncles').to('Ludwig Von Drake')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie')
+ bindings.bind.in_multibind(multibind_id).name('uncles').to('Scrooge McDuck')
+ bindings.bind.in_multibind(multibind_id).name('uncles').to('Ludwig Von Drake')
ducks = injector(lbinder).lookup(scope, 'donalds_family')
ducks['nephews'].should == ['Huey', 'Dewey', 'Louie']
ducks['uncles'].should == ['Scrooge McDuck', 'Ludwig Von Drake']
end
it "should be possible to combine hash multibind contributions with append, flat, and uniq, on conflict" do
# This case uses a multibind of individual strings, but combines them
# into an array bound to a hash key
# (There are other ways to do this - e.g. have the multibind lookup a multibind
# of array type to which nephews are contributed).
#
hash_of_data = type_factory.hash_of_data()
multibind_id = "ducks"
mb = bindings.multibind(multibind_id).type(hash_of_data).name('donalds_family')
mb.producer_options(:conflict_resolution => :append, :flatten => true, :uniq => true)
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Huey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Huey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Dewey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to(['Huey', ['Louie'], 'Dewey'])
- bindings.bind_in_multibind(multibind_id).name('uncles').to('Scrooge McDuck')
- bindings.bind_in_multibind(multibind_id).name('uncles').to('Ludwig Von Drake')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to(['Huey', ['Louie'], 'Dewey'])
+ bindings.bind.in_multibind(multibind_id).name('uncles').to('Scrooge McDuck')
+ bindings.bind.in_multibind(multibind_id).name('uncles').to('Ludwig Von Drake')
ducks = injector(lbinder).lookup(scope, 'donalds_family')
ducks['nephews'].should == ['Huey', 'Dewey', 'Louie']
ducks['uncles'].should == ['Scrooge McDuck', 'Ludwig Von Drake']
end
it "should fail attempts to append, perform uniq or flatten on type incompatible multibind hash" do
hash_of_integer = type_factory.hash_of(type_factory.integer())
ids = ["ducks1", "ducks2", "ducks3"]
mb = bindings.multibind(ids[0]).type(hash_of_integer).name('broken_family0')
mb.producer_options(:conflict_resolution => :append)
mb = bindings.multibind(ids[1]).type(hash_of_integer).name('broken_family1')
mb.producer_options(:flatten => :true)
mb = bindings.multibind(ids[2]).type(hash_of_integer).name('broken_family2')
mb.producer_options(:uniq => :true)
binder.define_categories(factory.categories([]))
binder.define_layers(factory.layered_bindings(test_layer_with_bindings(bindings.model)))
injector = injector(binder)
expect { injector.lookup(scope, 'broken_family0')}.to raise_error(/:conflict_resolution => :append/)
expect { injector.lookup(scope, 'broken_family1')}.to raise_error(/:flatten/)
expect { injector.lookup(scope, 'broken_family2')}.to raise_error(/:uniq/)
end
it "a higher priority contribution is selected when resolution is :priority" do
hash_of_duck = type_factory.hash_of(duck_type)
multibind_id = "ducks"
bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews')
- mb1 = bindings.when_in_category("highest", "test").bind_in_multibind(multibind_id)
+ mb1 = bindings.when_in_category("highest", "test").bind.in_multibind(multibind_id)
mb1.type(duck_type).name('nephew').to(InjectorSpecModule::NamedDuck, 'Huey')
- mb2 = bindings.bind_in_multibind(multibind_id)
+ mb2 = bindings.bind.in_multibind(multibind_id)
mb2.type(duck_type).name('nephew').to(InjectorSpecModule::NamedDuck, 'Dewey')
binder.define_categories(factory.categories(['highest', 'test']))
binder.define_layers(layered_bindings)
injector(binder).lookup(scope, hash_of_duck, "donalds_nephews")['nephew'].name.should == 'Huey'
end
it "a higher priority contribution wins when resolution is :merge" do
hash_of_data = type_factory.hash_of_data()
multibind_id = "hashed_ducks"
bindings.multibind(multibind_id).type(hash_of_data).name('donalds_nephews').producer_options(:conflict_resolution => :merge)
- mb1 = bindings.when_in_category("highest", "test").bind_in_multibind(multibind_id)
+ mb1 = bindings.when_in_category("highest", "test").bind.in_multibind(multibind_id)
mb1.name('nephew').to({'name' => 'Huey', 'is' => 'winner'})
- mb2 = bindings.bind_in_multibind(multibind_id)
+ mb2 = bindings.bind.in_multibind(multibind_id)
mb2.name('nephew').to({'name' => 'Dewey', 'is' => 'looser', 'has' => 'cap'})
binder.define_categories(factory.categories(['highest', 'test']))
binder.define_layers(layered_bindings)
the_ducks = injector(binder).lookup(scope, "donalds_nephews");
the_ducks['nephew']['name'].should == 'Huey'
the_ducks['nephew']['is'].should == 'winner'
the_ducks['nephew']['has'].should == 'cap'
end
end
context "of array kind" do
it "an array multibind produces contributed items, names are allowed but ignored" do
array_of_duck = type_factory.array_of(duck_type)
multibind_id = "ducks"
bindings.multibind(multibind_id).type(array_of_duck).name('donalds_nephews')
# one with name (ignored, expect no error)
- bindings.bind_in_multibind(multibind_id).type(duck_type).name('nephew1').to(InjectorSpecModule::NamedDuck, 'Huey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew1').to(InjectorSpecModule::NamedDuck, 'Huey')
# two without name
- bindings.bind_in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Dewey')
- bindings.bind_in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Louie')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Dewey')
+ bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Louie')
the_ducks = injector(lbinder).lookup(scope, array_of_duck, "donalds_nephews")
the_ducks.size.should == 3
the_ducks.collect {|d| d.name }.sort.should == ['Dewey', 'Huey', 'Louie']
end
it "should be able to make result contain only unique entries" do
# This case uses a multibind of individual strings, and combines them
# into an array of unique values
#
array_of_data = type_factory.array_of_data()
multibind_id = "ducks"
mb = bindings.multibind(multibind_id).type(array_of_data).name('donalds_family')
# turn off priority on named to not trigger conflict as all additions have the same precedence
# (could have used the default for unnamed and add unnamed entries).
mb.producer_options(:priority_on_named => false, :uniq => true)
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Huey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Dewey')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Dewey') # duplicate
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Louie')
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Louie') # duplicate
- bindings.bind_in_multibind(multibind_id).name('nephews').to('Louie') # duplicate
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey') # duplicate
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie')
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie') # duplicate
+ bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie') # duplicate
ducks = injector(lbinder).lookup(scope, 'donalds_family')
ducks.should == ['Huey', 'Dewey', 'Louie']
end
it "should be able to contribute elements and arrays of elements and flatten 1 level" do
# This case uses a multibind of individual strings and arrays, and combines them
# into an array of flattened
#
array_of_string = type_factory.array_of(type_factory.string())
multibind_id = "ducks"
mb = bindings.multibind(multibind_id).type(array_of_string).name('donalds_family')
# flatten one level
mb.producer_options(:flatten => 1)
- bindings.bind_in_multibind(multibind_id).to('Huey')
- bindings.bind_in_multibind(multibind_id).to('Dewey')
- bindings.bind_in_multibind(multibind_id).to('Louie') # duplicate
- bindings.bind_in_multibind(multibind_id).to(['Huey', 'Dewey', 'Louie'])
+ bindings.bind.in_multibind(multibind_id).to('Huey')
+ bindings.bind.in_multibind(multibind_id).to('Dewey')
+ bindings.bind.in_multibind(multibind_id).to('Louie') # duplicate
+ bindings.bind.in_multibind(multibind_id).to(['Huey', 'Dewey', 'Louie'])
ducks = injector(lbinder).lookup(scope, 'donalds_family')
ducks.should == ['Huey', 'Dewey', 'Louie', 'Huey', 'Dewey', 'Louie']
end
it "should produce detailed type error message" do
array_of_integer = type_factory.array_of(type_factory.integer())
multibind_id = "ints"
mb = bindings.multibind(multibind_id).type(array_of_integer).name('donalds_family')
- bindings.bind_in_multibind(multibind_id).to('Huey')
+ bindings.bind.in_multibind(multibind_id).to('Huey')
expect { ducks = injector(lbinder).lookup(scope, 'donalds_family')
}.to raise_error(%r{expected: Integer, or Array\[Integer\], got: String})
end
end
+ context "When using multibind in multibind" do
+ it "a hash multibind can be contributed to another" do
+ hash_of_data = type_factory.hash_of_data()
+ mb1_id = 'data1'
+ mb2_id = 'data2'
+ top = bindings.multibind(mb1_id).type(hash_of_data).name("top")
+ detail = bindings.multibind(mb2_id).type(hash_of_data).name("detail").in_multibind(mb1_id)
+
+ bindings.bind.in_multibind(mb1_id).name('a').to(10)
+ bindings.bind.in_multibind(mb1_id).name('b').to(20)
+ bindings.bind.in_multibind(mb2_id).name('a').to(30)
+ bindings.bind.in_multibind(mb2_id).name('b').to(40)
+ expect( injector(lbinder).lookup(scope, "top") ).to eql({'detail' => {'a' => 30, 'b' => 40}, 'a' => 10, 'b' => 20})
+ end
+ end
+
context "When looking up entries requiring evaluation" do
let(:node) { Puppet::Node.new('localhost') }
let(:compiler) { Puppet::Parser::Compiler.new(node)}
let(:scope) { Puppet::Parser::Scope.new(compiler) }
let(:parser) { Puppet::Pops::Parser::Parser.new() }
it "should be possible to lookup a concatenated string" do
scope['duck'] = 'Donald Fauntleroy Duck'
expr = parser.parse_string('"Hello $duck"').current()
bindings.bind.name('the_duck').to(expr)
injector(lbinder).lookup(scope, 'the_duck').should == 'Hello Donald Fauntleroy Duck'
end
it "should be possible to post process lookup with a puppet lambda" do
model = parser.parse_string('fake() |$value| {$value + 1 }').current
bindings.bind.name('an_int').to(42).producer_options( :transformer => model.lambda)
injector(lbinder).lookup(scope, 'an_int').should == 43
end
end
end
end
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:59 AM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10076268
Default Alt Text
(93 KB)

Event Timeline