This changeset introduces regexp in if expression with the use of the
=~ (match) and !~ (not match) operator.

Usage:

if $uname =~ /Linux|Debian/ {
  ...
}

Moreover this patch creates ephemeral variables ($0 to $9) in the current
scope which contains the regex captures:
if $uname =~ /(Linux|Debian)/ {
  notice("this is a $1 system")
}

Signed-off-by: Brice Figureau <[email protected]>
---
 lib/puppet/parser/ast.rb                |    1 +
 lib/puppet/parser/ast/ifstatement.rb    |   17 +-
 lib/puppet/parser/ast/match_operator.rb |   31 +
 lib/puppet/parser/grammar.ra            |    6 +
 lib/puppet/parser/parser.rb             | 1272 ++++++++++++++++---------------
 spec/unit/parser/ast/ifstatement.rb     |   75 ++
 spec/unit/parser/ast/match_operator.rb  |   50 ++
 test/data/snippets/ifexpression.pp      |   12 +
 test/data/snippets/ifexpression.rb      |    6 -
 test/language/ast.rb                    |    5 +-
 test/language/snippets.rb               |    4 +
 11 files changed, 836 insertions(+), 643 deletions(-)
 create mode 100644 lib/puppet/parser/ast/match_operator.rb
 create mode 100755 spec/unit/parser/ast/ifstatement.rb
 create mode 100755 spec/unit/parser/ast/match_operator.rb
 create mode 100644 test/data/snippets/ifexpression.pp
 delete mode 100644 test/data/snippets/ifexpression.rb

diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index ab23dd1..ad8af74 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -105,6 +105,7 @@ require 'puppet/parser/ast/function'
 require 'puppet/parser/ast/hostclass'
 require 'puppet/parser/ast/ifstatement'
 require 'puppet/parser/ast/leaf'
+require 'puppet/parser/ast/match_operator'
 require 'puppet/parser/ast/minus'
 require 'puppet/parser/ast/node'
 require 'puppet/parser/ast/nop'
diff --git a/lib/puppet/parser/ast/ifstatement.rb 
b/lib/puppet/parser/ast/ifstatement.rb
index d216b7c..9d52123 100644
--- a/lib/puppet/parser/ast/ifstatement.rb
+++ b/lib/puppet/parser/ast/ifstatement.rb
@@ -18,14 +18,19 @@ class Puppet::Parser::AST
         def evaluate(scope)
             value = @test.safeevaluate(scope)
 
-            if Puppet::Parser::Scope.true?(value)
-                return @statements.safeevaluate(scope)
-            else
-                if defined? @else
-                    return @else.safeevaluate(scope)
+            # let's emulate a new scope for each branches
+            begin
+                if Puppet::Parser::Scope.true?(value)
+                    return @statements.safeevaluate(scope)
                 else
-                    return nil
+                    if defined? @else
+                        return @else.safeevaluate(scope)
+                    else
+                        return nil
+                    end
                 end
+            ensure
+                scope.unset_ephemeral_var
             end
         end
     end
diff --git a/lib/puppet/parser/ast/match_operator.rb 
b/lib/puppet/parser/ast/match_operator.rb
new file mode 100644
index 0000000..17e2782
--- /dev/null
+++ b/lib/puppet/parser/ast/match_operator.rb
@@ -0,0 +1,31 @@
+require 'puppet'
+require 'puppet/parser/ast/branch'
+
+class Puppet::Parser::AST
+    class MatchOperator < AST::Branch
+
+        attr_accessor :lval, :rval, :operator
+
+        # Iterate across all of our children.
+        def each
+            [...@lval,@rval].each { |child| yield child }
+        end
+
+        # Returns a boolean which is the result of the boolean operation
+        # of lval and rval operands
+        def evaluate(scope)
+            lval = @lval.safeevaluate(scope)
+
+            return @operator == "=~" if rval.evaluate_match(lval, scope)
+            return @operator == "!~"
+        end
+
+        def initialize(hash)
+            super
+
+            unless %w{!~ =~}.include?(@operator)
+                raise ArgumentError, "Invalid regexp operator %s" % @operator
+            end
+        end
+    end
+end
diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra
index 68acf35..2040ee8 100644
--- a/lib/puppet/parser/grammar.ra
+++ b/lib/puppet/parser/grammar.ra
@@ -488,6 +488,12 @@ else:             # nothing
 # per operator :-(
 
 expression:   rvalue
