puppet 3.7.4 → 3.7.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +11 -6
  3. data/ext/build_defaults.yaml +2 -2
  4. data/ext/systemd/puppet.service +1 -0
  5. data/lib/hiera/puppet_function.rb +71 -0
  6. data/lib/puppet.rb +12 -0
  7. data/lib/puppet/application/device.rb +22 -5
  8. data/lib/puppet/daemon.rb +13 -4
  9. data/lib/puppet/defaults.rb +27 -4
  10. data/lib/puppet/environments.rb +1 -1
  11. data/lib/puppet/error.rb +4 -0
  12. data/lib/puppet/functions.rb +118 -65
  13. data/lib/puppet/functions/assert_type.rb +5 -5
  14. data/lib/puppet/functions/each.rb +12 -12
  15. data/lib/puppet/functions/epp.rb +3 -4
  16. data/lib/puppet/functions/filter.rb +12 -12
  17. data/lib/puppet/functions/hiera.rb +29 -0
  18. data/lib/puppet/functions/hiera_array.rb +34 -0
  19. data/lib/puppet/functions/hiera_hash.rb +36 -0
  20. data/lib/puppet/functions/hiera_include.rb +50 -0
  21. data/lib/puppet/functions/inline_epp.rb +2 -3
  22. data/lib/puppet/functions/map.rb +12 -12
  23. data/lib/puppet/functions/reduce.rb +6 -6
  24. data/lib/puppet/functions/scanf.rb +3 -3
  25. data/lib/puppet/functions/slice.rb +10 -9
  26. data/lib/puppet/functions/with.rb +3 -4
  27. data/lib/puppet/graph/simple_graph.rb +5 -5
  28. data/lib/puppet/metatype/manager.rb +1 -1
  29. data/lib/puppet/node/environment.rb +1 -1
  30. data/lib/puppet/parser/ast/arithmetic_operator.rb +1 -1
  31. data/lib/puppet/parser/ast/collexpr.rb +1 -1
  32. data/lib/puppet/parser/compiler.rb +3 -3
  33. data/lib/puppet/parser/functions/create_resources.rb +1 -9
  34. data/lib/puppet/parser/functions/defined.rb +1 -1
  35. data/lib/puppet/parser/functions/hiera.rb +20 -11
  36. data/lib/puppet/parser/functions/hiera_array.rb +23 -13
  37. data/lib/puppet/parser/functions/hiera_hash.rb +25 -15
  38. data/lib/puppet/parser/functions/hiera_include.rb +20 -9
  39. data/lib/puppet/parser/functions/lookup.rb +1 -1
  40. data/lib/puppet/parser/functions/realize.rb +1 -1
  41. data/lib/puppet/parser/functions/scanf.rb +21 -12
  42. data/lib/puppet/parser/parser_factory.rb +2 -2
  43. data/lib/puppet/parser/relationship.rb +1 -1
  44. data/lib/puppet/parser/scope.rb +34 -7
  45. data/lib/puppet/pops.rb +2 -0
  46. data/lib/puppet/pops/binder/lookup.rb +24 -7
  47. data/lib/puppet/pops/binder/producers.rb +2 -2
  48. data/lib/puppet/pops/evaluator/closure.rb +1 -1
  49. data/lib/puppet/pops/evaluator/evaluator_impl.rb +109 -17
  50. data/lib/puppet/pops/evaluator/puppet_proc.rb +69 -0
  51. data/lib/puppet/pops/evaluator/runtime3_converter.rb +175 -0
  52. data/lib/puppet/pops/evaluator/runtime3_support.rb +15 -128
  53. data/lib/puppet/pops/functions/dispatch.rb +21 -17
  54. data/lib/puppet/pops/functions/dispatcher.rb +3 -3
  55. data/lib/puppet/pops/functions/function.rb +46 -14
  56. data/lib/puppet/pops/issues.rb +2 -2
  57. data/lib/puppet/pops/model/model_label_provider.rb +1 -1
  58. data/lib/puppet/pops/parser/egrammar.ra +2 -0
  59. data/lib/puppet/pops/parser/eparser.rb +732 -724
  60. data/lib/puppet/pops/parser/heredoc_support.rb +1 -1
  61. data/lib/puppet/pops/parser/lexer2.rb +20 -22
  62. data/lib/puppet/pops/types/class_loader.rb +1 -1
  63. data/lib/puppet/pops/types/type_calculator.rb +104 -37
  64. data/lib/puppet/pops/types/type_factory.rb +1 -1
  65. data/lib/puppet/pops/types/types.rb +4 -1
  66. data/lib/puppet/pops/types/types_meta.rb +2 -2
  67. data/lib/puppet/pops/validation/checker4_0.rb +5 -3
  68. data/lib/puppet/provider/service/systemd.rb +1 -0
  69. data/lib/puppet/provider/yumrepo/inifile.rb +4 -1
  70. data/lib/puppet/resource.rb +3 -2
  71. data/lib/puppet/resource/catalog.rb +3 -2
  72. data/lib/puppet/resource/type.rb +1 -1
  73. data/lib/puppet/settings/environment_conf.rb +12 -4
  74. data/lib/puppet/type/package.rb +23 -13
  75. data/lib/puppet/util/autoload.rb +7 -7
  76. data/lib/puppet/util/errors.rb +4 -2
  77. data/lib/puppet/util/network_device/config.rb +5 -0
  78. data/lib/puppet/version.rb +1 -1
  79. data/lib/puppetx.rb +2 -2
  80. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/functions/callee.rb +8 -0
  81. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/functions/callee_ws.rb +8 -0
  82. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/metadata.json +9 -0
  83. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/user/caller.rb +5 -0
  84. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/user/caller_ws.rb +12 -0
  85. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/metadata.json +9 -0
  86. data/spec/integration/parser/environment_spec.rb +47 -0
  87. data/spec/integration/parser/future_compiler_spec.rb +11 -6
  88. data/spec/unit/application/device_spec.rb +52 -14
  89. data/spec/unit/daemon_spec.rb +0 -2
  90. data/spec/unit/environments_spec.rb +2 -2
  91. data/spec/unit/functions/assert_type_spec.rb +4 -25
  92. data/spec/unit/functions/hiera_spec.rb +127 -0
  93. data/spec/unit/functions/with_spec.rb +9 -4
  94. data/spec/unit/functions4_spec.rb +98 -35
  95. data/spec/unit/hiera/backend/puppet_backend_spec.rb +1 -1
  96. data/spec/unit/parser/functions/create_resources_spec.rb +2 -2
  97. data/spec/unit/parser/functions/defined_spec.rb +5 -0
  98. data/spec/unit/parser/functions/lookup_spec.rb +5 -1
  99. data/spec/unit/parser/functions/scanf_spec.rb +30 -0
  100. data/spec/unit/parser/scope_spec.rb +5 -0
  101. data/spec/unit/pops/binder/injector_spec.rb +1 -1
  102. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +33 -5
  103. data/spec/unit/pops/loaders/loaders_spec.rb +22 -1
  104. data/spec/unit/pops/parser/lexer2_spec.rb +28 -16
  105. data/spec/unit/pops/parser/parse_heredoc_spec.rb +21 -0
  106. data/spec/unit/pops/types/type_calculator_spec.rb +141 -19
  107. data/spec/unit/pops/types/type_factory_spec.rb +2 -2
  108. data/spec/unit/pops/validator/validator_spec.rb +25 -3
  109. data/spec/unit/provider/service/systemd_spec.rb +20 -4
  110. data/spec/unit/provider/user/hpux_spec.rb +1 -1
  111. data/spec/unit/provider/yumrepo/inifile_spec.rb +1 -0
  112. data/spec/unit/settings/environment_conf_spec.rb +12 -1
  113. data/spec/unit/type/package_spec.rb +0 -20
  114. data/spec/unit/util/network_device/config_spec.rb +6 -0
  115. metadata +3422 -3405
