merged from master to sync release
John Napiorkowski [Wed, 25 Mar 2015 20:24:33 +0000 (15:24 -0500)]
1  2 
Changes
lib/Catalyst.pm
lib/Catalyst/Controller.pm
lib/Catalyst/DispatchType/Chained.pm
lib/Catalyst/Runtime.pm

diff --combined Changes
+++ b/Changes
@@@ -1,11 -1,19 +1,25 @@@
  # This file documents the revision history for Perl extension Catalyst.
  
 +5.90089_001 - TBA
 +  - New Feature: Type Constraints on Args/CapturArgs.  ALlows you to declare
 +    a Moose, MooseX::Types or Type::Tiny named constraint on your Arg or 
 +    CaptureArg.
 +  - New top level document on Route matching. (Catalyst::RouteMatching).
 +
+ 5.90085 - 2015-03-25
+   - Small change to Catalyst::Action to prevent autovivication of Args value (dim1++)
+   - Minor typo fixes (Abraxxa++)
+   - Make sure than when using chained actions and when more than one action
+     matches the same path specification AND has Args(0), that we follow the
+     "in a tie, the last action defined wins" rule.  There is a small chance
+     this is a breaking change for you.  See Catalyst::Upgrading for more.
+     You may use the application configuration setting "use_chained_args_0_special_case"
+     to disable this new behavior, if you must for back-compat reasons.
+   - Added PATCH HTTP Method action attribute shortcut.
+   - Several new configuration options aimed to give improved backwards compatibility
+     for when your URL query parameters or keywords have non UTF-8 encodings.
+     See Catalyst::Upgrading.
  5.90084 - 2015-02-23
    - Small change to the way body parameters are created in order to prevent
      trying to create parameters twice.
