'under' and 'as' are parameterizable
Robert 'phaylon' Sedlacek [Fri, 18 Sep 2009 01:21:17 +0000 (03:21 +0200)]
Changes
lib/CatalystX/Declare/Context/StringParsing.pm [new file with mode: 0644]
lib/CatalystX/Declare/Keyword/Action.pm
t/022_parameterized_roles.t
t/lib/TestApp/Controller/Parameterized.pm
t/lib/TestApp/ControllerRole/Parameterized.pm

diff --git a/Changes b/Changes
index e209a38..8859452 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,6 +1,7 @@
 [0.011] ...
     - fixed broken isa RenderView test case
-    - parameterized roles now available in general
+    - parameterized roles now available
+    - path parts and chain base actions are parameterizable
 
 [0.010] Sun Sep 13 15:52:01 CEST 2009
     - Fix Compat with MooseX::Method::Signatures >=0.22 ( KENTNL )
diff --git a/lib/CatalystX/Declare/Context/StringParsing.pm b/lib/CatalystX/Declare/Context/StringParsing.pm
new file mode 100644 (file)
index 0000000..77ed7d2
--- /dev/null
@@ -0,0 +1,68 @@
+use MooseX::Declare;
+
+role CatalystX::Declare::Context::StringParsing {
+
+    use Devel::Declare;
+
+    after inject_code_parts_here (@args) {
+#        print "INJECT " . $self->get_linestr . "\n";
+        #print "BLOCK $_\n" for @args;
+    }
+
+    after inject_if_block (@args) {
+#        print "BLOCK " . $self->get_linestr . "\n";
+        #print "BLOCK $_\n" for @args;
+    }
+
+    method rest_of_line {
+
+        $self->skipspace;
+
+        my $linestr = $self->get_linestr;
+        my $left    = substr $linestr, $self->offset;
+
+        return $left;
+    }
+
+    method strip_from_linestr (Int $chars) {
+
+        my $linestr = $self->get_linestr;
+        substr($linestr, $self->offset, $chars) = '';
+        $self->set_linestr($linestr);
+    }
+
+    method get_string {
+
+        my $left = $self->rest_of_line;
+
+        if ($left =~ /^"/ and my $num = Devel::Declare::toke_scan_str $self->offset) {
+
+            my $found = Devel::Declare::get_lex_stuff;
+            Devel::Declare::clear_lex_stuff;
+            
+            $self->strip_from_linestr($num);
+            
+            return qq{"$found"};
+        }
+        else {
+            return $self->get_scalar;
+        }
+    }
+
+    method get_scalar {
+        
+        my $left = $self->rest_of_line;
+
+        if ($left =~ s/^ ( \$ [a-z_] [a-z0-9_]* ) //ix) {
+
+            my $found = $1;
+
+            $self->strip_from_linestr( length $found );
+
+            return qq{"$found"};
+        }
+        else {
+            return undef;
+        }
+    }
+}
index b78bad9..29a5b7d 100644 (file)
@@ -1,8 +1,7 @@
 use MooseX::Declare;
 use MooseX::Role::Parameterized ();
 
-class CatalystX::Declare::Keyword::Action
-    with MooseX::Declare::Syntax::KeywordHandling {
+class CatalystX::Declare::Keyword::Action {
 
 
     use Carp                qw( croak );
@@ -19,6 +18,7 @@ class CatalystX::Declare::Keyword::Action
     use constant UNDER_STACK    => '@CatalystX::Declare::SCOPE::UNDER_STACK';
 
     use aliased 'CatalystX::Declare::Action::CatchValidationError';
+    use aliased 'CatalystX::Declare::Context::StringParsing';
     use aliased 'MooseX::Method::Signatures::Meta::Method';
     use aliased 'MooseX::MethodAttributes::Role::Meta::Method', 'AttributeRole';
     use aliased 'MooseX::MethodAttributes::Role::Meta::Role',   'AttributeMetaRole';
@@ -111,6 +111,8 @@ class CatalystX::Declare::Keyword::Action
                 for qw( Args CaptureArgs Chained Signature Private );
         }
 
+        $self->_inject_attributes($ctx, \%attributes);
+
         if ($ctx->peek_next_char eq '{') {
             $ctx->inject_if_block($ctx->scope_injector_call . $method->injectable_code);
         }
@@ -122,22 +124,52 @@ class CatalystX::Declare::Keyword::Action
             );
         }
 