@@ -7,7 +7,7 @@ class Puppet::Parser::Relationship
7
7
  # This if statement is needed because the 3x parser cannot load
8
8
  # Puppet::Pops. This logic can be removed for 4.0 when the 3x AST
9
9
  # is removed (when Pops is always used).
10
- if !(Puppet[:parser] == 'future')
10
+ if !(Puppet.future_parser?)
11
11
  case resources
12
12
  when Puppet::Parser::Collector
13
13
  resources.collected.values
@@ -22,6 +22,9 @@ class Puppet::Parser::Scope
22
22
 
23
23
  AST = Puppet::Parser::AST
24
24
 
25
+ # Variables that always exist with nil value even if not set
26
+ BUILT_IN_VARS = ['module_name'.freeze, 'caller_module_name'.freeze].freeze
27
+
25
28
  Puppet::Util.logmethods(self)
26
29
 
27
30
  include Puppet::Util::Errors
@@ -181,9 +184,29 @@ class Puppet::Parser::Scope
181
184
 
182
185
  # Returns true if the variable of the given name is set to any value (including nil)
183
186
  #
187
+ # @return [Boolean] if variable exists or not
188
+ #
184
189
  def exist?(name)
185
- next_scope = inherited_scope || enclosing_scope
186
- effective_symtable(true).include?(name) || next_scope && next_scope.exist?(name)
190
+ # Note !! ensure the answer is boolean
191
+ !! if name =~ /^(.*)::(.+)$/
192
+ class_name = $1
193
+ variable_name = $2
194
+ return true if class_name == '' && BUILT_IN_VARS.include?(variable_name)
195
+
196
+ # lookup class, but do not care if it is not evaluated since that will result
197
+ # in it not existing anyway. (Tests may run with just scopes and no evaluated classes which
198
+ # will result in class_scope for "" not returning topscope).
199
+ klass = find_hostclass(class_name)
200
+ other_scope = klass.nil? ? nil : class_scope(klass)
201
+ if other_scope.nil?
202
+ class_name == '' ? compiler.topscope.exist?(variable_name) : false
203
+ else
204
+ other_scope.exist?(variable_name)
205
+ end
206
+ else
207
+ next_scope = inherited_scope || enclosing_scope
208
+ effective_symtable(true).include?(name) || next_scope && next_scope.exist?(name) || BUILT_IN_VARS.include?(name)
209
+ end
187
210
  end
