[Bps-public-commit] RT-Condition-Complex branch, master, updated. d2d7a80f30ede2a56e3e20ae2a83239f6439e883

Ruslan Zakirov ruz at bestpractical.com
Thu Nov 5 12:33:34 EST 2009


The branch, master has been updated
       via  d2d7a80f30ede2a56e3e20ae2a83239f6439e883 (commit)
       via  e8ca7617f23b819ce8e6e9e79362d002cdb3564a (commit)
       via  494f840834edbd53192fc486ad644d3a269b3e23 (commit)
      from  3043ccbcc6dffbfa638854ac5af3c35f314adf9a (commit)

Summary of changes:
 inc/Module/Install/RTx.pm         |   11 ++-
 inc/Module/Install/RTx/Factory.pm |    2 +-
 lib/RT/Condition/Complex.pm       |  181 ++++++++++++++++++++++++++++++++-----
 lib/RT/Condition/Complex/Test.pm  |    1 +
 t/basics.t                        |    2 +-
 5 files changed, 171 insertions(+), 26 deletions(-)

- Log -----------------------------------------------------------------
commit 494f840834edbd53192fc486ad644d3a269b3e23
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sun Sep 6 04:46:38 2009 +0400

    update docs

diff --git a/lib/RT/Condition/Complex.pm b/lib/RT/Condition/Complex.pm
index 38f0382..505d98d 100644
--- a/lib/RT/Condition/Complex.pm
+++ b/lib/RT/Condition/Complex.pm
@@ -14,6 +14,107 @@ RT::Condition::Complex - build complex conditions out of other conditions
 
 =head1 DESCRIPTION
 
+This extension adds new type of conditions to the RT. It's like User Defined
+condition, shipped with RT, but you don't have to write any code, but instead
+you use simple syntax to check properties of the ticket and transaction or
+run other conditions you have.
+
+There are several goals this extension tries to solve:
+
+=over 4
+
+=item code reusing - complex conditions still need coding, but often you
+want to reuse them with additinal simple conditions or reuse some condition
+many times with other. In RT you have to use User Defined condition or
+module based condition to tie everything together and each time you copy
+code around. With this condition you can check other module based conditins,
+so you can re-use some of them many times in different combinations.
+
+=item simplicity - make things more simple for poeple who don't know how
+to program in perl, some complex things have to be programmed, but now
+then can be reused.
+
+=head1 HOW TO USE
+
+You open the web interface in a browser, goto configuration, the global or
+to specific queue and then scrips. Create a new scrip, on the creation
+page you select 'Complex' condition and in the 'Custom is applicable code'
+text box you write a pseudo code. For example:
+
+    Type = 'Create' OR StatusChange{open}
+
+Below you can read details on the syntax.
+
+=head2 Basic syntax
+
+Syntax is similar to TicketSQL and you can use AND/OR and parentheses "(" and
+")" to group checks and build custom logic of your condition. As checks you
+can use either L</Expressions> or L</Calls>.
+
+=head2 Expressions
+
+Expression is comparision of a field with a constant. For example "is type
+of a transaction equal to "Create" can be writen as:
+
+    Type = 'Create'
+
+On the left you write L<field|/Fields> you want to check, then goes
+L<comparision function|/Comparision functions> and a L<constant|/Constants>.
+
+=head2 Comparision functions
+
+At this moment the following comparision functions are supported:
+
+=over 4
+
+=item =, !=, >, >=, <, <= - basic comparisions, work for strings and numbers,
+depends if constant is a string or number, string comparision is cases insensetive.
+
+=item contains, not contains - the constant is substring of the field and negative variant.
+
+=item starts with, not starts with - the constant is beginning of the field.
+
+=item ends with, not ends with.
+
+=back
+
+=head2 Fields
+
+At this moment not many fields are available, but it's easy to add more. Patches
+are welcome. Here is the list:
+
+=over 4
+
+=item Type, Field, OldValue and NewValue - basic properties of the current transaction.
+
+=back
+
+=head2 Constants
+
+Constant is a number or a quoted string. Strings can be quoted 
+using ' or " characters. Character you're using for quoting should
+be escaped with \ and \ should be escaped as well. For example:
+
+    "Don't need to escape ' when string is quoted with double quotes."
+    'But can escape \' with \\.'
+
+=head2 Calls
+
+It's possible to call another module based condition. For example you have
+RT::Conditon::XXX that implements some complex condition then you can use
+the following syntax to call 'XXX':
+
+    XXX
+    !XXX
+
+If the condition is controlled by its argument then you can use:
+
+    XXX{'quoted argument'}
+    !XXX{'negation with argument'}
+
+As you can see argument should be quoted, you can read about quoting
+rules above in </Constants> section.
+
 =cut
 
 use Parse::BooleanLogic;
