1 package Catalyst::Utils;
10 use Class::Load 'is_class_loaded';
11 use String::RewritePrefix;
14 use Devel::InnerPackage;
16 use Ref::Util qw(is_plain_hashref);
20 Catalyst::Utils - The Catalyst Utils
32 =head2 appprefix($class)
34 MyApp::Foo becomes myapp_foo
45 =head2 class2appclass($class);
47 MyApp::Controller::Foo::Bar becomes MyApp
48 My::App::Controller::Foo::Bar becomes My::App
53 my $class = shift || '';
55 if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
61 =head2 class2classprefix($class);
63 MyApp::Controller::Foo::Bar becomes MyApp::Controller
64 My::App::Controller::Foo::Bar becomes My::App::Controller
68 sub class2classprefix {
69 my $class = shift || '';
71 if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
77 =head2 class2classsuffix($class);
79 MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
83 sub class2classsuffix {
84 my $class = shift || '';
85 my $prefix = class2appclass($class) || '';
86 $class =~ s/$prefix\:://;
90 =head2 class2env($class);
92 Returns the environment name for class.
95 My::App becomes MY_APP
100 my $class = shift || '';
105 =head2 class2prefix( $class, $case );
107 Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
109 My::App::Controller::Foo::Bar becomes foo/bar
114 my $class = shift || '';
115 my $case = shift || 0;
117 if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
118 $prefix = $case ? $2 : lc $2;
119 $prefix =~ s{::}{/}g;
124 =head2 class2tempdir( $class [, $create ] );
126 Returns a tempdir for a class. If create is true it will try to create the path.
128 My::App becomes /tmp/my/app
129 My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar
134 my $class = shift || '';
135 my $create = shift || 0;
136 my @parts = split '::', lc $class;
138 my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
140 if ( $create && !-e $tmpdir ) {
142 eval { $tmpdir->mkpath; 1 }
144 # don't load Catalyst::Exception as a BEGIN in Utils,
145 # because Utils often gets loaded before MyApp.pm, and if
146 # Catalyst::Exception is loaded before MyApp.pm, it does
148 # $Catalyst::Exception::CATALYST_EXCEPTION_CLASS in
150 require Catalyst::Exception;
151 Catalyst::Exception->throw(
152 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
156 return $tmpdir->stringify;
161 Returns home directory for given class.
163 =head2 dist_indicator_file_list
165 Returns a list of files which can be tested to check if you're inside
166 a CPAN distribution which is not yet installed.
184 sub dist_indicator_file_list {
185 qw{Makefile.PL Build.PL dist.ini cpanfile};
191 # make an $INC{ $key } style string from the class name
192 (my $file = "$class.pm") =~ s{::}{/}g;
194 if ( my $inc_entry = $INC{$file} ) {
196 # look for an uninstalled Catalyst app
198 # find the @INC entry in which $file was found
199 (my $path = $inc_entry) =~ s/$file$//;
200 $path ||= cwd() if !defined $path || !length $path;
201 my $home = dir($path)->absolute->cleanup;
203 # pop off /lib and /blib if they're there
204 $home = $home->parent while $home =~ /b?lib$/;
206 # only return the dir if it has a Makefile.PL or Build.PL or dist.ini
207 if (grep { -f $home->file($_) } dist_indicator_file_list()) {
208 # clean up relative path:
209 # MyApp/script/.. -> MyApp
212 my @dir_list = $home->dir_list();
213 while (($dir = pop(@dir_list)) && $dir eq '..') {
214 $home = dir($home)->parent->parent;
217 return $home->stringify;
222 # look for an installed Catalyst app
224 # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
225 ( my $path = $inc_entry) =~ s/\.pm$//;
226 my $home = dir($path)->absolute->cleanup;
228 # return if it's a valid directory
229 return $home->stringify if -d $home;
237 =head2 prefix($class, $name);
239 Returns a prefixed action.
241 MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
246 my ( $class, $name ) = @_;
247 my $prefix = &class2prefix($class);
248 $name = "$prefix/$name" if $prefix;
254 Returns an L<HTTP::Request> object for a uri.
260 unless ( ref $request ) {
261 if ( $request =~ m/^http/i ) {
262 $request = URI->new($request);
265 $request = URI->new( 'http://localhost' . $request );
268 unless ( ref $request eq 'HTTP::Request' ) {
269 $request = HTTP::Request->new( 'GET', $request );
274 =head2 ensure_class_loaded($class_name, \%opts)
276 Loads the class unless it already has been loaded.
278 If $opts{ignore_loaded} is true always tries the require whether the package
279 already exists or not. Only pass this if you're either (a) sure you know the
280 file exists on disk or (b) have code to catch the file not found exception
281 that will result if it doesn't.
285 sub ensure_class_loaded {
289 croak "Malformed class Name $class"
290 if $class =~ m/(?:\b\:\b|\:{3,})/;
292 croak "Malformed class Name $class"
293 if $class =~ m/[^\w:]/;
295 croak "ensure_class_loaded should be given a classname, not a filename ($class)"
296 if $class =~ m/\.pm$/;
298 # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even
299 # if it already has symbol table entries. This is to support things like Schema::Loader, which
300 # part-generate classes in memory, but then also load some of their contents from disk.
301 return if !$opts->{ ignore_loaded }
302 && is_class_loaded($class); # if a symbol entry exists we don't load again
304 # this hack is so we don't overwrite $@ if the load did not generate an error
308 my $file = $class . '.pm';
310 eval { CORE::require($file) };
314 die $error if $error;
316 warn "require $class was successful but the package is not defined."
317 unless is_class_loaded($class);
322 =head2 merge_hashes($hashref, $hashref)
324 Base code to recursively merge two hashes together with right-hand precedence.
329 my ( $lefthash, $righthash ) = @_;
331 return $lefthash unless defined $righthash;
333 my %merged = %$lefthash;
334 for my $key ( keys %$righthash ) {
335 my $right_ref = is_plain_hashref( $righthash->{ $key } );
336 my $left_ref = exists $lefthash->{ $key } && is_plain_hashref( $lefthash->{ $key } );
337 if( $right_ref and $left_ref ) {
338 $merged{ $key } = merge_hashes(
339 $lefthash->{ $key }, $righthash->{ $key }
343 $merged{ $key } = $righthash->{ $key };
350 =head2 env_value($class, $key)
352 Checks for and returns an environment value. For instance, if $key is
353 'home', then this method will check for and return the first value it finds,
354 looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
359 my ( $class, $key ) = @_;
362 my @prefixes = ( class2env($class), 'CATALYST' );
364 for my $prefix (@prefixes) {
365 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
375 Try to guess terminal width to use with formatting of debug output
377 All you need to get this work, is:
379 1) Install Term::Size::Any, or
381 2) Export $COLUMNS from your shell.
383 (Warning to bash users: 'echo $COLUMNS' may be showing you the bash
384 variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
385 that 'env' now lists COLUMNS.)
387 As last resort, default value of 80 chars will be used.
389 Calling C<term_width> with a true value will cause it to be recalculated; you
390 can use this to cause it to get recalculated when your terminal is resized like
393 $SIG{WINCH} = sub { Catalyst::Utils::term_width(1) };
400 my $force_reset = shift;
402 undef $_term_width if $force_reset;
404 return $_term_width if $_term_width;
409 ($width) = Term::Size::Any::chars;
412 if($@ =~m[Can't locate Term/Size/Any.pm]) {
413 warn "Term::Size::Any is not installed, can't autodetect terminal column width\n";
415 warn "There was an error trying to detect your terminal size: $@\n";
417 warn 'Trouble trying to detect your terminal size, looking at $ENV{COLUMNS}'."\n";
418 $width = $ENV{COLUMNS}
419 if exists($ENV{COLUMNS})
420 && $ENV{COLUMNS} =~ m/^\d+$/;
424 warn "Cannot determine desired terminal width, using default of 80 columns\n";
425 $width = 80 } unless ($width && $width >= 80);
426 return $_term_width = $width;
430 =head2 resolve_namespace
432 Method which adds the namespace for plugins and actions.
434 __PACKAGE__->setup(qw(MyPlugin));
436 # will load Catalyst::Plugin::MyPlugin
441 sub resolve_namespace {
442 my $appnamespace = shift;
443 my $namespace = shift;
445 return String::RewritePrefix->rewrite({
446 q[] => qq[${namespace}::],
448 (defined $appnamespace
449 ? (q[~] => qq[${appnamespace}::])
455 =head2 build_middleware (@args)
457 Internal application that converts a single middleware definition (see
458 L<Catalyst/psgi_middleware>) into an actual instance of middleware.
462 sub build_middleware {
463 my ($class, $namespace, @init_args) = @_;
466 $namespace =~s/^\+// ||
467 $namespace =~/^Plack::Middleware/ ||
468 $namespace =~/^$class/
469 ) { ## the string is a full namespace
470 return Class::Load::try_load_class($namespace) ?
471 $namespace->new(@init_args) :
472 die "Can't load class $namespace";
473 } else { ## the string is a partial namespace
474 if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace
475 my $ns = $class .'::Middleware::'. $namespace;
476 return $ns->new(@init_args);
477 } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
478 return "Plack::Middleware::$namespace"->new(@init_args);
480 die "Can't load middleware via '$namespace'. It's not ".$class."::Middleware::".$namespace." or Plack::Middleware::$namespace";
484 return; ## be sure we can count on a proper return when valid
487 =head2 apply_registered_middleware ($psgi)
489 Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
490 around it and return the wrapped version.
492 This exists to deal with the fact Catalyst registered middleware can be
493 either an object with a wrap method or a coderef.
497 sub apply_registered_middleware {
498 my ($class, $psgi) = @_;
499 my $new_psgi = $psgi;
500 foreach my $middleware ($class->registered_middlewares) {
501 $new_psgi = Scalar::Util::blessed $middleware ?
502 $middleware->wrap($new_psgi) :
503 $middleware->($new_psgi);
508 =head2 inject_component
510 Used to add components at runtime:
512 into The Catalyst package to inject into (e.g. My::App)
513 component The component package to inject
514 traits (Optional) ArrayRef of L<Moose::Role>s that the componet should consume.
515 as An optional moniker to use as the package name for the derived component
519 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple )
521 The above will create 'My::App::Controller::Other::App::Controller::Apple'
523 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple, as => Apple )
525 The above will create 'My::App::Controller::Apple'
527 Catalyst::Utils::inject_component( into => $myapp, component => 'MyRootV', as => 'Controller::Root' );
529 Will inject Controller, Model, and View components into your Catalyst application
530 at setup (run)time. It does this by creating a new package on-the-fly, having that
531 package extend the given component, and then having Catalyst setup the new component
532 (via $app->setup_component).
534 B<NOTE:> This is basically a core version of L<CatalystX::InjectComponent>. If you were using that
535 you can now use this safely instead. Going forward changes required to make this work will be
536 synchronized with the core method.
538 B<NOTE:> The 'traits' option is unique to the L<Catalyst::Utils> version of this feature.
540 B<NOTE:> These injected components really need to be a L<Catalyst::Component> and a L<Moose>
545 sub inject_component {
547 my ($into, $component, $as) = @given{qw/into component as/};
549 croak "No Catalyst (package) given" unless $into;
550 croak "No component (package) given" unless $component;
552 Class::Load::load_class($component);
555 unless ( $as =~ m/^(?:Controller|Model|View)::/ || $given{skip_mvc_renaming} ) {
557 for (qw/ Controller Model View /) {
558 if ( $component->isa( "Catalyst::$_" ) ) {
563 croak "Don't know what kind of component \"$component\" is" unless $category;
564 $as = "${category}::$as";
566 my $component_package = join '::', $into, $as;
568 unless ( Class::Load::is_class_loaded $component_package ) {
569 eval "package $component_package; use base qw/$component/; 1;" or
570 croak "Unable to build component package for \"$component_package\": $@";
571 Moose::Util::apply_all_roles($component_package, @{$given{traits}}) if $given{traits};
572 (my $file = "$component_package.pm") =~ s{::}{/}g;
576 my $_setup_component = sub {
578 my $component_package = shift;
579 $into->components->{$component_package} = $into->delayed_setup_component( $component_package );
582 $_setup_component->( $into, $component_package );
587 Utility functions to make it easier to work with PSGI applications under Catalyst
589 =head2 env_at_path_prefix
591 Localize C<$env> under the current controller path prefix:
593 package MyApp::Controller::User;
597 use base 'Catalyst::Controller';
601 my $env = $c->Catalyst::Utils::env_at_path_prefix;
604 Assuming you have a request like GET /user/name:
606 In the example case C<$env> will have PATH_INFO of '/name' instead of
607 '/user/name' and SCRIPT_NAME will now be '/user'.
611 sub env_at_path_prefix {
613 my $path_prefix = $ctx->controller->path_prefix;
614 my $env = $ctx->request->env;
615 my $path_info = $env->{PATH_INFO};
616 my $script_name = ($env->{SCRIPT_NAME} || '');
618 $path_info =~ s/(^\/\Q$path_prefix\E)//;
619 $script_name = "$script_name$1";
623 PATH_INFO => $path_info,
624 SCRIPT_NAME => $script_name };
629 Localize C<$env> under the current action namespace.
631 package MyApp::Controller::User;
635 use base 'Catalyst::Controller';
639 my $env = $c->Catalyst::Utils::env_at_action;
642 Assuming you have a request like GET /user/name:
644 In the example case C<$env> will have PATH_INFO of '/' instead of
645 '/user/name' and SCRIPT_NAME will now be '/user/name'.
647 Alternatively, assuming you have a request like GET /user/name/foo:
649 In this example case C<$env> will have PATH_INFO of '/foo' instead of
650 '/user/name/foo' and SCRIPT_NAME will now be '/user/name'.
652 This is probably a common case where you want to mount a PSGI application
653 under an action but let the Args fall through to the PSGI app.
659 my $argpath = join '/', @{$ctx->request->arguments};
660 my $path = '/' . $ctx->request->path;
662 $path =~ s/\/?\Q$argpath\E\/?$//;
664 my $env = $ctx->request->env;
665 my $path_info = $env->{PATH_INFO};
666 my $script_name = ($env->{SCRIPT_NAME} || '');
668 $path_info =~ s/(^\Q$path\E)//;
669 $script_name = "$script_name$1";
673 PATH_INFO => $path_info,
674 SCRIPT_NAME => $script_name };
677 =head2 env_at_request_uri
679 Localize C<$env> under the current request URI:
681 package MyApp::Controller::User;
685 use base 'Catalyst::Controller';
687 sub name :Local Args(1) {
688 my ($self, $c, $id) = @_;
689 my $env = $c->Catalyst::Utils::env_at_request_uri
692 Assuming you have a request like GET /user/name/hello:
694 In the example case C<$env> will have PATH_INFO of '/' instead of
695 '/user/name' and SCRIPT_NAME will now be '/user/name/hello'.
699 sub env_at_request_uri {
701 my $path = '/' . $ctx->request->path;
702 my $env = $ctx->request->env;
703 my $path_info = $env->{PATH_INFO};
704 my $script_name = ($env->{SCRIPT_NAME} || '');
706 $path_info =~ s/(^\Q$path\E)//;
707 $script_name = "$script_name$1";
711 PATH_INFO => $path_info,
712 SCRIPT_NAME => $script_name };
717 Catalyst Contributors, see Catalyst.pm
721 This library is free software. You can redistribute it and/or modify it under
722 the same terms as Perl itself.