188
211
 
189
212
  # Returns true if the given name is bound in the current (most nested) scope for assignments.
@@ -400,8 +423,12 @@ class Puppet::Parser::Scope
400
423
  end
401
424
 
402
425
  def variable_not_found(name, reason=nil)
426
+ # Built in variables always exist
427
+ if BUILT_IN_VARS.include?(name)
428
+ return nil
429
+ end
403
430
  if Puppet[:strict_variables]
404
- if Puppet[:parser] == 'future'
431
+ if Puppet.future_parser?
405
432
  throw :undefined_variable
406
433
  else
407
434
  reason_msg = reason.nil? ? '' : "; #{reason}"
@@ -534,8 +561,8 @@ class Puppet::Parser::Scope
534
561
  #
535
562
  # @see to_hash_legacy
536
563
  def to_hash(recursive = true)
537
- @parser ||= Puppet[:parser]
538
- if @parser == 'future'
564
+ @future_parser ||= Puppet.future_parser?
565
+ if @future_parser
539
566
  to_hash_future(recursive)
540
567
  else
541
568
  to_hash_legacy(recursive)
@@ -847,7 +874,7 @@ class Puppet::Parser::Scope
847
874
  # lookup in the compiler.
848
875
  #
849
876
  # Makes names passed in the names array absolute if they are relative
850
- # Names are now made absolute if Puppet[:parser] == 'future', this will
877
+ # Names are now made absolute if Puppet.future_parser? is true, this will
851
878
  # be the default behavior in Puppet 4.0
852
879
  #
853
880
  # Transforms Class[] and Resource[] type referenes to class name
@@ -860,7 +887,7 @@ class Puppet::Parser::Scope
860
887
  # @return [Array<String>] names after transformation
861
888
  #
862
889
  def transform_and_assert_classnames(names)
863
- if Puppet[:parser] == 'future'
890
+ if Puppet.future_parser?
864
891
  names.map do |name|
865
892
  case name
866
893
  when String
@@ -96,11 +96,13 @@ module Puppet
96
96
 
97
97
  module Evaluator
98
98
  require 'puppet/pops/evaluator/callable_signature'
99
+ require 'puppet/pops/evaluator/runtime3_converter'
99
100
  require 'puppet/pops/evaluator/runtime3_support'
100
101
  require 'puppet/pops/evaluator/evaluator_impl'
101
102
  require 'puppet/pops/evaluator/epp_evaluator'
102
103
  require 'puppet/pops/evaluator/callable_mismatch_describer'
103
104
  require 'puppet/pops/evaluator/collector_transformer'