-        my @attributes;
-        for my $attr (keys %attributes) {
-            push @attributes, 
-                map { sprintf '%s%s', $attr, defined($_) ? sprintf('(%s)', $_) : '' }
-                    (ref($attributes{ $attr }) eq 'ARRAY') 
-                    ? @{ $attributes{ $attr } }
-                    : $attributes{ $attr };
-        }
+        my $compile_attrs = sub {
+            my $attributes = shift;;
+            my @attributes;
+
+            for my $attr (keys %$attributes) {
+                my $value = $attributes->{ $attr };
+
+                next if $attr eq 'Chained' and $value eq UNDER_VAR;
+
+#                $value = sprintf "'%s'", $value
+#                    if grep { $attr eq $_ } qw( Chained PathPart );
+
+                push @attributes, 
+                    map { sprintf '%s%s', $attr, defined($_) ? sprintf('(%s)', $_) : '' }
+                        (ref($value) eq 'ARRAY') 
+                        ? @$value
+                        : $value;
+            }
+
+            return \@attributes;
+        };
 
-        return $ctx->shadow(sub (&) {
+        return $ctx->shadow(sub {
             my $class = caller;
+            my $attrs = shift;
             my $body  = shift;
 
+            $body = $attrs and $attrs = {}
+                if ref $attrs eq 'CODE';
+
+            delete $attrs->{Chained}
+                unless defined $attrs->{Chained};
+
+            defined($attrs->{ $_ }) and $attrs->{ $_ } = sprintf "'%s'", $attrs->{ $_ }
+                for qw( Chained PathPart );
+
+#            pp \%attributes;
+#            pp $attrs;
+            my %full_attrs = (%attributes, %$attrs);
+#            pp \%full_attrs;
+            my $compiled_attrs = $compile_attrs->(\%full_attrs);
+#            pp $compiled_attrs;
+
             my $real_method = $method->reify(
                 actual_body => $body,
-                attributes  => \@attributes,
+                attributes  => $compiled_attrs,
             );
 
             if ($modifier) {
@@ -150,7 +182,7 @@ class CatalystX::Declare::Keyword::Action
                     my ($meta) = @_;
 
                     $meta->add_method($name, $real_method);
-                    $meta->register_method_attributes($meta->name->can($real_method->name), \@attributes);
+                    $meta->register_method_attributes($meta->name->can($real_method->name), $compiled_attrs);
                 };
 
                 if ($ctx->stack->[-1] and $ctx->stack->[-1]->is_parameterized) {
@@ -232,10 +264,7 @@ class CatalystX::Declare::Keyword::Action
         $attrs->{Action}    = [];
 
         push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, CatchValidationError;
-
-        if (defined $CatalystX::Declare::SCOPE::UNDER) {
-            $attrs->{Chained} ||= $CatalystX::Declare::SCOPE::UNDER;
-        }
+        $attrs->{Chained} ||= UNDER_VAR;
 
         return unless $populator;
         return $populator;
@@ -270,26 +299,20 @@ class CatalystX::Declare::Keyword::Action
 
     method _handle_under_option (Object $ctx, HashRef $attrs) {
 
-        my $target = $self->_strip_actionpath($ctx);
+        my $target = $self->_strip_actionpath($ctx, interpolate => 1);
         $ctx->skipspace;
 
         if ($ctx->peek_next_char eq '{' and $self->identifier eq 'under') {
             $ctx->inject_if_block(
-                sprintf '%s; BEGIN { push %s, %s; %s = qq(%s) };',
-                    $ctx->scope_injector_call(
-                        sprintf ';BEGIN { %s = pop %s };', 
-                            UNDER_VAR,
-                            UNDER_STACK,
-                    ),
-                    UNDER_STACK,
-                    UNDER_VAR,
+                $ctx->scope_injector_call .
+                sprintf ';local %s = %s;',
                     UNDER_VAR,
                     $target,
             );
             return STOP_PARSING;
         }
 
-        $attrs->{Chained} = "'$target'";
+        $attrs->{Chained} = $target;
 
         return sub {
             my $method = shift;
@@ -309,8 +332,8 @@ class CatalystX::Declare::Keyword::Action
 
         $ctx->skipspace;
 
-        my $path = $self->_strip_actionpath($ctx);
-        $attrs->{PathPart} = "'$path'";
+        my $path = $self->_strip_actionpath($ctx, interpolate => 1);
+        $attrs->{PathPart} = $path;
 
         return;
     }
@@ -331,26 +354,52 @@ class CatalystX::Declare::Keyword::Action
         return 0;
     }
 
-    method _strip_actionpath (Object $ctx) {
+    method _inject_attributes (Object $ctx, HashRef $attrs) {
+
+        my @inject = qw( Chained PathPart );
+
+        my $code = sprintf ' +{ %s }, sub ',
+            join ', ',
+            map  { (@$_) }
+#            map  { [$_->[0], sprintf '"%s"', $_->[1]] }
+#            map  { length(  $_->[1] ) ? $_ : [$_->[0], "''"] }
+            map  { defined( $_->[1] ) ? $_ : [$_->[0], 'undef'] }
+            map  { [pp($_), $attrs->{ $_ }] }
+            grep { defined $attrs->{ $_ } }
+            @inject;
+
+        $ctx->inject_code_parts_here($code);
+        $ctx->inc_offset(length $code);
+    }
+
+    method _strip_actionpath (Object $ctx, :$interpolate?) {
 
         $ctx->skipspace;
         my $linestr = $ctx->get_linestr;
         my $rest    = substr($linestr, $ctx->offset);
+        my $interp  = sub { $interpolate ? "'$_[0]'" : $_[0] };
 
         if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
             substr($linestr, $ctx->offset, length($1)) = '';
             $ctx->set_linestr($linestr);
-            return $1;
+            return $interp->($1);
         }
         elsif ($rest =~ /^ ' ( (?:[.:;,_a-z0-9]|\/)* ) ' /ix) {
             substr($linestr, $ctx->offset, length($1) + 2) = '';
             $ctx->set_linestr($linestr);
-            return $1;
+            return $interp->($1);
+        }
+        elsif ($interpolate and my $str = $ctx->get_string) {
+            return $str;
         }
         else {
             croak "Invalid syntax for action path: $rest";
         }
     }
+    
+    with 'MooseX::Declare::Syntax::KeywordHandling';
+
+    around context_traits { $self->$orig, StringParsing }
 }
 
 __END__
index 5ddab0a..6cdadc8 100644 (file)
@@ -9,5 +9,8 @@ use Test::More;
 use Catalyst::Test 'TestApp';
 
 is get('/param/greet'), 'foo:foo', 'parameterized role was consumed correctly';
+is get('/param/somebase/dynabase'), 'under somebase', 'dynamic base via parameter';
+is get('/param/somebase/somepart'), 'under somebase as somepart', 'dynamic base and path part via parameter';
+is get('/param/somebase/scoped'), 'scoped under somebase', 'dynamic base in under scope via parameter';
 
 done_testing;
index 9a52956..84312df 100644 (file)
@@ -1,7 +1,13 @@
 use CatalystX::Declare;
 
 controller TestApp::Controller::Parameterized {
-    with 'TestApp::ControllerRole::Parameterized' => { message => 'foo' };
+    with 'TestApp::ControllerRole::Parameterized' => { 
+        message => 'foo',
+        base    => 'somebase',
+        part    => 'somepart',
+    };
 
     action base under '/' as 'param';
+
+    action somebase under base;
 }
index 6248da2..10ffc62 100644 (file)
@@ -1,10 +1,25 @@
 use CatalystX::Declare;
 
-controller_role TestApp::ControllerRole::Parameterized (Str :$message) {
+controller_role TestApp::ControllerRole::Parameterized (Str :$message, Str :$base, Str :$part) {
 
     method get_message { $message }
 
     final action greet under base {
         $ctx->response->body( join ':', $self->get_message, $message );
     }
+
+    final action dynabase under "$base" {
+        $ctx->response->body( "under $base" );
+    }
+
+    final action dynapart under "$base" as $part {
+        $ctx->response->body( "under $base as $part" );
+    }
+
+    under $base {
+
+        final action scoped {
+            $ctx->response->body( "scoped under $base" );
+        }
+    }
 }