Merge branch 'abort-chain-doc-and-test' of https://github.com/melmothx/catalyst-runti...
John Napiorkowski [Mon, 1 May 2017 14:24:39 +0000 (09:24 -0500)]
14 files changed:
.travis.yml
Changes
Makefile.PL
lib/Catalyst.pm
lib/Catalyst/Component/ContextClosure.pm
lib/Catalyst/Controller.pm
lib/Catalyst/DispatchType/Chained.pm
lib/Catalyst/Dispatcher.pm
lib/Catalyst/Exception/Basic.pm
lib/Catalyst/Exception/Interface.pm
lib/Catalyst/Runtime.pm
lib/Catalyst/ScriptRole.pm
t/evil_stash.t [new file with mode: 0644]
t/relative_root_action_for_bug.t [new file with mode: 0644]

index b3bb3b9..9a04424 100644 (file)
@@ -1,6 +1,8 @@
 language: perl
 sudo: false
 perl:
+   - "5.24"
+   - "5.22"
    - "5.20"
    - "5.18"
    - "5.16"
@@ -51,7 +53,7 @@ script:
    - cpanm --test-only --metacpan Catalyst::Component::InstancePerContext
    - cpanm --test-only --metacpan Catalyst::Plugin::Session
    - cpanm --test-only --metacpan Catalyst::Plugin::Session::State::Cookie
-   - cpanm --test-only --metacpan Catalyst::Plugin::Static::Simple
+   - cpanm --test-only --verbose --metacpan Catalyst::Plugin::Static::Simple
    - cpanm --test-only --metacpan Catalyst::Plugin::ConfigLoader
    - cpanm --test-only --metacpan Catalyst::Plugin::ConfigLoader
    - cpanm --test-only --metacpan Catalyst::Authentication::Credential::HTTP
diff --git a/Changes b/Changes
index 5aa6778..bc26a08 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,29 @@
 # This file documents the revision history for Perl extension Catalyst.
 
+5.90115 - 2017-05-01
+  - fixes for silent bad behavior in Catalyst::ScriptRole and 'ensure_class_loaded'
+    (hobbs++)
+  - do not require MXRWO if Moose is new enough to have cored it (ether++)
+  - documentation improvements (ether++)
+  - Encoding documentation improvements (colinnewell++)
+
+5.90114 - 2016-12-19
+  - Fixed regression introduced in the last version (5.90113) which caused 
+    application to hang when the action private name contained a string
+    like 'foo/bar..html'.  If you are running 5.90113 you should consider this
+    a required update.
+  - Tweaked travis CI script.
+
+5.90113 - 2016-12-15
+  - Fixed issue with $controller->action_for when targeting an action in
+    a namespace nested inside the current controller and the current controller
+    is a 'root' controller.
+  - Enhanced $controller->action_for so that you can reference the 'parent'
+    controller via relative path (eg ->action_for('../foo')).
+  - Backcompat fix for people that made the mistake of doing $c->{stash}
+  - Sort controllers in setup_actions so cross-controller precedence is
+    consistent.
+
 5.90112 - 2016-07-25
   - Spelling fixes from Debian group.
   - Fixed regression introduced in last release that caused the code to crap out
index 68dd556..5a47d3e 100644 (file)
@@ -34,7 +34,7 @@ requires 'Class::Load' => '0.12';
 requires 'Data::OptList';
 requires 'Moose' => '1.03';
 requires 'MooseX::MethodAttributes::Role::AttrContainer::Inheritable' => '0.24';
-requires 'MooseX::Role::WithOverloading' => '0.09';
+requires 'MooseX::Role::WithOverloading' => '0.09' unless can_use('Moose', '2.1300');
 requires 'Carp' => '1.25';
 requires 'Class::C3::Adopt::NEXT' => '0.07';
 requires 'CGI::Simple::Cookie' => '1.109';
@@ -142,6 +142,7 @@ resources(
     'license',    => 'http://dev.perl.org/licenses/',
     'homepage',   => 'http://dev.catalyst.perl.org/',
     # r/w: catagits@git.shadowcat.co.uk:Catalyst-Runtime.git
+    # web: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits/Catalyst-Runtime.git;a=summary
     'repository', => 'git://git.shadowcat.co.uk/catagits/Catalyst-Runtime.git',
 );
 