105
+ require 'puppet/pops/evaluator/puppet_proc'
104
106
  module Collectors
105
107
  require 'puppet/pops/evaluator/collectors/abstract_collector'
106
108
  require 'puppet/pops/evaluator/collectors/fixed_set_collector'
@@ -46,14 +46,17 @@ class Puppet::Pops::Binder::Lookup
46
46
  names.each {|n| options[n] = undef_as_nil(input[n.to_s] || input[n]) }
47
47
  options
48
48
  end
49
+ private_class_method :to_symbolic_hash
49
50
 
50
51
  def self.type_mismatch(type_calculator, expected, got)
51
52
  "has wrong type, expected #{type_calculator.string(expected)}, got #{type_calculator.string(got)}"
52
53
  end
54
+ private_class_method :type_mismatch
53
55
 
54
56
  def self.fail(msg)
55
57
  raise Puppet::ParseError, "Function lookup() " + msg
56
58
  end
59
+ private_class_method :fail
57
60
 
58
61
  def self.fail_lookup(names)
59
62
  name_part = if names.size == 1
@@ -63,6 +66,7 @@ class Puppet::Pops::Binder::Lookup
63
66
  end
64
67
  fail("did not find a value for #{name_part}")
65
68
  end
69
+ private_class_method :fail_lookup
66
70
 
67
71
  def self.validate_options(options, type_calculator)
68
72
  type_parser = Puppet::Pops::Types::TypeParser.new
@@ -74,7 +78,7 @@ class Puppet::Pops::Binder::Lookup
74
78
 
75
79
  t = type_calculator.infer(options[:name])
76
80
  if ! type_calculator.assignable?(name_type, t)
77
- fail("given 'name' argument, #{type_mismatch(type_calculator, options[:name], t)}")
81
+ fail("given 'name' argument, #{type_mismatch(type_calculator, name_type, t)}")
78
82
  end
79
83
 
80
84
  # unless a type is already given (future case), parse the type (or default 'Data'), fails if invalid type is given
@@ -103,18 +107,22 @@ class Puppet::Pops::Binder::Lookup
103
107
  options[:override] = {} unless options[:override]
104
108
 
105
109
  end
110
+ private_class_method :validate_options
106
111
 
107
112
  def self.nil_as_undef(x)
108
113
  x.nil? ? :undef : x
109
114
  end
115
+ private_class_method :nil_as_undef
110
116
 
111
117
  def self.undef_as_nil(x)
112
118
  is_nil_or_undef?(x) ? nil : x
113
119
  end
120
+ private_class_method :undef_as_nil
114
121
 
115
122
  def self.is_nil_or_undef?(x)
116
123
  x.nil? || x == :undef
117
124
  end
125
+ private_class_method :is_nil_or_undef?
118
126
 
119
127
  # This is used as a marker - a value that cannot (at least not easily) by mistake be found in
120
128
  # hiera data.
@@ -128,7 +136,7 @@ class Puppet::Pops::Binder::Lookup
128
136
  elsif !(result = scope.compiler.injector.lookup(scope, type, name)).nil?
129
137
  result
130
138
  else
131
- result = scope.function_hiera([name, PrivateNotFoundMarker])
139
+ result = call_hiera_function(scope, name, PrivateNotFoundMarker)
132
140
  if !result.nil? && result != PrivateNotFoundMarker
133
141
  result
134
142
  else
@@ -136,8 +144,17 @@ class Puppet::Pops::Binder::Lookup
136
144
  end
137
145
  end
138
146
  end
147
+ private_class_method :search_for
139
148
 
140
- # This is the method called from the puppet/parser/functions/lookup.rb
149
+ def self.call_hiera_function(scope, name, dflt)
150
+ loader = scope.compiler.loaders.private_environment_loader
151
+ func = loader.load(:function, :hiera) unless loader.nil?
152
+ raise Error, 'Function not found: hiera' if func.nil?
153
+ func.call(scope, name, dflt)
154
+ end
155
+ private_class_method :call_hiera_function
156
+
157
+ # This is the method called from the puppet/parser/functions/lookup.rb
141
158
  # @param args [Array] array following the puppet function call conventions
142
159
  def self.lookup(scope, args)
143
160
  type_calculator = Puppet::Pops::Types::TypeCalculator.new
