moved version to ::Runtime, modified Makefile.PL
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
index 961899e..0d05976 100644 (file)
@@ -3,7 +3,6 @@ package Catalyst;
 use strict;
 use base 'Catalyst::Component';
 use bytes;
-use UNIVERSAL::require;
 use Catalyst::Exception;
 use Catalyst::Log;
 use Catalyst::Request;
@@ -11,7 +10,10 @@ use Catalyst::Request::Upload;
 use Catalyst::Response;
 use Catalyst::Utils;
 use Catalyst::Controller;
+use Catalyst::Runtime;
+use Devel::InnerPackage ();
 use File::stat;
+use Module::Pluggable::Object;
 use NEXT;
 use Text::SimpleTable;
 use Path::Class::Dir;
@@ -25,6 +27,8 @@ use attributes;
 use utf8;
 use Carp qw/croak/;
 
+BEGIN { require 5.008001; }
+
 __PACKAGE__->mk_accessors(
     qw/counter request response state action stack namespace stats/
 );
@@ -47,11 +51,6 @@ our $START     = time;
 our $RECURSION = 1000;
 our $DETACH    = "catalyst_detach\n";
 
-require Module::Pluggable::Fast;
-
-# Helper script generation
-our $CATALYST_SCRIPT_GEN = 27;
-
 __PACKAGE__->mk_classdata($_)
   for qw/components arguments dispatcher engine log dispatcher_class
   engine_class context_class request_class response_class setup_finished/;
@@ -61,7 +60,7 @@ __PACKAGE__->engine_class('Catalyst::Engine::CGI');
 __PACKAGE__->request_class('Catalyst::Request');
 __PACKAGE__->response_class('Catalyst::Response');
 
-our $VERSION = '5.6902';
+our $VERSION = $Catalyst::Runtime::VERSION;
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -104,7 +103,7 @@ Catalyst - The Elegant MVC Web Application Framework
     ### in lib/MyApp.pm
     use Catalyst qw/-Debug/; # include plugins here as well
     
