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
-~----------~----~----~----~------~----~------~--~---

Reply via email to