@@ -166,11 +183,11 @@ class Puppet::Pops::Binder::Lookup
166
183
  result = if pblock = options[:pblock]
167
184
  result2 = case pblock.parameter_count
168
185
  when 1
169
- pblock.call(scope, undef_as_nil(result))
186
+ pblock.call(undef_as_nil(result))
170
187
  when 2
171
- pblock.call(scope, result_with_name[ 0 ], undef_as_nil(result))
188
+ pblock.call(result_with_name[ 0 ], undef_as_nil(result))
172
189
  else
173
- pblock.call(scope, result_with_name[ 0 ], undef_as_nil(result), undef_as_nil(options[ :default ]))
190
+ pblock.call(result_with_name[ 0 ], undef_as_nil(result), undef_as_nil(options[ :default ]))
174
191
  end
175
192
 
176
193
  # if the given result was returned, there is no need to type-check it again
@@ -193,7 +210,7 @@ class Puppet::Pops::Binder::Lookup
193
210
  # way, and should instead be turned into :undef.
194
211
  # TODO PUPPET4: Simply return the result
195
212
  #
196
- Puppet[:parser] == 'future' ? result : nil_as_undef(result)
213
+ Puppet.future_parser? ? result : nil_as_undef(result)
197
214
  end
198
215
  end
199
216
  end
@@ -57,7 +57,7 @@ module Puppet::Pops::Binder::Producers
57
57
  def initialize(injector, binding, scope, options)
58
58
  if transformer_lambda = options[:transformer]
59
59
  if transformer_lambda.is_a?(Proc)
60
- raise ArgumentError, "Transformer Proc must take two arguments; scope, value." unless transformer_lambda.arity == 2
60
+ raise ArgumentError, "Transformer Proc must take one argument; value." unless transformer_lambda.arity == 1
61
61
  @transformer = transformer_lambda
62
62
  else
63
63
  raise ArgumentError, "Transformer must be a LambdaExpression" unless transformer_lambda.is_a?(Puppet::Pops::Model::LambdaExpression)
@@ -110,7 +110,7 @@ module Puppet::Pops::Binder::Producers
110
110
  #
111
111
  def do_transformation(scope, produced_value)
112
112
  return produced_value unless transformer
113
- transformer.call(scope, produced_value)
113
+ transformer.call(produced_value)
114
114
  end
115
115
  end
116
116
 
@@ -29,7 +29,7 @@ class Puppet::Pops::Evaluator::Closure < Puppet::Pops::Evaluator::CallableSignat
29
29
 
30
30
  # compatible with 3x AST::Lambda
31
31
  # @api public
32
- def call(scope, *args)
32
+ def call(*args)
33
33
  variable_bindings = combine_values_with_parameters(args)
34
34
 
35
35
  tc = Puppet::Pops::Types::TypeCalculator
@@ -53,9 +53,6 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
53
53
 
54
54
  @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new()
55
55
  @@relationship_operator ||= Puppet::Pops::Evaluator::RelationshipOperator.new()
56
-
57
- # Initialize the runtime module
58
- Puppet::Pops::Evaluator::Runtime3Support.instance_method(:initialize).bind(self).call()
59
56
  end
60
57
 
61
58
  # @api private
@@ -77,14 +74,43 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
77
74
  @@eval_visitor.visit_this_1(self, target, scope)
78
75
 
79
76
  rescue Puppet::Pops::SemanticError => e
80
- # a raised issue may not know the semantic target
77
+ # A raised issue may not know the semantic target, use errors call stack, but fill in the
78
+ # rest from a supplied semantic object, or the target instruction if there is not semantic
79
+ # object.
80
+ #
81
81
  fail(e.issue, e.semantic || target, e.options, e)
82
82
 
83
- rescue StandardError => e
84
- if e.is_a? Puppet::ParseError
85
- # ParseError's are supposed to be fully configured with location information
83
+ rescue Puppet::PreformattedError => e
84
+ # Already formatted with location information, and with the wanted call stack.
85
+ # Note this is currently a specialized ParseError, so rescue-order is important
86
+ #
87
+ raise e
88
+
89
+ rescue Puppet::ParseError => e
90
+ # ParseError may be raised in ruby code without knowing the location
91
+ # in puppet code.
92
+ # Accept a ParseError that has file or line information available
93
+ # as an error that should be used verbatim. (Tests typically run without
94
+ # setting a file name).
95
+ # ParseError can supply an original - it is impossible to determine which
96
+ # call stack that should be propagated, using the ParseError's backtrace.
97
+ #
98
+ if e.file || e.line
86
99
  raise e