@@ -23,18 +124,22 @@ use Regexp::Common qw(delimited);
 my $re_quoted = qr{$RE{delimited}{-delim=>qq{\'\"}}{-esc=>'\\'}};
 
 my $re_exec_module = qr{[a-z][a-z0-9-]+}i;
+# module argument must be quoted as we don't know if it's quote to
+# protect from AND/OR words or argument of the condition should be
+# quoted
+my $re_module_argument = qr{$re_quoted};
 my $re_field = qr{[a-z.]+}i;
 my $re_value = qr{$re_quoted|[-+]?[0-9]+};
 my $re_bin_op = qr{!?=|[><]=?|(?:not\s+)?(?:contains|starts\s+with|ends\s+with)}i;
 my $re_un_op = qr{IS\s+(?:NOT\s+)?NULL|}i;
 
 my %op_handler = (
-    '='  => sub { return $_[1] =~ /\D/? $_[0] eq $_[1] : $_[0] == $_[1] },
-    '!=' => sub { return $_[1] =~ /\D/? $_[0] ne $_[1] : $_[0] != $_[1] },
-    '>'  => sub { return $_[1] =~ /\D/? $_[0] gt $_[1] : $_[0] > $_[1] },
-    '>=' => sub { return $_[1] =~ /\D/? $_[0] ge $_[1] : $_[0] >= $_[1] },
-    '<'  => sub { return $_[1] =~ /\D/? $_[0] lt $_[1] : $_[0] < $_[1] },
-    '<=' => sub { return $_[1] =~ /\D/? $_[0] le $_[1] : $_[0] <= $_[1] },
+    '='  => sub { return $_[1] =~ /\D/? lc $_[0] eq lc $_[1] : $_[0] == $_[1] },
+    '!=' => sub { return $_[1] =~ /\D/? lc $_[0] ne lc $_[1] : $_[0] != $_[1] },
+    '>'  => sub { return $_[1] =~ /\D/? lc $_[0] gt lc $_[1] : $_[0] > $_[1] },
+    '>=' => sub { return $_[1] =~ /\D/? lc $_[0] ge lc $_[1] : $_[0] >= $_[1] },
+    '<'  => sub { return $_[1] =~ /\D/? lc $_[0] lt lc $_[1] : $_[0] < $_[1] },
+    '<=' => sub { return $_[1] =~ /\D/? lc $_[0] le lc $_[1] : $_[0] <= $_[1] },
     'contains'         => sub { return index(lc $_[0], lc $_[1]) >= 0 },
     'not contains'     => sub { return index(lc $_[0], lc $_[1]) < 0 },
     'starts with'      => sub { return rindex(lc $_[0], lc $_[1], 0) == 0 },
@@ -108,8 +213,8 @@ sub ParseCode {
         error_cb => sub { push @errors, $_[0]; },
         operand_cb => sub {
             my $op = shift;
-            if ( $op =~ /^(!?)($re_exec_module)(?:{(.*)})?$/o ) {
-                return { module => $2, negative => $1, argument => $3 };
+            if ( $op =~ /^(!?)($re_exec_module)(?:{$re_module_argument})?$/o ) {
+                return { module => $2, negative => $1, argument => $parser->dq($3) };
             }
             elsif ( $op =~ /^($re_field)\s+($re_bin_op)\s+($re_value)$/o ) {
                 return { op => $2, lhs => $1, rhs => $3 };
@@ -118,7 +223,7 @@ sub ParseCode {
                 return { op => $2, lhs => $1 };
             }
             else {
-                push @errors, "'$op' is not a sub-condition Complex condition knows about";
+                push @errors, "'$op' is not a check 'Complex' condition knows about";
                 return undef;
             }
         },

commit e8ca7617f23b819ce8e6e9e79362d002cdb3564a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Nov 6 00:30:37 2009 +0700

    update inc/

diff --git a/inc/Module/Install/RTx.pm b/inc/Module/Install/RTx.pm
index 20a354b..a06cb6d 100644
--- a/inc/Module/Install/RTx.pm
+++ b/inc/Module/Install/RTx.pm
@@ -144,6 +144,7 @@ dropdb ::
         $has_etc{acl}++;
     }
     if ( -e 'etc/initialdata' ) { $has_etc{initialdata}++; }
+    if ( -e 'etc/upgrade' ) { $has_etc{upgrade}++; }
 
     $self->postamble("$postamble\n");
     unless ( $subdirs{'lib'} ) {
@@ -172,6 +173,14 @@ dropdb ::
 .
         $self->postamble("initdb ::\n$initdb\n");
         $self->postamble("initialize-database ::\n$initdb\n");
+
+        if ( $has_etc{upgrade} ) {
+            my $cmds = <<".";
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Minc::Module::Install -e"RTxInitDB(qw(upgrade))"
+.
+            $self->postamble("upgrade ::\n$cmds\n");
+        }
+        
     }
 }
 
@@ -188,4 +197,4 @@ sub RTxInit {
 
 __END__
 
-#line 302
+#line 311
diff --git a/inc/Module/Install/RTx/Factory.pm b/inc/Module/Install/RTx/Factory.pm
index 23ce911..cf9ad8e 100644
--- a/inc/Module/Install/RTx/Factory.pm
+++ b/inc/Module/Install/RTx/Factory.pm
@@ -30,7 +30,7 @@ sub RTxInitDB {
         "-I$lib_path",
         "$RT::SbinPath/rt-setup-database",
         "--action"      => $action,
-        "--datadir"     => "etc",
+        "--datadir"     => $action ne 'upgrade'? 'etc' : 'etc/upgrade',
         (($action eq 'insert') ? ("--datafile"    => "etc/initialdata") : ()),
         "--dba"         => $RT::DatabaseUser,
         "--prompt-for-dba-password" => ''

commit d2d7a80f30ede2a56e3e20ae2a83239f6439e883
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Nov 6 00:30:51 2009 +0700

    switch over RT::Extension::ColumnMap

diff --git a/lib/RT/Condition/Complex.pm b/lib/RT/Condition/Complex.pm
index 505d98d..7cabdb8 100644
--- a/lib/RT/Condition/Complex.pm
+++ b/lib/RT/Condition/Complex.pm
@@ -4,7 +4,7 @@ use 5.008003;
 use strict;
 use warnings;
 
-our $VERSION = '0.01';
+our $VERSION = '0.02';
 
 use base 'RT::Condition';
 
@@ -34,6 +34,24 @@ so you can re-use some of them many times in different combinations.
 to program in perl, some complex things have to be programmed, but now
 then can be reused.
 
+=head1 INSTALLATION
+
+Since version 0.02 this extension depends on L<RT::Extension::ColumnMap>,
+first of all install it.
+
+    # install RT::Extension::ColumnMap
+
+    perl Makefile.PL
+    make
+    make install
+
+    # in RT_SiteConfig.pm
+    Set( @Plugins,
+        ... more plugin ...
+        RT::Extension::ColumnMap
+        RT::Condition::Complex
+    );
+
 =head1 HOW TO USE
 
 You open the web interface in a browser, goto configuration, the global or
@@ -80,14 +98,12 @@ depends if constant is a string or number, string comparision is cases insenseti
 
 =head2 Fields
 
-At this moment not many fields are available, but it's easy to add more. Patches
-are welcome. Here is the list:
-
-=over 4
+Fields are based on L<RT::Extension::ColumnMap>. At this moment not many
+fields are available, but it's easy to add more. Patches
+for the L<RT::Extension::ColumnMap> are welcome.
 
-=item Type, Field, OldValue and NewValue - basic properties of the current transaction.
-
-=back
+The current transaction has no prefix, so 'Type' is type of the current
+transaction. 'Ticket.' is prefix for the current ticket.
 
 =head2 Constants
 
@@ -123,12 +139,14 @@ my $parser = new Parse::BooleanLogic;
 use Regexp::Common qw(delimited);
 my $re_quoted = qr{$RE{delimited}{-delim=>qq{\'\"}}{-esc=>'\\'}};
 
+use RT::Extension::ColumnMap;
+my $re_field = RT::Extension::ColumnMap->RE('column');
+
 my $re_exec_module = qr{[a-z][a-z0-9-]+}i;
 # module argument must be quoted as we don't know if it's quote to
 # protect from AND/OR words or argument of the condition should be
 # quoted
 my $re_module_argument = qr{$re_quoted};
-my $re_field = qr{[a-z.]+}i;
 my $re_value = qr{$re_quoted|[-+]?[0-9]+};
 my $re_bin_op = qr{!?=|[><]=?|(?:not\s+)?(?:contains|starts\s+with|ends\s+with)}i;
 my $re_un_op = qr{IS\s+(?:NOT\s+)?NULL|}i;
@@ -149,9 +167,6 @@ my %op_handler = (
     'is null'          => sub { return !(defined $_[0] && length $_[0]) },
     'is not null'      => sub { return   defined $_[0] && length $_[0] },
 );
-my %field_handler = (
-    ( map { my $m = $_; lc $m => sub { $_[1]->$m() } } qw(Type Field OldValue NewValue) ),
-);
 
 sub IsApplicable {
     my $self = shift;
@@ -172,9 +187,12 @@ my $solver = sub {
     my $cond = shift;
     my $self = $_[0];
     if ( $cond->{'op'} ) {
-        return $self->OpHandler($cond->{'op'})->(
-            $self->GetField( $cond->{'lhs'}, @_ ),
-            $self->GetValue( $cond->{'rhs'}, @_ )
+        return $self->SolveCondition(
+            Field       => $cond->{'lhs'},
+            Operator    => $cond->{'op'},
+            Value       => $self->GetValue( $cond->{'rhs'}, @_ ),
+            Transaction => $_[1],
+            Ticket      => $_[2],
         );
     }
     elsif ( $cond->{'module'} ) {
@@ -202,6 +220,24 @@ sub Solve {
     return $parser->solve( $tree, $solver, $self, $txn, $ticket );
 }
 
+sub SolveCondition {
+    my $self = shift;
+    my %args = @_;
+
+    my $op_handler = $self->OpHandler( $args{'Operator'} );
+    my $value = $args{'Value'};
+    my $checker = sub { return $op_handler->( $_[0], $value ) };
+    
+    return RT::Extension::ColumnMap->Check(
+        String => $args{'Field'},
+        Objects => {
+            '' => $args{'Transaction'},
+            'Ticket' => $args{'Ticket'},
+        },
+        Checker => $checker,
+    );
+}
+
 sub ParseCode {
     my $self = shift;
 
@@ -237,12 +273,6 @@ sub OpHandler {
     return $op_handler{ lc $op };
 }
 
-sub GetField {
-    my $self = shift;
-    my $field = shift;
-    return $field_handler{ lc $field }->(@_);
-}
-
 sub GetValue {
     my $self = shift;
     my $value = shift;
diff --git a/lib/RT/Condition/Complex/Test.pm b/lib/RT/Condition/Complex/Test.pm
index 5f38103..a540a9e 100644
--- a/lib/RT/Condition/Complex/Test.pm
+++ b/lib/RT/Condition/Complex/Test.pm
@@ -29,6 +29,7 @@ sub import {
     } else {
         $args{'testing'} = 'RT::Condition::Complex';
     }
+    unshift @{ $args{'requires'} }, 'RT::Extension::ColumnMap';
 
     $class->SUPER::import( %args );
     $class->export_to_level(1);
diff --git a/t/basics.t b/t/basics.t
index 8a65e02..a9279a9 100644
--- a/t/basics.t
+++ b/t/basics.t
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use RT::Condition::Complex::Test tests => 400;
+use RT::Condition::Complex::Test tests => 9;
 
 my $scrip;
 {

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list