diff --combined lib/Catalyst.pm
@@@ -129,7 -129,7 +129,7 @@@ __PACKAGE__->stats_class('Catalyst::Sta
  __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
  
  # Remember to update this in Catalyst::Runtime as well!
 -our $VERSION = '5.90085';
 +our $VERSION = '5.90089_001';
  $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
  
  sub import {
@@@ -3904,6 -3904,51 +3904,51 @@@ parameter to true
  
  =item *
  
+ C<do_not_decode_query>
+ If true, then do not try to character decode any wide characters in your
+ request URL query or keywords.  Most readings of the relevent specifications
+ suggest these should be UTF-* encoded, which is the default that L<Catalyst>
+ will use, hwoever if you are creating a lot of URLs manually or have external
+ evil clients, this might cause you trouble.  If you find the changes introduced
+ in Catalyst version 5.90080+ break some of your query code, you may disable 
+ the UTF-8 decoding globally using this configuration.
+ This setting takes precedence over C<default_query_encoding> and
+ C<decode_query_using_global_encoding>
+ =item *
+ C<default_query_encoding>
+ By default we decode query and keywords in your request URL using UTF-8, which
+ is our reading of the relevent specifications.  This setting allows one to
+ specify a fixed value for how to decode your query.  You might need this if
+ you are doing a lot of custom encoding of your URLs and not using UTF-8.
+ This setting take precedence over C<decode_query_using_global_encoding>.
+ =item *
+ C<decode_query_using_global_encoding>
+ Setting this to true will default your query decoding to whatever your
+ general global encoding is (the default is UTF-8).
+ =item *
+ C<use_chained_args_0_special_case>
+ In older versions of Catalyst, when more than one action matched the same path
+ AND all those matching actions declared Args(0), we'd break the tie by choosing
+ the first action defined.  We now normalized how Args(0) works so that it
+ follows the same rule as Args(N), which is to say when we need to break a tie
+ we choose the LAST action defined.  If this breaks your code and you don't
+ have time to update to follow the new normalized approach, you may set this
+ value to true and it will globally revert to the original chaining behavior.
+ =item *
  C<psgi_middleware> - See L<PSGI MIDDLEWARE>.
  
  =item *
@@@ -550,6 -550,7 +550,7 @@@ sub _parse_PUT_attr     { Method => 'PU
  sub _parse_DELETE_attr  { Method => 'DELETE'  }
  sub _parse_OPTIONS_attr { Method => 'OPTIONS' }
  sub _parse_HEAD_attr    { Method => 'HEAD'    }
+ sub _parse_PATCH_attr  { Method => 'PATCH'  }
  
  sub _expand_role_shortname {
      my ($self, @shortnames) = @_;
@@@ -786,29 -787,7 +787,29 @@@ Like L</Regex> but scoped under the nam
  
  =head2 CaptureArgs
  
 -Please see L<Catalyst::DispatchType::Chained>
 +Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two
 +allowed) or you can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny>
 +named constraint such as CaptureArgs(Int,Str) would require two args with
 +the first being a Integer and the second a string.  You may declare your own
 +custom type constraints and import them into the controller namespace:
 +
 +    package MyApp::Controller::Root;
 +
 +    use Moose;
 +    use MooseX::MethodAttributes;
 +    use MyApp::Types qw/Int/;
 +
 +    extends 'Catalyst::Controller';
 +
 +    sub chain_base :Chained(/) CaptureArgs(1) { }
 +
 +      sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { }
 +
 +      sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
 +
 +See L<Catalyst::RouteMatching> for more.
 +
 +Please see L<Catalyst::DispatchType::Chained> for more.
  
  =head2 ActionClass
  
@@@ -858,38 -837,6 +859,38 @@@ When used with L</Path> indicates the n
  the path.  However if no Args value is set, assumed to 'slurp' all
  remaining path pars under this namespace.
  
 +Allowed values for Args is a single integer (Args(2), meaning two allowed) or you
 +can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny> named constraint such
 +as Args(Int,Str) would require two args with the first being a Integer and the
 +second a string.  You may declare your own custom type constraints and import
 +them into the controller namespace:
 +
 +    package MyApp::Controller::Root;
 +
 +    use Moose;
 +    use MooseX::MethodAttributes;
 +    use MyApp::Types qw/Tuple Int Str StrMatch UserId/;
 +
 +    extends 'Catalyst::Controller';
 +
 +    sub user :Local Args(UserId) {
 +      my ($self, $c, $int) = @_;
 +    }
 +
 +    sub an_int :Local Args(Int) {
 +      my ($self, $c, $int) = @_;
 +    }
 +
 +    sub many_ints :Local Args(ArrayRef[Int]) {
 +      my ($self, $c, @ints) = @_;
 +    }
 +
 +    sub match :Local Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
 +      my ($self, $c, $int) = @_;
 +    }
 +
 +See L<Catalyst::RouteMatching> for more.
 +
  =head2 Consumes('...')
  
  Matches the current action against the content-type of the request.  Typically
@@@ -98,7 -98,7 +98,7 @@@ sub list 
                             @{ $self->_endpoints }
                    ) {
          my $args = $endpoint->list_extra_info->{Args};
 -        my @parts = (defined($args) ? (("*") x $args) : '...');
 +        my @parts = (defined($endpoint->attributes->{Args}[0]) ? (("*") x $args) : '...');
          my @parents = ();
          my $parent = "DUMMY";
          my $extra  = $self->_list_extra_http_methods($endpoint);
                  $name = "${extra} ${name}";
              }
              if (defined(my $cap = $p->list_extra_info->{CaptureArgs})) {
 -                $name .= ' ('.$cap.')';
 +                if($p->has_captures_constraints) {
 +                  my $tc = join ',', @{$p->captures_constraints};
 +                  $name .= " ($tc)";
 +                } else {
 +                  $name .= " ($cap)";
 +                }
              }
              if (defined(my $ct = $p->list_extra_info->{Consumes})) {
                  $name .= ' :'.$ct;
              }
              push(@rows, [ '', $name ]);
          }
 +
 +        if($endpoint->has_args_constraints) {
 +          my $tc = join ',', @{$endpoint->args_constraints};
 +          $endpoint .= " ($tc)";
 +        } else {
 +          $endpoint .= defined($endpoint->attributes->{Args}[0]) ? " ($args)" : " (...)";
 +        }
          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) || '/';