index 48d3456..dbdcdee 100644 (file)
@@ -51,6 +51,7 @@ use Catalyst::Middleware::Stash;
 use Plack::Util;
 use Class::Load 'load_class';
 use Encode 2.21 'decode_utf8', 'encode_utf8';
+use Scalar::Util;
 
 BEGIN { require 5.008003; }
 
@@ -204,7 +205,7 @@ sub composed_stats_class {
 __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
 
 # Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90112';
+our $VERSION = '5.90114';
 $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
 
 sub import {
@@ -474,7 +475,7 @@ or stash it like so:
 
 and access it from the stash.
 
-Keep in mind that the C<end> method used is that of the caller action. So a C<$c-E<gt>detach> inside a forwarded action would run the C<end> method from the original action requested.
+Keep in mind that the C<end> method used is that of the caller action. So a C<< $c->detach >> inside a forwarded action would run the C<end> method from the original action requested.
 
 =cut
 
@@ -2321,6 +2322,10 @@ sub finalize_encoding {
       (defined($res->body)) and
       (ref(\$res->body) eq 'SCALAR')
     ) {
+        # if you are finding yourself here and your body is already encoded correctly
+        # and you want to turn this off, use $c->clear_encoding to prevent encoding
+        # at this step, or set encoding to undef in the config to do so for the whole
+        # application.  See the ENCODING documentaiton for better notes.
         $c->res->body( $c->encoding->encode( $c->res->body, $c->_encode_check ) );
 
         # Set the charset if necessary.  This might be a bit bonkers since encodable response
@@ -2490,6 +2495,8 @@ sub prepare {
     };
 
     $c->log_request;
+    $c->{stash} = $c->stash;
+    Scalar::Util::weaken($c->{stash});
 
     return $c;
 }
@@ -4764,6 +4771,11 @@ the encoding configuration to undef.
 
 This is recommended for temporary backwards compatibility only.
 
+To turn it off for a single request use the L<clear_encoding>
+method to turn off encoding for this request.  This can be useful
+when you are setting the body to be an arbitrary block of bytes,
+especially if that block happens to be a block of UTF8 text.
+
 Encoding is automatically applied when the content-type is set to
 a type that can be encoded.  Currently we encode when the content type
 matches the following regular expression:
@@ -4886,7 +4898,7 @@ andrewalker: AndrĂ© Walker <andre@cpan.org>
 
 Andrew Bramble
 
-Andrew Ford E<lt>A.Ford@ford-mason.co.ukE<gt>
+Andrew Ford <A.Ford@ford-mason.co.uk>
 
 Andrew Ruthven
 
@@ -4910,7 +4922,7 @@ Danijel Milicevic C<me@danijel.de>
 
 davewood: David Schmidt <davewood@cpan.org>
 
-David Kamholz E<lt>dkamholz@cpan.orgE<gt>
+David Kamholz <dkamholz@cpan.org>
 
 David Naughton, C<naughton@umn.edu>
 
index 9c3139a..b25cc46 100644 (file)
@@ -67,7 +67,7 @@ L<CatalystX::LeakChecker>
 
 =head1 AUTHOR
 
-Florian Ragwitz E<lt>rafl@debian.orgE<gt>
+Florian Ragwitz <rafl@debian.org>
 
 =end stopwords
 
index ad88a51..fbab60f 100644 (file)
@@ -662,10 +662,25 @@ arguments, when it is instantiated:
 From L<Catalyst::Component::ApplicationAttribute>, stashes the application
 instance as $self->_application.
 
-=head2 $self->action_for('name')
+=head2 $self->action_for($action_name)
 
-Returns the Catalyst::Action object (if any) for a given method name
-in this component.
+Returns the Catalyst::Action object (if any) for a given action in this
+controller or relative to it.  You may refer to actions in controllers
+nested under the current controllers namespace, or in controllers 'up'
+from the current controller namespace.  For example:
+
+    package MyApp::Controller::One::Two;
+    use base 'Catalyst::Controller';
+
+    sub foo :Local {
+      my ($self, $c) = @_;
+      $self->action_for('foo'); # action 'foo' in Controller 'One::Two'
+      $self->action_for('three/bar'); # action 'bar' in Controller 'One::Two::Three'
+      $self->action_for('../boo'); # action 'boo' in Controller 'One'
+    }
+
+This returns 'undef' if there is no action matching the requested action
+name (after any path normalization) so you should check for this as needed.
 
 =head2 $self->action_namespace($c)
 
index cb1dfed..2eb0bc1 100644 (file)
@@ -715,7 +715,7 @@ parts of the path (separated by C</>) this action wants to capture as
 its arguments. If it doesn't expect any, just specify
 C<:CaptureArgs(0)>.  The captures get passed to the action's C<@_> right
 after the context, but you can also find them as array references in
-C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
+C<< $c->request->captures->[$level] >>. The C<$level> is the
 level of the action in the chain that captured the parts of the path.
 
 An action that is part of a chain (that is, one that has a C<:Chained>
@@ -764,7 +764,7 @@ of path parts after the endpoint.
 
 Just as with C<:CaptureArgs>, the arguments get passed to the action in
 C<@_> after the context object. They can also be reached through
-C<$c-E<gt>request-E<gt>arguments>.
+C<< $c->request->arguments >>.
 
 You should see 'Args' in L<Catalyst::Controller> for more details on using
 type constraints in your Args declarations.
index d64b17e..cf98256 100644 (file)
@@ -407,9 +407,14 @@ sub prepare_action {
       if ( $c->debug && @args );
 }
 
-=head2 $self->get_action( $action, $namespace )
+=head2 $self->get_action( $action_name, $namespace )
 
-returns a named action from a given namespace.
+returns a named action from a given namespace.  C<$action_name>
+may be a relative path on that C<$namespace> such as
+
+    $self->get_action('../bar', 'foo/baz');
+
+In which case we look for the action at 'foo/bar'.
 
 =cut
 
@@ -419,17 +424,22 @@ sub get_action {
 
     $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
 
-    return $self->_action_hash->{"${namespace}/${name}"};
+    return $self->get_action_by_path("${namespace}/${name}");
 }
 
 =head2 $self->get_action_by_path( $path );
 
 Returns the named action by its full private path.
 
+This method performs some normalization on C<$path> so that if
+it includes '..' it will do the right thing (for example if
+C<$path> is '/foo/../bar' that is normalized to '/bar'.
+
 =cut
 
 sub get_action_by_path {
     my ( $self, $path ) = @_;
+    $path =~s/[^\/]+\/\.\.\/// while $path=~m/[^\/]+\/\.\.\//;
     $path =~ s/^\///;
     $path = "/$path" unless $path =~ /\//;
     $self->_action_hash->{$path};
@@ -620,7 +630,7 @@ sub setup_actions {
       $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
     @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
 
-    foreach my $comp ( values %{ $c->components } ) {
+    foreach my $comp ( map @{$_}{sort keys %$_}, $c->components ) {
         $comp = $comp->() if ref($comp) eq 'CODE';
         $comp->register_actions($c) if $comp->can('register_actions');
     }
index 713bb5f..253b6a8 100644 (file)
@@ -1,6 +1,8 @@
 package Catalyst::Exception::Basic;
 
-use MooseX::Role::WithOverloading;
+use Moose::Role;
+use if !eval { require Moose; Moose->VERSION('2.1300') },
+    'MooseX::Role::WithOverloading';
 use Carp;
 use namespace::clean -except => 'meta';
 
index aae67f2..73e4cc0 100644 (file)
@@ -1,6 +1,8 @@
 package Catalyst::Exception::Interface;
 
-use MooseX::Role::WithOverloading;
+use Moose::Role;
+use if !eval { require Moose; Moose->VERSION('2.1300') },
+    'MooseX::Role::WithOverloading';
 use namespace::clean -except => 'meta';
 
 use overload
index 75796db..6f5a59f 100644 (file)
@@ -7,7 +7,7 @@ BEGIN { require 5.008003; }
 
 # Remember to update this in Catalyst as well!
 
-our $VERSION = '5.90112';
+our $VERSION = '5.90114';
 $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
 
 =head1 NAME
index f8a12da..845e891 100644 (file)
@@ -4,15 +4,14 @@ use Pod::Usage;
 use MooseX::Getopt;
 use Catalyst::EngineLoader;
 use Moose::Util::TypeConstraints;
-use Catalyst::Utils qw/ ensure_class_loaded /;
-use Class::Load 'load_class';
+use Catalyst::Utils;
 use namespace::autoclean;
 
 subtype 'Catalyst::ScriptRole::LoadableClass',
   as 'ClassName';
 coerce 'Catalyst::ScriptRole::LoadableClass',
   from 'Str',
-  via { ensure_class_loaded($_); 1 };
+  via { Catalyst::Utils::ensure_class_loaded($_); $_ };
 
 with 'MooseX::Getopt' => {
     -version => 0.48,
@@ -88,7 +87,7 @@ sub _plack_engine_name {}
 sub _run_application {
     my $self = shift;
     my $app = $self->application_name;
-    load_class($app);
+    Catalyst::Utils::ensure_class_loaded($app);
     my $server;
     if (my $e = $self->_plack_engine_name ) {
         $server = $self->load_engine($e, $self->_plack_loader_args);
diff --git a/t/evil_stash.t b/t/evil_stash.t
new file mode 100644 (file)
index 0000000..e97a131
--- /dev/null
@@ -0,0 +1,37 @@
+use warnings;
+use strict;
+use Test::More;
+
+{
+  package MyApp::Controller::Root;
+  $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+  use base 'Catalyst::Controller';
+
+  sub root :Path('') Args(0) {
+    my ($self, $c) = @_;
+    $c->{stash}->{foo} = 'bar';
+    $c->stash(baz=>'boor');
+    $c->{stash}->{baz} = $c->stash->{baz} . 2;
+    
+    Test::More::is($c->stash->{foo}, 'bar');
+    Test::More::is($c->stash->{baz}, 'boor2');
+    Test::More::is($c->{stash}->{foo}, 'bar');
+    Test::More::is($c->{stash}->{baz}, 'boor2');
+
+    $c->res->body('return');
+  }
+
+  package MyApp;
+  use Catalyst;
+  MyApp->setup;
+}
+
+use HTTP::Request::Common;
+use Catalyst::Test 'MyApp';
+
+{
+   ok my $res = request POST 'root/';
+}
+
+done_testing();
diff --git a/t/relative_root_action_for_bug.t b/t/relative_root_action_for_bug.t
new file mode 100644 (file)
index 0000000..06cd0c2
--- /dev/null
@@ -0,0 +1,93 @@
+use warnings;
+use strict;
+use Test::More;
+
+{
+    package MyApp::Controller::Root;
+    $INC{'MyApp/Controller/Root.pm'} = __FILE__;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/) PathPart('') CaptureArgs(0) {
+      my ($self, $c) = @_;
+    }
+
+    sub top :Chained('root') Args(0) {
+      my ($self, $c) = @_;
+      Test::More::is $self->action_for('top'), 'top';
+      Test::More::is $self->action_for('story/story'), 'story/story';
+    }
+
+    sub default : Path {
+
+        my ($self, $c) = @_;
+        $c->response->body("Ok");
+    }
+
+    MyApp::Controller::Root->config(namespace=>'');
+
+    package MyApp::Controller::Story;
+    $INC{'MyApp/Controller/Story.pm'} = __FILE__;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/root) PathPart('') CaptureArgs(0) {
+      my ($self, $c) = @_;
+    }
+
+    sub story :Chained(root) Args(0) {
+      my ($self, $c) = @_;
+
+      Test::More::is $self->action_for('story'), 'story/story';
+      Test::More::is $self->action_for('author/author'), 'story/author/author';
+    }
+
+    __PACKAGE__->meta->make_immutable;
+
+    package MyApp::Controller::Story::Author;
+    $INC{'MyApp/Controller/Story/Author.pm'} = __FILE__;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/story/root) PathPart('') CaptureArgs(0) {
+      my ($self, $c) = @_;
+    }
+
+    sub author :Chained(root) Args(0) {
+      my ($self, $c, $id) = @_;
+      Test::More::is $self->action_for('author'), 'story/author/author';
+      Test::More::is $self->action_for('../story'), 'story/story';
+      Test::More::is $self->action_for('../../top'), 'top';
+    }
+
+    __PACKAGE__->meta->make_immutable;
+
+    package MyApp;
+    $INC{'MyApp.pm'} = __FILE__;
+
+    use Catalyst;
+
+    MyApp->setup;
+}
+
+use Catalyst::Test 'MyApp';
+
+ok request '/top';
+ok request '/story';
+ok request '/author';
+ok request '/double';
+ok request '/double/file.ext';
+ok request '/double/file..ext';
+
+
+done_testing(13);
+