100
+ else
101
+ # Since it had no location information, treat it as user intended a general purpose
102
+ # error. Pass on its call stack.
103
+ fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e)
87
104
  end
105
+
106
+
107
+ rescue Puppet::Error => e
108
+ # PuppetError has the ability to wrap an exception, if so, use the wrapped exception's
109
+ # call stack instead
110
+ fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e.original || e)
111
+
112
+ rescue StandardError => e
113
+ # All other errors, use its message and call stack
88
114
  fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e)
89
115
  end
90
116
  end
@@ -238,6 +264,8 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
238
264
  def eval_UnfoldExpression(o, scope)
239
265
  candidate = evaluate(o.expr, scope)
240
266
  case candidate
267
+ when nil
268
+ []
241
269
  when Array
242
270
  candidate
243
271
  when Hash
@@ -763,12 +791,7 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
763
791
  fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o})
764
792
  end
765
793
  name = o.functor_expr.value
766
-
767
- evaluated_arguments = unfold([], o.arguments, scope)
768
-
769
- # wrap lambda in a callable block if it is present
770
- evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda
771
- call_function(name, evaluated_arguments, o, scope)
794
+ call_function_with_block(name, unfold([], o.arguments, scope), o, scope)
772
795
  end
773
796
 
774
797
  # Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name)
@@ -783,13 +806,82 @@ class Puppet::Pops::Evaluator::EvaluatorImpl
783
806
  fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o})
784
807
  end
785
808
  name = name.value # the string function name
809
+ call_function_with_block(name, unfold([receiver], o.arguments || [], scope), o, scope)
810
+ end
786
811
 
787
- evaluated_arguments = unfold([receiver], o.arguments || [], scope)
812
+ def call_function_with_block(name, evaluated_arguments, o, scope)
813
+ if o.lambda.nil?
814
+ call_function(name, evaluated_arguments, o, scope)
815
+ else
816
+ closure = Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope)
817
+ call_function(name, evaluated_arguments, o, scope, &proc_from_closure(closure))
818
+ end
819
+ end
820
+ private :call_function_with_block
788
821
 
789
- # wrap lambda in a callable block if it is present
790
- evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda
791
- call_function(name, evaluated_arguments, o, scope)
822
+ # Creates a Proc with an arity count that matches the parameters of the given closure. The arity will
823
+ # be correct up to 10 parameters and then default to varargs (-1)
824
+ #
825
+ def proc_from_closure(closure)
826
+ return Puppet::Pops::Evaluator::PuppetProc.new(closure) { |*args| closure.call(*args) } unless RUBY_VERSION[0,3] == '1.8'
827
+
828
+ # This code is required since a Proc isn't propagated by reference in Ruby 1.8.x. It produces a standard
829
+ # Proc that has correct arity as a replacement for the otherwise used PuppetProc
830
+ # TODO: Remove when Ruby 1.8.x support is dropped
831
+ arity = closure.parameters.reduce(0) do |memo, param|
832
+ count = memo + 1
833
+ break -count if param.captures_rest || !param.value.nil?
834
+ count
835
+ end
836
+
837
+ case arity
838
+ when 0
839
+ proc { || closure.call }
840
+ when 1
841
+ proc { |a| closure.call(a) }
842
+ when 2
843
+ proc { |a, b| closure.call(a, b) }
844
+ when 3
845
+ proc { |a, b, c| closure.call(a, b, c) }
846
+ when 4
847
+ proc { |a, b, c, d| closure.call(a, b, c, d) }
848
+ when 5
849
+ proc { |a, b, c, d, e| closure.call(a, b, c, d, e) }
850
+ when 6
851
+ proc { |a, b, c, d, e, f| closure.call(a, b, c, d, e, f) }
852
+ when 7
853
+ proc { |a, b, c, d, e, f, g| closure.call(a, b, c, d, e, f, g) }
854
+ when 8
855
+ proc { |a, b, c, d, e, f, g, h| closure.call(a, b, c, d, e, f, g, h) }
856
+ when 9
857
+ proc { |a, b, c, d, e, f, g, h, i| closure.call(a, b, c, d, e, f, g, h, i) }
858
+ when 10
859
+ proc { |a, b, c, d, e, f, g, h, i, j| closure.call(a, b, c, d, e, f, g, h, i, j) }
860
+ when -1
861
+ proc { |*v| closure.call(*v) }
862
+ when -2
863
+ proc { |a, *v| closure.call(a, *v) }
864
+ when -3
865
+ proc { |a, b, *v| closure.call(a, b, *v) }
866
+ when -4
867
+ proc { |a, b, c, *v| closure.call(a, b, c, *v) }
868
+ when -5
869
+ proc { |a, b, c, d, *v| closure.call(a, b, c, d, *v) }
870
+ when -6
871
+ proc { |a, b, c, d, e, *v| closure.call(a, b, c, d, e, *v) }
872
+ when -7
873
+ proc { |a, b, c, d, e, f, *v| closure.call(a, b, c, d, e, f, *v) }
874
+ when -8
875
+ proc { |a, b, c, d, e, f, g, *v| closure.call(a, b, c, d, e, f, g, *v) }
876
+ when -9
877
+ proc { |a, b, c, d, e, f, g, h, *v| closure.call(a, b, c, d, e, f,g, h, *v) }
878
+ when -10
879
+ proc { |a, b, c, d, e, f, g, h, i, *v| closure.call(a, b, c, d, e, f,g, h, i, *v) }
880
+ else
881
+ proc { |*a| closure.call(*a) }
882
+ end
792
883
  end
