use File::stat;
use NEXT;
use Text::SimpleTable;
-use Path::Class;
+use Path::Class::Dir;
+use Path::Class::File;
use Time::HiRes qw/gettimeofday tv_interval/;
use URI;
-use Scalar::Util qw/weaken/;
+use Scalar::Util qw/weaken blessed/;
use Tree::Simple qw/use_weak_refs/;
use Tree::Simple::Visitor::FindByUID;
use attributes;
-use YAML::Syck;
-use File::Slurp;
+use Carp qw/croak/;
__PACKAGE__->mk_accessors(
qw/counter request response state action stack namespace/
require Module::Pluggable::Fast;
# Helper script generation
-our $CATALYST_SCRIPT_GEN = 26;
+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/;
+ engine_class context_class request_class response_class setup_finished/;
__PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
__PACKAGE__->engine_class('Catalyst::Engine::CGI');
__PACKAGE__->request_class('Catalyst::Request');
__PACKAGE__->response_class('Catalyst::Response');
-our $VERSION = '5.64';
+our $VERSION = '5.66';
sub import {
my ( $class, @arguments ) = @_;
use Catalyst qw/My::Module/;
+If your plugin starts with a name other than C<Catalyst::Plugin::>, you can
+fully qualify the name by using a unary plus:
+
+ use Catalyst qw/
+ My::Module
+ +Fully::Qualified::Plugin::Name
+ /;
+
Special flags like C<-Debug> and C<-Engine> can also be specified as
arguments when Catalyst is loaded:
=head1 METHODS
-=head2 Information about the current request
+=head2 INFORMATION ABOUT THE CURRENT REQUEST
=head2 $c->action
Returns the current L<Catalyst::Request> object. See
L<Catalyst::Request>.
-=head2 Processing and response to the current request
+=head2 PROCESSING AND RESPONSE TO THE CURRENT REQUEST
=head2 $c->forward( $action [, \@arguments ] )
my $foodata = $c->forward('/foo');
$c->forward('index');
- $c->forward(qw/MyApp::Model::CDBI::Foo do_stuff/);
+ $c->forward(qw/MyApp::Model::DBIC::Foo do_stuff/);
$c->forward('MyApp::View::TT');
=cut
$c->error('Something bad happened');
-Clear errors. You probably don't want to clear the errors unless you are
-implementing a custom error screen.
-
- $c->error(0);
-
=cut
sub error {
my $c = shift;
if ( $_[0] ) {
my $error = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
+ croak @$error unless ref $c;
push @{ $c->{error} }, @$error;
}
elsif ( defined $_[0] ) { $c->{error} = undef }
return $c->{error} || [];
}
+=head2 $c->clear_errors
+
+Clear errors. You probably don't want to clear the errors unless you are
+implementing a custom error screen.
+
+This is equivalent to running
+
+ $c->error(0);
+
+=cut
+
+sub clear_errors {
+ my $c = shift;
+ $c->error(0);
+}
+
=head2 $c->response
=head2 $c->res
Contains the return value of the last executed action.
-=head2 Component Accessors
+=cut
+
+# search via regex
+sub _comp_search {
+ my ( $c, @names ) = @_;
+
+ foreach my $name (@names) {
+ foreach my $component ( keys %{ $c->components } ) {
+ return $c->components->{$component} if $component =~ /$name/i;
+ }
+ }
+
+ return undef;
+}
+
+# try explicit component names
+sub _comp_explicit {
+ my ( $c, @names ) = @_;
+
+ foreach my $try (@names) {
+ return $c->components->{$try} if ( exists $c->components->{$try} );
+ }
+
+ return undef;
+}
+
+# like component, but try just these prefixes before regex searching,
+# and do not try to return "sort keys %{ $c->components }"
+sub _comp_prefixes {
+ my ( $c, $name, @prefixes ) = @_;
+
+ my $appclass = ref $c || $c;
+
+ my @names = map { "${appclass}::${_}::${name}" } @prefixes;
+
+ my $comp = $c->_comp_explicit(@names);
+ return $comp if defined($comp);
+ $comp = $c->_comp_search($name);
+ return $comp;
+}
+
+# Find possible names for a prefix
+
+sub _comp_names {
+ my ( $c, @prefixes ) = @_;
+
+ my $appclass = ref $c || $c;
+
+ my @pre = map { "${appclass}::${_}::" } @prefixes;
+
+ my @names;
+
+ COMPONENT: foreach my $comp ($c->component) {
+ foreach my $p (@pre) {
+ if ($comp =~ s/^$p//) {
+ push(@names, $comp);
+ next COMPONENT;
+ }
+ }
+ }
+
+ return @names;
+}
+
+# Return a component if only one matches.
+sub _comp_singular {
+ my ( $c, @prefixes ) = @_;
+
+ my $appclass = ref $c || $c;
+
+ my ( $comp, $rest ) =
+ map { $c->_comp_search("^${appclass}::${_}::") } @prefixes;
+ return $comp unless $rest;
+}
+
+# Filter a component before returning by calling ACCEPT_CONTEXT if available
+sub _filter_component {
+ my ( $c, $comp, @args ) = @_;
+ if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) {
+ return $comp->ACCEPT_CONTEXT( $c, @args );
+ }
+ else { return $comp }
+}
+
+=head2 COMPONENT ACCESSORS
=head2 $c->comp($name)
qw/Model M Controller C View V/
);
- foreach my $try (@names) {
-
- if ( exists $c->components->{$try} ) {
-
- my $comp = $c->components->{$try};
- if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) {
- return $comp->ACCEPT_CONTEXT($c);
- }
- else { return $comp }
- }
- }
-
- foreach my $component ( keys %{ $c->components } ) {
- my $comp;
- $comp = $c->components->{$component} if $component =~ /$name/i;
- if ($comp) {
- if ( ref $comp && $comp->can('ACCEPT_CONTEXT') ) {
- return $comp->ACCEPT_CONTEXT($c);
- }
- else { return $comp }
- }
- }
+ my $comp = $c->_comp_explicit(@names);
+ return $c->_filter_component( $comp, @_ ) if defined($comp);
+ $comp = $c->_comp_search($name);
+ return $c->_filter_component( $comp, @_ ) if defined($comp);
}
return sort keys %{ $c->components };
$c->controller('Foo')->do_stuff;
+If name is omitted, will return the controller for the dispatched action.
+
=cut
sub controller {
- my ( $c, $name ) = @_;
- my $controller = $c->comp("Controller::$name");
- return $controller if defined $controller;
- return $c->comp("C::$name");
+ my ( $c, $name, @args ) = @_;
+ return $c->_filter_component( $c->_comp_prefixes( $name, qw/Controller C/ ),
+ @args )
+ if ($name);
+ return $c->component( $c->action->class );
+}
+
+=head2 $c->controllers
+
+Returns the available names which can be passed to $c->controller
+
+=cut
+
+sub controllers {
+ my ( $c ) = @_;
+ return $c->_comp_names(qw/Controller C/);
}
=head2 $c->model($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.
+
=cut
sub model {
- my ( $c, $name ) = @_;
- my $model = $c->comp("Model::$name");
- return $model if defined $model;
- return $c->comp("M::$name");
+ my ( $c, $name, @args ) = @_;
+ return $c->_filter_component( $c->_comp_prefixes( $name, qw/Model M/ ),
+ @args )
+ if $name;
+ return $c->component( $c->config->{default_model} )
+ if $c->config->{default_model};
+ return $c->_filter_component( $c->_comp_singular(qw/Model M/), @args );
+
+}
+
+=head2 $c->models
+
+Returns the available names which can be passed to $c->model
+
+=cut
+
+sub models {
+ my ( $c ) = @_;
+ return $c->_comp_names(qw/Model M/);
}
=head2 $c->view($name)
$c->view('Foo')->do_stuff;
+If the name is omitted, it will look for a config setting 'default_view',
+or check if there is only one view, and forward to it if that's the case.
+
=cut
sub view {
- my ( $c, $name ) = @_;
- my $view = $c->comp("View::$name");
- return $view if defined $view;
- return $c->comp("V::$name");
+ my ( $c, $name, @args ) = @_;
+ return $c->_filter_component( $c->_comp_prefixes( $name, qw/View V/ ),
+ @args )
+ if $name;
+ return $c->component( $c->config->{default_view} )
+ if $c->config->{default_view};
+ return $c->_filter_component( $c->_comp_singular(qw/View V/) );
+}
+
+=head2 $c->views
+
+Returns the available names which can be passed to $c->view
+
+=cut
+
+sub views {
+ my ( $c ) = @_;
+ return $c->_comp_names(qw/View V/);
}
=head2 Class data and helper classes
Returns or takes a hashref containing the application's configuration.
- __PACKAGE__->config({ db => 'dsn:SQLite:foo.db' });
+ __PACKAGE__->config( { db => 'dsn:SQLite:foo.db' } );
-You can also use a L<YAML> config file like myapp.json in your
+You can also use a L<YAML> config file like myapp.yml in your
applications home directory.
---
db: dsn:SQLite:foo.db
+=cut
+
+sub config {
+ my $c = shift;
+
+ $c->log->warn("Setting config after setup has been run is not a good idea.")
+ if ( @_ and $c->setup_finished );
+
+ $c->NEXT::config(@_);
+}
+
=head2 $c->debug
Overload to enable debug messages (same as -Debug option).
=cut
-=head2 Utility methods
+=head2 UTILITY METHODS
=head2 $c->path_to(@path)
sub path_to {
my ( $c, @path ) = @_;
- my $path = dir( $c->config->{home}, @path );
+ my $path = Path::Class::Dir->new( $c->config->{home}, @path );
if ( -d $path ) { return $path }
- else { return file( $c->config->{home}, @path ) }
+ else { return Path::Class::File->new( $c->config->{home}, @path ) }
}
=head2 $c->plugin( $name, $class, @args )
sub plugin {
my ( $class, $name, $plugin, @args ) = @_;
- $plugin->require;
-
- if ( my $error = $UNIVERSAL::require::ERROR ) {
- Catalyst::Exception->throw(
- message => qq/Couldn't load instant plugin "$plugin", "$error"/ );
- }
+ $class->_register_plugin( $plugin, 1 );
eval { $plugin->import };
$class->mk_classdata($name);
$class->setup_home( delete $flags->{home} );
- # YAML config support
- my $confpath = $class->config->{file}
- || $class->path_to(
- ( Catalyst::Utils::appprefix( ref $class || $class ) . '.yml' ) );
- my $conf = {};
- if ( -f $confpath ) {
- my $content = read_file("$confpath");
- $conf = YAML::Syck::Load($content);
- }
- my $oldconf = $class->config;
- $class->config( { %$oldconf, %$conf } );
-
$class->setup_log( delete $flags->{log} );
$class->setup_plugins( delete $flags->{plugins} );
$class->setup_dispatcher( delete $flags->{dispatcher} );
$class->log->info("$name powered by Catalyst $Catalyst::VERSION");
}
$class->log->_flush() if $class->log->can('_flush');
+
+ $class->setup_finished(1);
}
-=head2 $c->uri_for( $path, [ @args ] )
+=head2 $c->uri_for( $path, @args?, \%query_values? )
Merges path with C<$c-E<gt>request-E<gt>base> for absolute uri's and
with C<$c-E<gt>namespace> for relative uri's, then returns a
normalized L<URI> object. If any args are passed, they are added at the
-end of the path.
+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.
=cut
$namespace = '' if $path =~ /^\//;
$path =~ s/^\///;
+ my $params =
+ ( scalar @args && ref $args[$#args] eq 'HASH' ? pop @args : {} );
+
# join args with '/', or a blank string
my $args = ( scalar @args ? '/' . join( '/', @args ) : '' );
$args =~ s/^\/// unless $path;
my $res =
URI->new_abs( URI->new_abs( "$path$args", "$basepath$namespace" ), $base )
->canonical;
+ $res->query_form(%$params);
$res;
}
$class = $c->component($class) || $class;
$c->state(0);
+ if ( $c->depth >= $RECURSION ) {
+ my $action = "$code";
+ $action = "/$action" unless $action =~ /\-\>/;
+ my $error = qq/Deep recursion detected calling "$action"/;
+ $c->log->error($error);
+ $c->error($error);
+ $c->state(0);
+ return $c->state;
+ }
+
if ( $c->debug ) {
my $action = "$code";
$action = "/$action" unless $action =~ /\-\>/;
$c->counter->{"$code"}++;
- if ( $c->counter->{"$code"} > $RECURSION ) {
- my $error = qq/Deep recursion detected in "$action"/;
- $c->log->error($error);
- $c->error($error);
- $c->state(0);
- return $c->state;
- }
-
# determine if the call was the result of a forward
# this is done by walking up the call stack and looking for a calling
# sub of Catalyst::forward before the eval
$c->log->error($error);
}
- $c->finalize_uploads;
-
- # Error
- if ( $#{ $c->error } >= 0 ) {
- $c->finalize_error;
+ # Allow engine to handle finalize flow (for POE)
+ if ( $c->engine->can('finalize') ) {
+ $c->engine->finalize( $c );
}
+ else {
- $c->finalize_headers;
+ $c->finalize_uploads;
- # HEAD request
- if ( $c->request->method eq 'HEAD' ) {
- $c->response->body('');
- }
+ # Error
+ if ( $#{ $c->error } >= 0 ) {
+ $c->finalize_error;
+ }
+
+ $c->finalize_headers;
- $c->finalize_body;
+ # HEAD request
+ if ( $c->request->method eq 'HEAD' ) {
+ $c->response->body('');
+ }
+
+ $c->finalize_body;
+ }
return $c->response->status;
}
if ( $c->response->body && !$c->response->content_length ) {
# get the length from a filehandle
- if ( ref $c->response->body && $c->response->body->can('read') ) {
+ if ( blessed( $c->response->body ) && $c->response->body->can('read') )
+ {
if ( my $stat = stat $c->response->body ) {
$c->response->content_length( $stat->size );
}
sub get_actions { my $c = shift; $c->dispatcher->get_actions( $c, @_ ) }
-=head2 handle_request( $class, @arguments )
+=head2 $c->handle_request( $class, @arguments )
Called to handle each HTTP request.
$c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
}
- $c->prepare_request(@arguments);
- $c->prepare_connection;
- $c->prepare_query_parameters;
- $c->prepare_headers;
- $c->prepare_cookies;
- $c->prepare_path;
-
- # On-demand parsing
- $c->prepare_body unless $c->config->{parse_on_demand};
+ # Allow engine to direct the prepare flow (for POE)
+ if ( $c->engine->can('prepare') ) {
+ $c->engine->prepare( $c, @arguments );
+ }
+ else {
+ $c->prepare_request(@arguments);
+ $c->prepare_connection;
+ $c->prepare_query_parameters;
+ $c->prepare_headers;
+ $c->prepare_cookies;
+ $c->prepare_path;
+
+ # On-demand parsing
+ $c->prepare_body unless $c->config->{parse_on_demand};
+ }
my $method = $c->req->method || '';
my $path = $c->req->path || '';
if ($home) {
$class->config->{home} ||= $home;
- $class->config->{root} ||= dir($home)->subdir('root');
+ $class->config->{root} ||= Path::Class::Dir->new($home)->subdir('root');
}
}
=cut
-sub setup_plugins {
- my ( $class, $plugins ) = @_;
+=head2 $c->registered_plugins
- $plugins ||= [];
- for my $plugin ( reverse @$plugins ) {
+Returns a sorted list of the plugins which have either been stated in the
+import list or which have been added via C<< MyApp->plugin(@args); >>.
- $plugin = "Catalyst::Plugin::$plugin";
+If passed a given plugin name, it will report a boolean value indicating
+whether or not that plugin is loaded. A fully qualified name is required if
+the plugin name does not begin with C<Catalyst::Plugin::>.
+
+ if ($c->registered_plugins('Some::Plugin')) {
+ ...
+ }
+
+=cut
+
+{
+
+ sub registered_plugins {
+ my $proto = shift;
+ return sort keys %{ $proto->_plugins } unless @_;
+ my $plugin = shift;
+ return 1 if exists $proto->_plugins->{$plugin};
+ return exists $proto->_plugins->{"Catalyst::Plugin::$plugin"};
+ }
+
+ sub _register_plugin {
+ my ( $proto, $plugin, $instant ) = @_;
+ my $class = ref $proto || $proto;
$plugin->require;
- if ($@) {
+ if ( my $error = $@ ) {
+ my $type = $instant ? "instant " : '';
Catalyst::Exception->throw(
- message => qq/Couldn't load plugin "$plugin", "$@"/ );
+ message => qq/Couldn't load ${type}plugin "$plugin", $error/ );
}
- {
+ $proto->_plugins->{$plugin} = 1;
+ unless ($instant) {
no strict 'refs';
unshift @{"$class\::ISA"}, $plugin;
}
+ return $class;
+ }
+
+ sub setup_plugins {
+ my ( $class, $plugins ) = @_;
+
+ $class->_plugins( {} ) unless $class->_plugins;
+ $plugins ||= [];
+ for my $plugin ( reverse @$plugins ) {
+
+ unless ( $plugin =~ s/\A\+// ) {
+ $plugin = "Catalyst::Plugin::$plugin";
+ }
+
+ $class->_register_plugin($plugin);
+ }
}
}
=head2 $c->stack
-Returns the stack.
+Returns an arrayref of the internal execution stack (actions that are currently
+executing).
=head2 $c->write( $data )
Brian Cassidy
+Carl Franks
+
Christian Hansen
Christopher Hicks