Issue #11331 has been updated by Henrik Lindberg.
This was much easier to implement that I first imagined. (Pull request coming
soon).
By introducing a "parameterized block" (a Puppet::Parser::AST::Lambda) and some
minor improvements to the ephemeral scope support I was able to quickly make
progress on this.
The syntax for calling a "method" is:
<pre>
lhs.funcname
lhs.funcname(<optional arguments>)
lhs.funcname {|...| ... }
lhs.funcname(<optional arguments>) {|...| ... }
</pre>
This is equivalent to calling the same function with the lhs as the first
parameter, and the lambda (if present) as the last argument). (This means any
existing function can be called using this syntax - e.g:
<pre>$a = $b.split('[.:]')
</pre>
Support for foreach is then simply implemented as a function.
The syntax for the LAMBDA is:
<pre>
{|<parameter_list>| <statements> }
{|<parameter_list>| = expression }
</pre>
Although the later is not required for the foreach function, it is required to
be able to support lambdas that function as predicates e.g.
<pre> $b = $a.reject {|$x| = $x =~ /ugly/ }
</pre>
The requirement to use a syntax marker (i.e. =) is a bit unfortunate, but it is
currently not possible to support statements | expression without this marker
unless a major refactoring is done of the grammar / evaluation as there are
ambiguities between function calls with and without parentheses. It is also not
possible to implement this as an eval function, since functions invokable as
statements are not allowed to produce an rvalue. (The use of = to turn an
expression into a statement is only allowed at the opening of a lambda, and
this may later be made optional (when/if grammar is refactored).
The <parameter_list> supports default values, but if used, they must be at the
end of the list as call is made "by position" and not "by name".
The foreach function allows iterating array slices of 1 or more elements (i.e.
pairs, triplets, etc). and a hash can be iterated over keys or keys and values.
e.g:
<pre>
$a = [1, present, 2, absent]
$a.foreach { |$x, $y| file ( "/file_$x: ensure => $y}
$a = {'1' => present, '2' => absent}
$a.foreach { |$x, $y| file ( "/file_$x: ensure => $y}
</pre>
To produce /file_1 with ensure present, and /file_2 with ensure absent.
Other examples of iterating over hashes.
<pre>
# iterate over keys
$a = {'1' => present, '2' => absent}
$a.foreach { |$x| file ( "/file_$x: ensure => present}
# access variable outside of lambda
$a = {'1' => present, '2' => absent}
$a.foreach { |$x| file ( "/file_$x: ensure => $a[$x]}
</pre>
It is not allowed to use class, define, nor node-statements in the lambda, and
variables that are assigned in the lambda are local and can not be referenced
outside of the scope of the lambda (but they are visible to any nested inner
lambdas). Lambda parameters shadow parameters with the same name in outer
scopes.
Since the implementation makes it possible to easily add custom methods (i.e.
done just like any other custom function), the implementation of lambda
evaluation is also very simple. If passed a lambda it is evaluated by:
<pre>
a_lambda.call(scope, *arguments)
</pre>
A lambda has two additional useful methods `#parameter_count` and
ยด#optional_parameter_count`.
----------------------------------------
Feature #11331: Add 'foreach' structure in manifests
https://projects.puppetlabs.com/issues/11331#change-81504
Author: Steve Shipway
Status: Needs Decision
Priority: High
Assignee: J.D. Welch
Category: language
Target version: 3.x
Affected Puppet version:
Keywords: ux backlog
Branch:
I doubt this would be simple to do, but it would be very useful to be able to
do something like this:
$variable = [ 'a', 'b', 'c' ]
foreach $loop $variable {
file { "/tmp/$loop": content=>"I am file $loop"; }
}
While it is already possible to use an array as a namevar to get something
similar, it doesnt allow you to have calculated parameters as well.
This would not be expected to break the declarative nature of puppet, though it
would bring in a new variable scope in the loop.
Using a define with an array for the namevar would work provided the top level
could not be called multiple times.
We want to have something like this:
define firewall($users,$port) {
iptables::open { $users: port=>$port; }
}
node foo {
$webusers = [ 'fred', 'sid' ]
$sshusers = [ 'fred', 'joe' ]
firewall { port80: users=>$webusers, port=>80; }
firewall { port22: users=>$sshusers, port=>22; }
}
This example would fail because the iptables::open define is called with user
'fred' two times (although with a different port parameter). If we could
instead have a foreach iteration then something like this would be useable:
define firewall($users,$port) {
foreach $users {
iptables::open { "$loop:$port": user=>$loop, port=>$port; }
}
}
This would ensure a unique namevar for iptables::open. We would also be able
to do things like initialise an array of users with different metadata
parameters (eg, their full names pulled form LDAP)
--
You have received this notification because you have either subscribed to it,
or are involved in it.
To change your notification preferences, please click here:
http://projects.puppetlabs.com/my/account
--
You received this message because you are subscribed to the Google Groups
"Puppet Bugs" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/puppet-bugs?hl=en.