finished the scheme matching and uri_for updates
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Chained.pm
index 2b27ec4..e29e5b5 100644 (file)
@@ -8,6 +8,7 @@ use Catalyst::ActionChain;
 use Catalyst::Utils;
 use URI;
 use Scalar::Util ();
+use Encode 2.21 'decode_utf8';
 
 has _endpoints => (
                    is => 'rw',
@@ -100,6 +101,9 @@ sub list {
         my @parts = (defined($args) ? (("*") x $args) : '...');
         my @parents = ();
         my $parent = "DUMMY";
+        my $extra  = $self->_list_extra_http_methods($endpoint);
+        my $consumes = $self->_list_extra_consumes($endpoint);
+        my $scheme = $self->_list_extra_scheme($endpoint);
         my $curr = $endpoint;
         while ($curr) {
             if (my $cap = $curr->list_extra_info->{CaptureArgs}) {
@@ -121,19 +125,28 @@ sub list {
         my @rows;
         foreach my $p (@parents) {
             my $name = "/${p}";
+
+            if (defined(my $extra = $self->_list_extra_http_methods($p))) {
+                $name = "${extra} ${name}";
+            }
             if (defined(my $cap = $p->list_extra_info->{CaptureArgs})) {
                 $name .= ' ('.$cap.')';
             }
+            if (defined(my $ct = $p->list_extra_info->{Consumes})) {
+                $name .= ' :'.$ct;
+            }
+            if (defined(my $s = $p->list_extra_info->{Scheme})) {
+                $scheme = uc $s;
+            }
+
             unless ($p eq $parents[0]) {
                 $name = "-> ${name}";
             }
-            if (defined(my $extra = $p->list_extra_info->{HTTP_METHODS})) {
-                $name .= ' ('.join(', ', @$extra).')';
-            }
             push(@rows, [ '', $name ]);
         }
-        push(@rows, [ '', (@rows ? "=> " : '')."/${endpoint}" ]);
-        $rows[0][0] = join('/', '', @parts) || '/';
+        push(@rows, [ '', (@rows ? "=> " : '').($extra ? "$extra " : ''). ($scheme ? "$scheme: ":'')."/${endpoint}". ($consumes ? " :$consumes":"" ) ]);
+        my @display_parts = map { $_ =~s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; decode_utf8 $_ } @parts;
+        $rows[0][0] = join('/', '', @display_parts) || '/';
         $paths->row(@$_) for @rows;
     }
 
@@ -142,6 +155,25 @@ sub list {
         if $has_unattached_actions;
 }
 
+sub _list_extra_http_methods {
+    my ( $self, $action ) = @_;
+    return unless defined $action->list_extra_info->{HTTP_METHODS};
+    return join(', ', @{$action->list_extra_info->{HTTP_METHODS}});
+
+}
+
+sub _list_extra_consumes {
+    my ( $self, $action ) = @_;
+    return unless defined $action->list_extra_info->{CONSUMES};
+    return join(', ', @{$action->list_extra_info->{CONSUMES}});
+}
+
+sub _list_extra_scheme {
+    my ( $self, $action ) = @_;
+    return unless defined $action->list_extra_info->{Scheme};
+    return uc $action->list_extra_info->{Scheme};
+}
+
 =head2 $self->match( $c, $path )
 
 Calls C<recurse_match> to see if a chain matches the C<$path>.
@@ -204,16 +236,16 @@ sub recurse_match {
         my @try_actions = @{$children->{$try_part}};
         TRY_ACTION: foreach my $action (@try_actions) {
             if (my $capture_attr = $action->attributes->{CaptureArgs}) {
-                $capture_attr ||= 0;
+                my $capture_count = $capture_attr->[0] || 0;
 
                 # Short-circuit if not enough remaining parts
-                next TRY_ACTION unless @parts >= $capture_attr->[0];
+                next TRY_ACTION unless @parts >= $capture_count;
 
                 my @captures;
                 my @parts = @parts; # localise
 
                 # strip CaptureArgs into list
-                push(@captures, splice(@parts, 0, $capture_attr->[0]));
+                push(@captures, splice(@parts, 0, $capture_count));
 
                 # check if the action may fit, depending on a given test by the app
                 if ($action->can('match_captures')) { next TRY_ACTION unless $action->match_captures($c, \@captures) }
@@ -277,6 +309,32 @@ Calls register_path for every Path attribute for the given $action.
 
 =cut
 
+sub _check_args_attr {
+    my ( $self, $action, $name ) = @_;
+
+    return unless exists $action->attributes->{$name};
+
+    if (@{$action->attributes->{$name}} > 1) {
+        Catalyst::Exception->throw(
+          "Multiple $name attributes not supported registering " . $action->reverse()
+        );
+    }
+    my $args = $action->attributes->{$name}->[0];
+    if (defined($args) and not (
+        Scalar::Util::looks_like_number($args) and
+        int($args) == $args and $args >= 0
+    )) {
+        require Data::Dumper;
+        local $Data::Dumper::Terse = 1;
+        local $Data::Dumper::Indent = 0;
+        $args = Data::Dumper::Dumper($args);
+        Catalyst::Exception->throw(
+          "Invalid $name($args) for action " . $action->reverse() .
+          " (use '$name' or '$name(<number>)')"
+        );
+    }
+}
+
 sub register {
     my ( $self, $c, $action ) = @_;
 
@@ -315,27 +373,24 @@ sub register {
         );
     }
 
-    $action->attributes->{PathPart} = [ $part ];
+    my $encoded_part = URI->new($part)->canonical;
+    $encoded_part =~ s{(?<=[^/])/+\z}{};
+
+    $action->attributes->{PathPart} = [ $encoded_part ];
 
-    unshift(@{ $children->{$part} ||= [] }, $action);
+    unshift(@{ $children->{$encoded_part} ||= [] }, $action);
 
     $self->_actions->{'/'.$action->reverse} = $action;
 
-    if (exists $action->attributes->{Args}) {
-        my $args = $action->attributes->{Args}->[0];
-        if (defined($args) and not (
-            Scalar::Util::looks_like_number($args) and
-            int($args) == $args
-        )) {
-            require Data::Dumper;
-            local $Data::Dumper::Terse = 1;
-            local $Data::Dumper::Indent = 0;
-            $args = Data::Dumper::Dumper($args);
-            Catalyst::Exception->throw(
-              "Invalid Args($args) for action " . $action->reverse() .
-              " (use 'Args' or 'Args(<number>)'"
-            );
-        }
+    foreach my $name (qw(Args CaptureArgs)) {
+        $self->_check_args_attr($action, $name);
+    }
+
+    if (exists $action->attributes->{Args} and exists $action->attributes->{CaptureArgs}) {
+        Catalyst::Exception->throw(
+          "Combining Args and CaptureArgs attributes not supported registering " .
+          $action->reverse()
+        );
     }
 
     unless ($action->attributes->{CaptureArgs}) {
@@ -677,9 +732,11 @@ of the endpoint of the chain, not on the chained actions way. The
 C<auto> actions will be run before the chain dispatching begins. In
 every other aspect, C<auto> actions behave as documented.
 
-The C<forward>ing to other actions does just what you would expect. But if
-you C<detach> out of a chain, the rest of the chain will not get called
-after the C<detach>.
+The C<forward>ing to other actions does just what you would expect. i.e.
+only the target action is run. The actions that that action is chained
+to are not run.
+If you C<detach> out of a chain, the rest of the chain will not get
+called after the C<detach>.
 
 =head2 match_captures