884
+ private :proc_from_closure
793
885
 
794
886
  # @example
795
887
  # $x ? { 10 => true, 20 => false, default => 0 }
@@ -0,0 +1,69 @@
1
+ # Complies with Proc API by mapping a Puppet::Pops::Evaluator::Closure to a ruby Proc.
2
+ # Creating and passing an instance of this class instead of just a plain block makes
3
+ # it possible to inherit the parameter info and arity from the closure. Advanced users
4
+ # may also access the closure itself. The Puppet::Pops::Functions::Dispatcher uses this
5
+ # when it needs to get the Callable type of the closure.
6
+ #
7
+ # The class is part of the Puppet Function API for Ruby and thus public API but a user
8
+ # should never create an instance of this class.
9
+ #
10
+ # @api public
11
+ class Puppet::Pops::Evaluator::PuppetProc < Proc
12
+ # Creates a new instance from a closure and a block that will dispatch
13
+ # all parameters to the closure. The block must be similar to:
14
+ #
15
+ # { |*args| closure.call(*args) }
16
+ #
17
+ # @param closure [Puppet::Pops::Evaluator::Closure] The closure to map
18
+ # @param &block [Block] The varargs block that invokes the closure.call method
19
+ #
20
+ # @api private
21
+ def self.new(closure, &block)
22
+ proc = super(&block)
23
+ proc.instance_variable_set(:@closure, closure)
24
+ proc
25
+ end
26
+
27
+ # @return [Puppet::Pops::Evaluator::Closure] the mapped closure
28
+ # @api public
29
+ attr_reader :closure
30
+
31
+ # @overrides Block.lambda?
32
+ # @return [Boolean] always false since this proc doesn't do the Ruby lambda magic
33
+ # @api public
34
+ def lambda?
35
+ false
36
+ end
37
+
38
+ # Maps the closure parameters to standard Block parameter info where each
39
+ # parameter is represented as a two element Array where the first
40
+ # element is :req, :opt, or :rest and the second element is the name
41
+ # of the parameter.
42
+ #
43
+ # @return [Array<Array<Symbol>>] array of parameter info pairs
44
+ # @overrides Block.parameters
45
+ # @api public
46
+ def parameters
47
+ @closure.parameters.map do |param|
48
+ sym = param.name.to_sym
49
+ if param.captures_rest
50
+ [ :rest, sym ]
51
+ elsif param.value
52
+ [ :opt, sym ]
53
+ else
54
+ [ :req, sym ]
55
+ end
56
+ end
57
+ end
58
+
59
+ # @return [Fixnum] the arity of the block
60
+ # @overrides Block.arity
61
+ # @api public
62
+ def arity
63
+ parameters.reduce(0) do |memo, param|
64
+ count = memo + 1
65
+ break -count unless param[0] == :req
66
+ count
67
+ end
68
+ end
69
+ end