-       ### In libMyApp/Controller/Root.pm (autocreated)
+       ### In lib/MyApp/Controller/Root.pm (autocreated)
     sub foo : Global { # called for /foo, /foo/1, /foo/1/2, etc.
         my ( $self, $c, @args ) = @_; # args are qw/1 2/ for /foo/1/2
         $c->stash->{template} = 'foo.tt'; # set the template
@@ -164,7 +163,7 @@ Catalyst - The Elegant MVC Web Application Framework
     sub details : Regex('^product/(\w+)/details$') {
         my ( $self, $c ) = @_;
         # extract the (\w+) from the URI
-        my $product = $c->req->snippets->[0];
+        my $product = $c->req->captures->[0];
     }
 
 See L<Catalyst::Manual::Intro> for additional information.
@@ -202,7 +201,9 @@ The following flags are supported:
 
 =head2 -Debug
 
-Enables debug output.
+Enables debug output. You can also force this setting from the system
+environment with CATALYST_DEBUG or <MYAPP>_DEBUG. The environment settings
+override the app, with <MYAPP>_DEBUG having highest priority.
 
 =head2 -Engine
 
@@ -319,8 +320,9 @@ sub stash {
     my $c = shift;
     if (@_) {
         my $stash = @_ > 1 ? {@_} : $_[0];
-        while ( my ( $key, $val ) = each %$stash ) {
-            $c->{stash}->{$key} = $val;
+       croak('stash takes a hash or hashref') unless ref $stash;
+        foreach my $key ( keys %$stash ) {
+            $c->{stash}->{$key} = $stash->{$key};
         }
     }
     return $c->{stash};
@@ -488,7 +490,7 @@ Gets a L<Catalyst::Model> instance by name.
     $c->model('Foo')->do_stuff;
 
 If the name is omitted, it will look for a config setting 'default_model',
-or check if there is only one model, and forward to it if that's the case.
+or check if there is only one view, and return it if that's the case.
 
 =cut
 
@@ -645,6 +647,9 @@ L<Catalyst::Log> man page.
 
 Overload to enable debug messages (same as -Debug option).
 
+Note that this is a static method, not an accessor and should be overloaded
+by declaring "sub debug { 1 }" in your MyApp.pm, not by calling $c->debug(1).
+
 =cut
 
 sub debug { 0 }
@@ -725,6 +730,9 @@ Catalyst> line.
 sub setup {
     my ( $class, @arguments ) = @_;
 
+    $class->log->warn("Running setup twice is not a good idea.")
+      if ( $class->setup_finished );
+
     unless ( $class->isa('Catalyst') ) {
 
         Catalyst::Exception->throw(
@@ -769,8 +777,9 @@ sub setup {
         }
     }
 
-    $class->log->warn(
-        <<"EOF") if ( $ENV{CATALYST_SCRIPT_GEN} && ( $ENV{CATALYST_SCRIPT_GEN} < $Catalyst::CATALYST_SCRIPT_GEN ) );
+    eval { require Catalyst::Devel; };
+    if( !$@ && $ENV{CATALYST_SCRIPT_GEN} && ( $ENV{CATALYST_SCRIPT_GEN} < $Catalyst::Devel::CATALYST_SCRIPT_GEN ) ) {
+        $class->log->warn(<<"EOF");
 You are running an old script!
 
   Please update by running (this will overwrite existing files):
@@ -779,7 +788,8 @@ You are running an old script!
   or (this will not overwrite existing files):
     catalyst.pl -scripts $class
 EOF
-
+    }
+    
     if ( $class->debug ) {
 
         my @plugins = ();
@@ -792,7 +802,7 @@ EOF
         }
 
         if (@plugins) {
-            my $t = Text::SimpleTable->new(76);
+            my $t = Text::SimpleTable->new(74);
             $t->row($_) for @plugins;
             $class->log->debug( "Loaded plugins:\n" . $t->draw );
         }
@@ -824,7 +834,7 @@ EOF
     $class->setup_components;
 
     if ( $class->debug ) {
-        my $t = Text::SimpleTable->new( [ 65, 'Class' ], [ 8, 'Type' ] );
+        my $t = Text::SimpleTable->new( [ 63, 'Class' ], [ 8, 'Type' ] );
         for my $comp ( sort keys %{ $class->components } ) {
             my $type = ref $class->components->{$comp} ? 'instance' : 'class';
             $t->row( $comp, $type );
@@ -856,6 +866,11 @@ end of the path.  If the last argument to uri_for is a hash reference,
 it is assumed to contain GET parameter key/value pairs, which will be
 appended to the URI in standard fashion.
 
+Instead of $path, you can also optionally pass a $action object which will
+be resolved to a path using $c->dispatcher->uri_for_action; if the first
+element of @args is an arrayref it is treated as a list of captures to be
+passed to uri_for_action.
+
 =cut
 
 sub uri_for {
@@ -864,10 +879,18 @@ sub uri_for {
     my $basepath = $base->path;
     $basepath =~ s/\/$//;
     $basepath .= '/';
-    my $namespace = $c->namespace;
+    my $namespace = $c->namespace || '';
+
+    if ( Scalar::Util::blessed($path) ) { # action object
+        my $captures = ( scalar @args && ref $args[0] eq 'ARRAY'
+                         ? shift(@args)
+                         : [] );
+        $path = $c->dispatcher->uri_for_action($path, $captures);
+        return undef unless defined($path);
+    }
 
     # massage namespace, empty if absolute path
-    $namespace =~ s/^\///;
+    $namespace =~ s/^\/// if $namespace;
     $namespace .= '/' if $namespace;
     $path ||= '';
     $namespace = '' if $path =~ /^\//;
@@ -928,7 +951,6 @@ sub welcome_message {
                 text-align: left;
                 background-color: #ccc;
                 border: 1px solid #aaa;
-                -moz-border-radius: 10px;
             }
             p, h1, h2 {
                 margin-left: 20px;
@@ -958,7 +980,6 @@ sub welcome_message {
                 margin: 10px;
                 background-color: #fff;
                 border: 1px solid #aaa;
-                -moz-border-radius: 10px;
             }
             h1 {
                 font-size: 0.9em;
@@ -1277,7 +1298,7 @@ sub finalize {
 
     # Allow engine to handle finalize flow (for POE)
     if ( $c->engine->can('finalize') ) {
-        $c->engine->finalize( $c );
+        $c->engine->finalize($c);
     }
     else {
 
@@ -1440,7 +1461,7 @@ sub handle_request {
             $elapsed = sprintf '%f', $elapsed;
             my $av = sprintf '%.3f',
               ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
-            my $t = Text::SimpleTable->new( [ 64, 'Action' ], [ 9, 'Time' ] );
+            my $t = Text::SimpleTable->new( [ 62, 'Action' ], [ 9, 'Time' ] );
 
             $stats->traverse(
                 sub {
@@ -1492,7 +1513,7 @@ sub prepare {
                     parameters       => {},
                     query_parameters => {},
                     secure           => 0,
-                    snippets         => [],
+                    captures         => [],
                     uploads          => {}
                 }
             ),
@@ -1518,9 +1539,8 @@ sub prepare {
     if ( $c->debug ) {
         my $secs = time - $START || 1;
         my $av = sprintf '%.3f', $COUNT / $secs;
-        $c->log->debug('**********************************');
-        $c->log->debug("* Request $COUNT ($av/s) [$$]");
-        $c->log->debug('**********************************');
+        my $time = localtime time;
+        $c->log->info("*** Request $COUNT ($av/s) [$$] [$time] ***");
         $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
     }
 
@@ -1541,7 +1561,7 @@ sub prepare {
     }
 
     my $method  = $c->req->method  || '';
-    my $path    = $c->req->path    || '';
+    my $path    = $c->req->path    || '/';
     my $address = $c->req->address || '';
 
     $c->log->debug(qq/"$method" request for "$path" from "$address"/)
@@ -1578,7 +1598,7 @@ sub prepare_body {
     $c->prepare_uploads;
 
     if ( $c->debug && keys %{ $c->req->body_parameters } ) {
-        my $t = Text::SimpleTable->new( [ 37, 'Key' ], [ 36, 'Value' ] );
+        my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] );
         for my $key ( sort keys %{ $c->req->body_parameters } ) {
             my $param = $c->req->body_parameters->{$key};
             my $value = defined($param) ? $param : '';
@@ -1672,7 +1692,7 @@ sub prepare_query_parameters {
     $c->engine->prepare_query_parameters( $c, @_ );
 
     if ( $c->debug && keys %{ $c->request->query_parameters } ) {
-        my $t = Text::SimpleTable->new( [ 37, 'Key' ], [ 36, 'Value' ] );
+        my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] );
         for my $key ( sort keys %{ $c->req->query_parameters } ) {
             my $param = $c->req->query_parameters->{$key};
             my $value = defined($param) ? $param : '';
@@ -1712,8 +1732,8 @@ sub prepare_uploads {
 
     if ( $c->debug && keys %{ $c->request->uploads } ) {
         my $t = Text::SimpleTable->new(
-            [ 12, 'Key' ],
-            [ 28, 'Filename' ],
+            [ 12, 'Parameter' ],
+            [ 26, 'Filename' ],
             [ 18, 'Type' ],
             [ 9,  'Size' ]
         );
@@ -1782,64 +1802,73 @@ sub setup_actions { my $c = shift; $c->dispatcher->setup_actions( $c, @_ ) }
 
 =head2 $c->setup_components
 
-Sets up components.
+Sets up components. Specify a C<setup_components> config option to pass additional options
+directly to L<Module::Pluggable>. To add additional search paths, specify a key named
+C<search_extra> as an array reference. Items in the array beginning with C<::> will have the
+application class name prepended to them.
 
 =cut
 
 sub setup_components {
     my $class = shift;
 
-    my $callback = sub {
-        my ( $component, $context ) = @_;
-
-        unless ( $component->can('COMPONENT') ) {
-            return $component;
+    my @paths   = qw( ::Controller ::C ::Model ::M ::View ::V );
+    my $config  = $class->config->{ setup_components };
+    my $extra   = delete $config->{ search_extra } || [];
+    
+    push @paths, @$extra;
+        
+    my $locator = Module::Pluggable::Object->new(
+        search_path => [ map { s/^(?=::)/$class/; $_; } @paths ],
+        %$config
+    );
+    
+    for my $component ( sort { length $a <=> length $b } $locator->plugins ) {
+        Catalyst::Utils::ensure_class_loaded( $component );
+
+        my $module  = $class->setup_component( $component );
+        my %modules = (
+            $component => $module,
+            map {
+                $_ => $class->setup_component( $_ )
+            } Devel::InnerPackage::list_packages( $component )
+        );
+        
+        for my $key ( keys %modules ) {
+            $class->components->{ $key } = $modules{ $key };
         }
+    }
+}
 
-        my $suffix = Catalyst::Utils::class2classsuffix($component);
-        my $config = $class->config->{$suffix} || {};
-
-        my $instance;
-
-        eval { $instance = $component->COMPONENT( $context, $config ); };
+=head2 $c->setup_component
 
-        if ( my $error = $@ ) {
+=cut
 
-            chomp $error;
+sub setup_component {
+    my( $class, $component ) = @_;
 
-            Catalyst::Exception->throw( message =>
-                  qq/Couldn't instantiate component "$component", "$error"/ );
-        }
+    unless ( $component->can( 'COMPONENT' ) ) {
+        return $component;
+    }
 
-        Catalyst::Exception->throw( message =>
-qq/Couldn't instantiate component "$component", "COMPONENT() didn't return a object"/
-          )
-          unless ref $instance;
-        return $instance;
-    };
+    my $suffix = Catalyst::Utils::class2classsuffix( $component );
+    my $config = $class->config->{ $suffix } || {};
 
-    eval "package $class;\n" . q!Module::Pluggable::Fast->import(
-            name   => '_catalyst_components',
-            search => [
-                "$class\::Controller", "$class\::C",
-                "$class\::Model",      "$class\::M",
-                "$class\::View",       "$class\::V"
-            ],
-            callback => $callback
-        );
-    !;
+    my $instance = eval { $component->COMPONENT( $class, $config ); };
 
     if ( my $error = $@ ) {
-
         chomp $error;
-
         Catalyst::Exception->throw(
-            message => qq/Couldn't load components "$error"/ );
+            message => qq/Couldn't instantiate component "$component", "$error"/
+        );
     }
 
-    for my $component ( $class->_catalyst_components($class) ) {
-        $class->components->{ ref $component || $component } = $component;
-    }
+    Catalyst::Exception->throw(
+        message =>
+        qq/Couldn't instantiate component "$component", "COMPONENT() didn't return an object-like value"/
+    ) unless eval { $instance->can( 'can' ) };
+
+    return $instance;
 }
 
 =head2 $c->setup_dispatcher
@@ -1868,11 +1897,8 @@ sub setup_dispatcher {
         $dispatcher = $class->dispatcher_class;
     }
 
-    $dispatcher->require;
-
-    if ($@) {
-        Catalyst::Exception->throw(
-            message => qq/Couldn't load dispatcher "$dispatcher", "$@"/ );
+    unless (Class::Inspector->loaded($dispatcher)) {
+        require Class::Inspector->filename($dispatcher);
     }
 
     # dispatcher instance
@@ -1963,12 +1989,8 @@ sub setup_engine {
         $engine = $class->engine_class;
     }
 
-    $engine->require;
-
-    if ($@) {
-        Catalyst::Exception->throw( message =>
-qq/Couldn't load engine "$engine" (maybe you forgot to install it?), "$@"/
-        );
+    unless (Class::Inspector->loaded($engine)) {
+        require Class::Inspector->filename($engine);
     }
 
     # check for old engines that are no longer compatible
@@ -2096,12 +2118,8 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
         my ( $proto, $plugin, $instant ) = @_;
         my $class = ref $proto || $proto;
 
-        $plugin->require;
-
-        if ( my $error = $@ ) {
-            my $type = $instant ? "instant " : '';
-            Catalyst::Exception->throw(
-                message => qq/Couldn't load ${type}plugin "$plugin", $error/ );
+        unless (Class::Inspector->loaded($plugin)) {
+            require Class::Inspector->filename($plugin);
         }
 
         $proto->_plugins->{$plugin} = 1;