+            | expression MATCH regex {
+    result = ast AST::MatchOperator, :operator => val[1][:value], :lval => 
val[0], :rval => val[2]
+}
+            | expression NOMATCH regex {
+    result = ast AST::MatchOperator, :operator => val[1][:value], :lval => 
val[0], :rval => val[2]
+}
             | expression PLUS expression {
     result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval 
=> val[0], :rval => val[2]
 }
diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb
index ef08d6e..906304b 100644
--- a/lib/puppet/parser/parser.rb
+++ b/lib/puppet/parser/parser.rb
<Patch Elided>
diff --git a/spec/unit/parser/ast/ifstatement.rb 
b/spec/unit/parser/ast/ifstatement.rb
new file mode 100755
index 0000000..ab8379d
--- /dev/null
+++ b/spec/unit/parser/ast/ifstatement.rb
@@ -0,0 +1,75 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Parser::AST::IfStatement 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)
+
+            @stmt = stub 'stmt'
+            @stmt.stubs(:safeevaluate).with(@scope)
+
+            @else = stub 'else'
+            @else.stubs(:safeevaluate).with(@scope)
+
+            @ifstmt = Puppet::Parser::AST::IfStatement.new :test => @test, 
:statements => @stmt
+            @ifelsestmt = Puppet::Parser::AST::IfStatement.new :test => @test, 
:statements => @stmt, :else => @else
+        end
+
+        it "should evaluate test" do
+            Puppet::Parser::Scope.stubs(:true?).returns(false)
+
+            @test.expects(:safeevaluate).with(@scope)
+
+            @ifstmt.evaluate(@scope)
+        end
+
+        it "should evaluate if statements if test is true" do
+            Puppet::Parser::Scope.stubs(:true?).returns(true)
+
+            @stmt.expects(:safeevaluate).with(@scope)
+
+            @ifstmt.evaluate(@scope)
+        end
+
+        it "should not evaluate if statements if test is true" do
+            Puppet::Parser::Scope.stubs(:true?).returns(false)
+
+            @stmt.expects(:safeevaluate).with(@scope).never
+
+            @ifstmt.evaluate(@scope)
+        end
+
+        it "should evaluate the else branch if test is false" do
+            Puppet::Parser::Scope.stubs(:true?).returns(false)
+
+            @else.expects(:safeevaluate).with(@scope)
+
+            @ifelsestmt.evaluate(@scope)
+        end
+
+        it "should not evaluate the else branch if test is true" do
+            Puppet::Parser::Scope.stubs(:true?).returns(true)
+
+            @else.expects(:safeevaluate).with(@scope).never
+
+            @ifelsestmt.evaluate(@scope)
+        end
+
+        it "should reset ephemeral statements after evaluation" do
+            Puppet::Parser::Scope.stubs(:true?).returns(true)
+
+            @stmt.expects(:safeevaluate).with(@scope)
+            @scope.expects(:unset_ephemeral_var)
+
+            @ifstmt.evaluate(@scope)
+        end
+    end
+end
diff --git a/spec/unit/parser/ast/match_operator.rb 
b/spec/unit/parser/ast/match_operator.rb
new file mode 100755
index 0000000..985cf60
--- /dev/null
+++ b/spec/unit/parser/ast/match_operator.rb
@@ -0,0 +1,50 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Parser::AST::MatchOperator do
+    before :each do
+        @scope = Puppet::Parser::Scope.new()
+
+        @lval = stub 'lval'
+        @lval.stubs(:safeevaluate).with(@scope).returns("this is a string")
+
+        @rval = stub 'rval'
+        @rval.stubs(:evaluate_match)
+
+        @operator = Puppet::Parser::AST::MatchOperator.new :lval => @lval, 
:rval => @rval, :operator => "=~"
+    end
+
+    it "should evaluate the left operand" do
+        @lval.expects(:safeevaluate).with(@scope)
+
+        @operator.evaluate(@scope)
+    end
+
+    it "should fail for an unknown operator" do
+        lambda { operator = Puppet::Parser::AST::MatchOperator.new :lval => 
@lval, :operator => "unknown", :rval => @rval }.should raise_error
+    end
+
+    it "should evaluate_match the left operand" do
+        @rval.expects(:evaluate_match).with("this is a string", 
@scope).returns(:match)
+
+        @operator.evaluate(@scope)
+    end
+
+    { "=~" => true, "!~" => false }.each do |op, res|
+        it "should return #{res} if the regexp matches with #{op}" do
+            match = stub 'match'
+            @rval.stubs(:evaluate_match).with("this is a string", 
@scope).returns(match)
+
+            operator = Puppet::Parser::AST::MatchOperator.new :lval => @lval, 
:rval => @rval, :operator => op
+            operator.evaluate(@scope).should == res
+        end
+
+        it "should return #{!res} if the regexp doesn't match" do
+            @rval.stubs(:evaluate_match).with("this is a string", 
@scope).returns(nil)
+
+            operator = Puppet::Parser::AST::MatchOperator.new :lval => @lval, 
:rval => @rval, :operator => op
+            operator.evaluate(@scope).should == !res
+        end
+    end
+end
diff --git a/test/data/snippets/ifexpression.pp 
b/test/data/snippets/ifexpression.pp
new file mode 100644
index 0000000..29a6372
--- /dev/null
+++ b/test/data/snippets/ifexpression.pp
@@ -0,0 +1,12 @@
+$one = 1
+$two = 2
+
+if ($one < $two) and (($two < 3) or ($two == 2)) {
+    notice("True!")
+}
+
+if "test regex" =~ /(.*) regex/ {
+    file {
+        "/tmp/${1}iftest": ensure => file, mode => 0755
+    }
+}
diff --git a/test/data/snippets/ifexpression.rb 
b/test/data/snippets/ifexpression.rb
deleted file mode 100644
index eea3b85..0000000
--- a/test/data/snippets/ifexpression.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-$one = 1
-$two = 2
-
-if ($one < $two) and (($two < 3) or ($two == 2)) {
-    notice("True!")
-}
diff --git a/test/language/ast.rb b/test/language/ast.rb
index 8c0f31a..ed11bc1 100755
--- a/test/language/ast.rb
+++ b/test/language/ast.rb
@@ -15,6 +15,7 @@ class TestAST < Test::Unit::TestCase
     include PuppetTest::Support::Collection
 
     def test_if
+        scope = mkscope
         astif = nil
         astelse = nil
         fakeelse = FakeAST.new(:else)
@@ -35,14 +36,14 @@ class TestAST < Test::Unit::TestCase
         # We initialized it to true, so we should get that first
         ret = nil
         assert_nothing_raised {
-            ret = astif.evaluate("yay")
+            ret = astif.evaluate(scope)
         }
         assert_equal(:if, ret)
 
         # Now set it to false and check that
         faketest.evaluate = false
         assert_nothing_raised {
-            ret = astif.evaluate("yay")
+            ret = astif.evaluate(scope)
         }
         assert_equal(:else, ret)
     end
diff --git a/test/language/snippets.rb b/test/language/snippets.rb
index 87ad9e7..cfca10e 100755
--- a/test/language/snippets.rb
+++ b/test/language/snippets.rb
@@ -481,6 +481,10 @@ class TestSnippets < Test::Unit::TestCase
         assert_mode_equal(0600, path)
     end
 
+    def snippet_ifexpression
+        assert_file("/tmp/testiftest","if test");
+    end
+
     # Iterate across each of the snippets and create a test.
     Dir.entries(snippetdir).sort.each { |file|
         next if file =~ /^\./
-- 
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