@@@ -248,7 -236,7 +248,7 @@@ sub recurse_match 
          my @try_actions = @{$children->{$try_part}};
          TRY_ACTION: foreach my $action (@try_actions) {
              if (my $capture_attr = $action->attributes->{CaptureArgs}) {
 -                my $capture_count = $capture_attr->[0] || 0;
 +                my $capture_count = $action->number_of_captures|| 0;
  
                  # Short-circuit if not enough remaining parts
                  next TRY_ACTION unless @parts >= $capture_count;
                  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) }
 +                next TRY_ACTION unless $action->match_captures($c, \@captures);
  
                  # try the remaining parts against children of this action
                  my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
                  #    The current best action might also be Args(0),
                  #    but we couldn't chose between then anyway so we'll take the last seen
  
-                 if (!$best_action                       ||
+                 if (
+                     !$best_action                       ||
                      @parts < @{$best_action->{parts}}   ||
-                     (!@parts && defined($args_attr) && $args_attr eq "0")){
+                     (
+                         !@parts && 
+                         defined($args_attr) && 
+                         (
+                             $args_attr eq "0" &&
+                             (
+                               ($c->config->{use_chained_args_0_special_case}||0) || 
+                                 (
+                                   exists($best_action->{args_attr}) && defined($best_action->{args_attr}) ?
+                                   ($best_action->{args_attr} ne 0) : 1
+                                 )
+                             )
+                         )
+                     )
+                 ){
                      $best_action = {
                          actions => [ $action ],
                          captures=> [],
                          parts   => \@parts,
+                         args_attr => $args_attr,
                          n_pathparts => scalar(@pathparts),
                      };
                  }
@@@ -321,6 -325,32 +337,6 @@@ Calls register_path for every Path attr
  
  =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 ) = @_;
  
  
      $self->_actions->{'/'.$action->reverse} = $action;
  
 -    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 " .
@@@ -399,15 -433,11 +415,15 @@@ sub uri_for_action 
      my @captures = @$captures;
      my $parent = "DUMMY";
      my $curr = $action;
 +    # If this is an action chain get the last action in the chain
 +    if($curr->can('chain') ) {
 +      $curr = ${$curr->chain}[-1];
 +    }
      while ($curr) {
 -        if (my $cap = $curr->attributes->{CaptureArgs}) {
 -            return undef unless @captures >= ($cap->[0]||0); # not enough captures
 -            if ($cap->[0]) {
 -                unshift(@parts, splice(@captures, -$cap->[0]));
 +        if (my $cap = $curr->number_of_captures) {
 +            return undef unless @captures >= $cap; # not enough captures
 +            if ($cap) {
 +                unshift(@parts, splice(@captures, -$cap));
              }
          }
          if (my $pp = $curr->attributes->{PathPart}) {
@@@ -691,28 -721,6 +707,28 @@@ An action that is part of a chain (tha
  attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
  as a chain end.
  
 +Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two
 +allowed) or you can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny>
 +named constraint such as CaptureArgs(Int,Str) would require two args with
 +the first being a Integer and the second a string.  You may declare your own
 +custom type constraints and import them into the controller namespace:
 +
 +    package MyApp::Controller::Root;
 +
 +    use Moose;
 +    use MooseX::MethodAttributes;
 +    use MyApp::Types qw/Int/;
 +
 +    extends 'Catalyst::Controller';
 +
 +    sub chain_base :Chained(/) CaptureArgs(1) { }
 +
 +      sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { }
 +
 +      sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
 +
 +See L<Catalyst::RouteMatching> for more.
 +
  =item Args
  
  By default, endpoints receive the rest of the arguments in the path. You
diff --combined lib/Catalyst/Runtime.pm
@@@ -1,4 -1,4 +1,3 @@@
--package Catalyst::Runtime;
  
  use strict;
  use warnings;
@@@ -7,7 -7,7 +6,7 @@@ BEGIN { require 5.008003; 
  
  # Remember to update this in Catalyst as well!
  
 -our $VERSION = '5.90085';
 +our $VERSION = '5.90089_001';
  $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
  
  =head1 NAME