The case and selector statements define ephemeral vars, like 'if'.
Usage:
$var = "foobar"
case $var {
"foo": {
notify { "got a foo": }
}
/(.*)bar$/: {
notify{ "hey we got a $1": }
}
}
$val = $test ? {
/^match.*$/ => "matched",
default => "default"
}
Signed-off-by: Brice Figureau <[email protected]>
---
lib/puppet/parser/ast/caseopt.rb | 10 +
lib/puppet/parser/ast/casestatement.rb | 11 +-
lib/puppet/parser/ast/selector.rb | 12 +-
lib/puppet/parser/grammar.ra | 1 +
lib/puppet/parser/parser.rb | 1179 +++++++++++++++++---------------
spec/unit/parser/ast/casestatement.rb | 143 ++++
spec/unit/parser/ast/selector.rb | 156 +++++
test/data/snippets/casestatement.pp | 7 +
test/data/snippets/selectorvalues.pp | 7 +
test/language/snippets.rb | 3 +-
test/lib/puppettest/parsertesting.rb | 5 +
11 files changed, 962 insertions(+), 572 deletions(-)
create mode 100755 spec/unit/parser/ast/casestatement.rb
create mode 100755 spec/unit/parser/ast/selector.rb
diff --git a/lib/puppet/parser/ast/caseopt.rb b/lib/puppet/parser/ast/caseopt.rb
index 824bde8..47f32e2 100644
--- a/lib/puppet/parser/ast/caseopt.rb
+++ b/lib/puppet/parser/ast/caseopt.rb
@@ -51,6 +51,16 @@ class Puppet::Parser::AST
end
end
+ def eachopt
+ if @value.is_a?(AST::ASTArray)
+ @value.each { |subval|
+ yield subval
+ }
+ else
+ yield @value
+ end
+ end
+
# Evaluate the actual statements; this only gets called if
# our option matched.
def evaluate(scope)
diff --git a/lib/puppet/parser/ast/casestatement.rb
b/lib/puppet/parser/ast/casestatement.rb
index 0727479..69d0f1b 100644
--- a/lib/puppet/parser/ast/casestatement.rb
+++ b/lib/puppet/parser/ast/casestatement.rb
@@ -21,9 +21,8 @@ class Puppet::Parser::AST
# Iterate across the options looking for a match.
default = nil
@options.each { |option|
- option.eachvalue(scope) { |opval|
- opval = opval.downcase if ! sensitive and
opval.respond_to?(:downcase)
- if opval == value
+ option.eachopt { |opt|
+ if opt.evaluate_match(value, scope, :file => file, :line
=> line, :sensitive => sensitive)
found = true
break
end
@@ -31,7 +30,11 @@ class Puppet::Parser::AST
if found
# we found a matching option
- retvalue = option.safeevaluate(scope)
+ begin
+ retvalue = option.safeevaluate(scope)
+ ensure
+ scope.unset_ephemeral_var
+ end
break
end
diff --git a/lib/puppet/parser/ast/selector.rb
b/lib/puppet/parser/ast/selector.rb
index ecad163..dceec61 100644
--- a/lib/puppet/parser/ast/selector.rb
+++ b/lib/puppet/parser/ast/selector.rb
@@ -32,13 +32,13 @@ class Puppet::Parser::AST
# Then look for a match in the options.
@values.each { |obj|
- param = obj.param.safeevaluate(scope)
- if ! sensitive && param.respond_to?(:downcase)
- param = param.downcase
- end
- if param == paramvalue
+ if obj.param.evaluate_match(paramvalue, scope, :file => file,
:line => line, :sensitive => sensitive)
# we found a matching option
- retvalue = obj.value.safeevaluate(scope)
+ begin
+ retvalue = obj.value.safeevaluate(scope)
+ ensure
+ scope.unset_ephemeral_var
+ end
found = true
break
elsif obj.param.is_a?(Default)
diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra
index 2040ee8..072f351 100644
--- a/lib/puppet/parser/grammar.ra
+++ b/lib/puppet/parser/grammar.ra
@@ -620,6 +620,7 @@ selectlhand: name
| DEFAULT {
result = ast AST::Default, :value => val[0][:value], :line => val[0][:line]
}
+ | regex
# These are only used for importing, and we don't interpolate there.
qtexts: quotedtext { result = [val[0].value] }
diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb
index 906304b..cedc7de 100644
--- a/lib/puppet/parser/parser.rb
+++ b/lib/puppet/parser/parser.rb
<Patch Elided>
diff --git a/spec/unit/parser/ast/casestatement.rb
b/spec/unit/parser/ast/casestatement.rb
new file mode 100755
index 0000000..554e295
--- /dev/null
+++ b/spec/unit/parser/ast/casestatement.rb
@@ -0,0 +1,143 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Parser::AST::CaseStatement do
+ before :each do
+ @scope = Puppet::Parser::Scope.new()
+ end
+
+ describe "when evaluating" do
+
+ before :each do
+ @test = stub 'test'
+ @test.stubs(:safeevaluate).with(@scope).returns("value")
+
+ @option1 = stub 'option1', :eachopt => nil, :default? => false
+ @option2 = stub 'option2', :eachopt => nil, :default? => false
+
+ @options = stub 'options'
+ @options.stubs(:each).multiple_yields(@option1, @option2)
+
+ @casestmt = Puppet::Parser::AST::CaseStatement.new :test => @test,
:options => @options
+ end
+
+ it "should evaluate test" do
+ @test.expects(:safeevaluate).with(@scope)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should downcase the evaluated test value if allowed" do
+ Puppet.stubs(:[]).with(:casesensitive).returns(false)
+ value = stub 'test'
+ @test.stubs(:safeevaluate).with(@scope).returns(value)
+
+ value.expects(:downcase)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should scan each option" do
+ @options.expects(:each).multiple_yields(@option1, @option2)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ describe "when scanning options" do
+ before :each do
+ @opval1 = stub_everything 'opval1'
+ @option1.stubs(:eachopt).yields(@opval1)
+
+ @opval2 = stub_everything 'opval2'
+ @option2.stubs(:eachopt).yields(@opval2)
+ end
+
+ it "should evaluate each sub-option" do
+ @option1.expects(:eachopt)
+ @option2.expects(:eachopt)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should evaluate first matching option" do
+ @opval2.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
}.returns(true)
+ @option2.expects(:safeevaluate).with(@scope)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should evaluate_match with sensitive parameter" do
+ Puppet.stubs(:[]).with(:casesensitive).returns(true)
+ @opval1.expects(:evaluate_match).with { |*arg|
arg[2][:sensitive] == true }
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should return the first matching evaluated option" do
+ @opval2.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
}.returns(true)
+ @option2.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @casestmt.evaluate(@scope).should == :result
+ end
+
+ it "should evaluate the default option if none matched" do
+ @option1.stubs(:default?).returns(true)
+ @option1.expects(:safeevaluate).with(@scope)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should return the default evaluated option if none matched" do
+ @option1.stubs(:default?).returns(true)
+ @option1.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @casestmt.evaluate(@scope).should == :result
+ end
+
+ it "should return nil if nothing matched" do
+ @casestmt.evaluate(@scope).should be_nil
+ end
+
+ it "should match and set scope ephemeral variables" do
+ @opval1.expects(:evaluate_match).with { |*arg| arg[0] ==
"value" and arg[1] == @scope }
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should evaluate this regex option if it matches" do
+ @opval1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+
+ @option1.expects(:safeevaluate).with(@scope)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should return this evaluated regex option if it matches" do
+ @opval1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+ @option1.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @casestmt.evaluate(@scope).should == :result
+ end
+
+ it "should unset scope ephemeral variables after option
evaluation" do
+ @opval1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+ @option1.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @scope.expects(:unset_ephemeral_var)
+
+ @casestmt.evaluate(@scope)
+ end
+
+ it "should not leak ephemeral variables even if evaluation fails"
do
+ @opval1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+ @option1.stubs(:safeevaluate).with(@scope).raises
+
+ @scope.expects(:unset_ephemeral_var)
+
+ lambda { @casestmt.evaluate(@scope) }.should raise_error
+ end
+ end
+
+ end
+end
diff --git a/spec/unit/parser/ast/selector.rb b/spec/unit/parser/ast/selector.rb
new file mode 100755
index 0000000..8b00577
--- /dev/null
+++ b/spec/unit/parser/ast/selector.rb
@@ -0,0 +1,156 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Parser::AST::Selector do
+ before :each do
+ @scope = Puppet::Parser::Scope.new()
+ end
+
+ describe "when evaluating" do
+
+ before :each do
+ @param = stub 'param'
+ @param.stubs(:safeevaluate).with(@scope).returns("value")
+
+ @value1 = stub 'value1'
+ @param1 = stub_everything 'param1'
+ @param1.stubs(:safeevaluate).with(@scope).returns(@param1)
+ @param1.stubs(:respond_to?).with(:downcase).returns(false)
+ @value1.stubs(:param).returns(@param1)
+ @value1.stubs(:value).returns(@value1)
+
+ @value2 = stub 'value2'
+ @param2 = stub_everything 'param2'
+ @param2.stubs(:safeevaluate).with(@scope).returns(@param2)
+ @param2.stubs(:respond_to?).with(:downcase).returns(false)
+ @value2.stubs(:param).returns(@param2)
+ @value2.stubs(:value).returns(@value2)
+
+ @values = stub 'values', :instance_of? => true
+ @values.stubs(:each).multiple_yields(@value1, @value2)
+
+ @selector = Puppet::Parser::AST::Selector.new :param => @param,
:values => @values
+ @selector.stubs(:fail)
+ end
+
+ it "should evaluate param" do
+ @param.expects(:safeevaluate).with(@scope)
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should downcase the evaluated param value if allowed" do
+ Puppet.stubs(:[]).with(:casesensitive).returns(false)
+ value = stub 'param'
+ @param.stubs(:safeevaluate).with(@scope).returns(value)
+
+ value.expects(:downcase)
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should scan each option" do
+ @values.expects(:each).multiple_yields(@value1, @value2)
+
+ @selector.evaluate(@scope)
+ end
+
+ describe "when scanning values" do
+ it "should evaluate first matching option" do
+ @param2.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
}.returns(true)
+ @value2.expects(:safeevaluate).with(@scope)
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should return the first matching evaluated option" do
+ @param2.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
}.returns(true)
+ @value2.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @selector.evaluate(@scope).should == :result
+ end
+
+ it "should evaluate the default option if none matched" do
+
@param1.stubs(:is_a?).with(Puppet::Parser::AST::Default).returns(true)
+ @value1.expects(:safeevaluate).with(@scope).returns(@param1)
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should return the default evaluated option if none matched" do
+ result = stub 'result'
+
@param1.stubs(:is_a?).with(Puppet::Parser::AST::Default).returns(true)
+ @value1.stubs(:safeevaluate).returns(result)
+
+ @selector.evaluate(@scope).should == result
+ end
+
+ it "should return nil if nothing matched" do
+ @selector.evaluate(@scope).should be_nil
+ end
+
+ it "should delegate matching to evaluate_match" do
+ @param1.expects(:evaluate_match).with { |*arg| arg[0] ==
"value" and arg[1] == @scope }
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should transmit the sensitive parameter to evaluate_match" do
+ Puppet.stubs(:[]).with(:casesensitive).returns(:sensitive)
+ @param1.expects(:evaluate_match).with { |*arg|
arg[2][:sensitive] == :sensitive }
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should transmit the AST file and line to evaluate_match" do
+ @selector.file = :file
+ @selector.line = :line
+ @param1.expects(:evaluate_match).with { |*arg| arg[2][:file]
== :file and arg[2][:line] == :line }
+
+ @selector.evaluate(@scope)
+ end
+
+
+ it "should evaluate the matching param" do
+ @param1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+
+ @value1.expects(:safeevaluate).with(@scope)
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should return this evaluated option if it matches" do
+ @param1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+ @value1.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @selector.evaluate(@scope).should == :result
+ end
+
+ it "should unset scope ephemeral variables after option
evaluation" do
+ @param1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+ @value1.stubs(:safeevaluate).with(@scope).returns(:result)
+
+ @scope.expects(:unset_ephemeral_var)
+
+ @selector.evaluate(@scope)
+ end
+
+ it "should not leak ephemeral variables even if evaluation fails"
do
+ @param1.stubs(:evaluate_match).with { |*arg| arg[0] == "value"
and arg[1] == @scope }.returns(true)
+ @value1.stubs(:safeevaluate).with(@scope).raises
+
+ @scope.expects(:unset_ephemeral_var)
+
+ lambda { @selector.evaluate(@scope) }.should raise_error
+ end
+
+ it "should fail if there is no default" do
+ @selector.expects(:fail)
+
+ @selector.evaluate(@scope)
+ end
+ end
+
+ end
+end
diff --git a/test/data/snippets/casestatement.pp
b/test/data/snippets/casestatement.pp
index 9559828..66ecd72 100644
--- a/test/data/snippets/casestatement.pp
+++ b/test/data/snippets/casestatement.pp
@@ -56,3 +56,10 @@ case $yay {
default: { file { "/tmp/existsfile5": mode => 711, ensure => file } }
}
+
+$regexvar = "exists regex"
+case $regexvar {
+ "no match": { file { "/tmp/existsfile6": mode => 644, ensure => file } }
+ /(.*) regex$/: { file { "/tmp/${1}file6": mode => 755, ensure => file } }
+ default: { file { "/tmp/existsfile6": mode => 711, ensure => file } }
+}
diff --git a/test/data/snippets/selectorvalues.pp
b/test/data/snippets/selectorvalues.pp
index cd8cf77..d80d26c 100644
--- a/test/data/snippets/selectorvalues.pp
+++ b/test/data/snippets/selectorvalues.pp
@@ -34,9 +34,16 @@ $mode6 = $mode5 ? {
755 => 755
}
+$mode7 = "test regex" ? {
+ /regex$/ => 755,
+ default => 644
+}
+
+
file { "/tmp/selectorvalues1": ensure => file, mode => $mode1 }
file { "/tmp/selectorvalues2": ensure => file, mode => $mode2 }
file { "/tmp/selectorvalues3": ensure => file, mode => $mode3 }
file { "/tmp/selectorvalues4": ensure => file, mode => $mode4 }
file { "/tmp/selectorvalues5": ensure => file, mode => $mode5 }
file { "/tmp/selectorvalues6": ensure => file, mode => $mode6 }
+file { "/tmp/selectorvalues7": ensure => file, mode => $mode7 }
diff --git a/test/language/snippets.rb b/test/language/snippets.rb
index cfca10e..5c7805c 100755
--- a/test/language/snippets.rb
+++ b/test/language/snippets.rb
@@ -237,6 +237,7 @@ class TestSnippets < Test::Unit::TestCase
/tmp/existsfile3
/tmp/existsfile4
/tmp/existsfile5
+ /tmp/existsfile6
}
paths.each { |path|
@@ -281,7 +282,7 @@ class TestSnippets < Test::Unit::TestCase
end
def snippet_selectorvalues
- nums = %w{1 2 3 4 5}
+ nums = %w{1 2 3 4 5 6 7}
files = nums.collect { |n|
"/tmp/selectorvalues%s" % n
}
diff --git a/test/lib/puppettest/parsertesting.rb
b/test/lib/puppettest/parsertesting.rb
index 8186bf3..dee38eb 100644
--- a/test/lib/puppettest/parsertesting.rb
+++ b/test/lib/puppettest/parsertesting.rb
@@ -33,6 +33,11 @@ module PuppetTest::ParserTesting
def safeevaluate(*args)
evaluate()
end
+
+ def evaluate_match(othervalue, scope, options={})
+ value = evaluate()
+ othervalue == value
+ end
end
def astarray(*args)
--
1.6.0.2
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Puppet Developers" 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-dev?hl=en
-~----------~----~----~----~------~----~------~--~---