class Puppet::Pops::Loader::PuppetPlanInstantiator

The PuppetPlanInstantiator instantiates a Puppet::Functions::PuppetFunction given a Puppet Programming language source that when called evaluates the Puppet logic it contains.

Public Class Methods

create(loader, typed_name, source_ref, pp_code_string) click to toggle source

Produces an instance of the Function class with the given typed_name, or fails with an error if the given puppet source does not produce this instance when evaluated.

@param loader [Loader] The loader the function is associated with @param typed_name [TypedName] the type / name of the function to load @param source_ref [URI, String] a reference to the source / origin of the puppet code to evaluate @param pp_code_string [String] puppet code in a string

@return [Functions::Function] - an instantiated function with global scope closure associated with the given loader

   # File lib/puppet/pops/loader/puppet_plan_instantiator.rb
17 def self.create(loader, typed_name, source_ref, pp_code_string)
18   parser = Parser::EvaluatingParser.new()
19 
20   # parse and validate
21   result = parser.parse_string(pp_code_string, source_ref)
22 
23   # The parser attaches all definitions, including those nested in apply
24   # blocks, to the Program object. Node definitions in apply blocks are
25   # perfectly legal and don't count as the file containing multiple
26   # definitions for this purpose. By this point, we've already validated that
27   # there are no node definitions *outside* apply blocks, so we simply ignore
28   # them here.
29   definitions = result.definitions.reject { |definition| definition.is_a?(Puppet::Pops::Model::NodeDefinition) }
30 
31   # Only one plan is allowed (and no other definitions)
32   case definitions.size
33   when 0
34     raise ArgumentError, _("The code loaded from %{source_ref} does not define the plan '%{plan_name}' - it is empty.") % { source_ref: source_ref, plan_name: typed_name.name }
35   when 1
36     # ok
37   else
38     raise ArgumentError, _("The code loaded from %{source_ref} must contain only the plan '%{plan_name}' - it has additional definitions.") % { source_ref: source_ref, plan_name: typed_name.name }
39   end
40   the_plan_definition = definitions[0]
41 
42   unless the_plan_definition.is_a?(Model::PlanDefinition)
43     raise ArgumentError, _("The code loaded from %{source_ref} does not define the plan '%{plan_name}' - no plan found.") % { source_ref: source_ref, plan_name: typed_name.name }
44   end
45   unless the_plan_definition.name == typed_name.name
46     expected = typed_name.name
47     actual = the_plan_definition.name
48     raise ArgumentError, _("The code loaded from %{source_ref} produced plan with the wrong name, expected %{expected}, actual %{actual}") % { source_ref: source_ref, expected: expected, actual: actual }
49   end
50   unless result.body == the_plan_definition
51     raise ArgumentError, _("The code loaded from %{source} contains additional logic - can only contain the plan %{plan_name}") % { source: source_ref, plan_name: typed_name.name }
52   end
53 
54   # Adapt the function definition with loader - this is used from logic contained in it body to find the
55   # loader to use when making calls to the new function API. Such logic have a hard time finding the closure (where
56   # the loader is known - hence this mechanism
57   private_loader = loader.private_loader
58   Adapters::LoaderAdapter.adapt(the_plan_definition).loader_name = private_loader.loader_name
59 
60   # Cannot bind loaded functions to global scope, that must be done without binding that scope as
61   # loaders survive a compilation.
62   closure_scope = nil
63 
64   created = create_function_class(the_plan_definition)
65   # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things
66   # when calling functions etc.
67   # It should be bound to global scope
68 
69   created.new(closure_scope, private_loader)
70 end
create_from_model(plan_definition, loader) click to toggle source

Creates Function class and instantiates it based on a FunctionDefinition model @return [Array<TypedName, Functions.Function>] - array of

typed name, and an instantiated function with global scope closure associated with the given loader
   # File lib/puppet/pops/loader/puppet_plan_instantiator.rb
76 def self.create_from_model(plan_definition, loader)
77   created = create_function_class(plan_definition)
78   typed_name = TypedName.new(:plan, plan_definition.name)
79   [typed_name, created.new(nil, loader)]
80 end
create_function_class(plan_definition) click to toggle source
   # File lib/puppet/pops/loader/puppet_plan_instantiator.rb
82 def self.create_function_class(plan_definition)
83   # Create a 4x function wrapper around a named closure
84   Puppet::Functions.create_function(plan_definition.name, Puppet::Functions::PuppetFunction) do
85     # TODO: should not create a new evaluator per function
86     init_dispatch(Evaluator::Closure::Named.new(
87       plan_definition.name,
88       Evaluator::EvaluatorImpl.new(), plan_definition))
89   end
90 end