[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