Use Ref::Util where appropriate
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Utils.pm
CommitLineData
f05af9ba 1package Catalyst::Utils;
2
3use strict;
37a3ac5c 4use File::Spec;
d837e1a7 5use HTTP::Request;
812a28c9 6use Path::Class;
d837e1a7 7use URI;
5e5bd6df 8use Carp qw/croak/;
a8946dc8 9use Cwd;
e7399d8b 10use Class::Load 'is_class_loaded';
17b3d800 11use String::RewritePrefix;
3086ccde 12use Class::Load ();
0db6e9d5 13use namespace::clean;
ec4d7259 14use Devel::InnerPackage;
cbe627b9 15use Moose::Util;
dd4530ec 16use Ref::Util qw(is_plain_hashref);
0db6e9d5 17
f05af9ba 18=head1 NAME
19
20Catalyst::Utils - The Catalyst Utils
21
22=head1 SYNOPSIS
23
24See L<Catalyst>.
25
26=head1 DESCRIPTION
27
b0ad47c1 28Catalyst Utilities.
39fc2ce1 29
f05af9ba 30=head1 METHODS
31
b5ecfcf0 32=head2 appprefix($class)
41ca9ba7 33
85d9fce6 34 MyApp::Foo becomes myapp_foo
41ca9ba7 35
36=cut
37
38sub appprefix {
39 my $class = shift;
0ef447d8 40 $class =~ s/::/_/g;
41ca9ba7 41 $class = lc($class);
42 return $class;
43}
44
b5ecfcf0 45=head2 class2appclass($class);
84cf74e7 46
0ef447d8 47 MyApp::Controller::Foo::Bar becomes MyApp
48 My::App::Controller::Foo::Bar becomes My::App
2d90477f 49
84cf74e7 50=cut
51
52sub class2appclass {
53 my $class = shift || '';
54 my $appname = '';
0ef447d8 55 if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
84cf74e7 56 $appname = $1;
57 }
58 return $appname;
59}
60
b5ecfcf0 61=head2 class2classprefix($class);
2930d610 62
0ef447d8 63 MyApp::Controller::Foo::Bar becomes MyApp::Controller
64 My::App::Controller::Foo::Bar becomes My::App::Controller
2d90477f 65
2930d610 66=cut
67
68sub class2classprefix {
69 my $class = shift || '';
70 my $prefix;
0ef447d8 71 if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
2930d610 72 $prefix = $1;
73 }
74 return $prefix;
75}
76
b5ecfcf0 77=head2 class2classsuffix($class);
84cf74e7 78
0ef447d8 79 MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
2d90477f 80
84cf74e7 81=cut
82
83sub class2classsuffix {
84 my $class = shift || '';
85 my $prefix = class2appclass($class) || '';
0ef447d8 86 $class =~ s/$prefix\:://;
84cf74e7 87 return $class;
88}
89
b5ecfcf0 90=head2 class2env($class);
3ad654e0 91
26e73131 92Returns the environment name for class.
3ad654e0 93
94 MyApp becomes MYAPP
95 My::App becomes MY_APP
96
97=cut
98
99sub class2env {
100 my $class = shift || '';
0ef447d8 101 $class =~ s/::/_/g;
3ad654e0 102 return uc($class);
103}
104
b5ecfcf0 105=head2 class2prefix( $class, $case );
f05af9ba 106
e2cc89a9 107Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
f05af9ba 108
0ef447d8 109 My::App::Controller::Foo::Bar becomes foo/bar
2d90477f 110
f05af9ba 111=cut
112
113sub class2prefix {
114 my $class = shift || '';
e494bd6b 115 my $case = shift || 0;
f05af9ba 116 my $prefix;
0ef447d8 117 if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
e494bd6b 118 $prefix = $case ? $2 : lc $2;
0ef447d8 119 $prefix =~ s{::}{/}g;
f05af9ba 120 }
121 return $prefix;
122}
123
b5ecfcf0 124=head2 class2tempdir( $class [, $create ] );
37a3ac5c 125
e2cc89a9 126Returns a tempdir for a class. If create is true it will try to create the path.
37a3ac5c 127
128 My::App becomes /tmp/my/app
7d7519a4 129 My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar
37a3ac5c 130
131=cut
132
133sub class2tempdir {
134 my $class = shift || '';
135 my $create = shift || 0;
4be535b1 136 my @parts = split '::', lc $class;
37a3ac5c 137
138 my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
139
4be535b1 140 if ( $create && !-e $tmpdir ) {
37a3ac5c 141
ab61f021 142 eval { $tmpdir->mkpath; 1 }
143 or do {
41a8bf1f 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
147 # not honor setting
148 # $Catalyst::Exception::CATALYST_EXCEPTION_CLASS in
149 # MyApp.pm
150 require Catalyst::Exception;
37a3ac5c 151 Catalyst::Exception->throw(
4be535b1 152 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
37a3ac5c 153 }
154 }
155
156 return $tmpdir->stringify;
157}
158
a8946dc8 159=head2 home($class)
160
161Returns home directory for given class.
162
0f519d62 163=head2 dist_indicator_file_list
164
a8946dc8 165Returns a list of files which can be tested to check if you're inside
e01b6093 166a CPAN distribution which is not yet installed.
167
168These are:
169
170=over
171
172=item Makefile.PL
173
174=item Build.PL
175
176=item dist.ini
177
df221478 178=item L<cpanfile>
179
e01b6093 180=back
0f519d62 181
182=cut
183
184sub dist_indicator_file_list {
df221478 185 qw{Makefile.PL Build.PL dist.ini cpanfile};
0f519d62 186}
187
812a28c9 188sub home {
51f412bd 189 my $class = shift;
190
191 # make an $INC{ $key } style string from the class name
192 (my $file = "$class.pm") =~ s{::}{/}g;
193
194 if ( my $inc_entry = $INC{$file} ) {
51452916 195 {
51f412bd 196 # look for an uninstalled Catalyst app
197
198 # find the @INC entry in which $file was found
199 (my $path = $inc_entry) =~ s/$file$//;
a8946dc8 200 $path ||= cwd() if !defined $path || !length $path;
201 my $home = dir($path)->absolute->cleanup;
202
203 # pop off /lib and /blib if they're there
204 $home = $home->parent while $home =~ /b?lib$/;
205
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
210
211 my $dir;
212 my @dir_list = $home->dir_list();
213 while (($dir = pop(@dir_list)) && $dir eq '..') {
214 $home = dir($home)->parent->parent;
215 }
216
217 return $home->stringify;
218 }
51452916 219 }
4be535b1 220
51f412bd 221 {
222 # look for an installed Catalyst app
223
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;
227
bd85860b 228 # return if it's a valid directory
51f412bd 229 return $home->stringify if -d $home;
62459712 230 }
812a28c9 231 }
51f412bd 232
233 # we found nothing
a8946dc8 234 return 0;
03fb1bee 235}
236
b5ecfcf0 237=head2 prefix($class, $name);
812a28c9 238
239Returns a prefixed action.
240
0ef447d8 241 MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
812a28c9 242
243=cut
244
245sub prefix {
246 my ( $class, $name ) = @_;
247 my $prefix = &class2prefix($class);
248 $name = "$prefix/$name" if $prefix;
249 return $name;
250}
251
b5ecfcf0 252=head2 request($uri)
4d60aa90 253
e2cc89a9 254Returns an L<HTTP::Request> object for a uri.
4d60aa90 255
256=cut
257
258sub request {
259 my $request = shift;
260 unless ( ref $request ) {
a88c7ec8 261 if ( $request =~ m/^http/i ) {
f4c0f6f7 262 $request = URI->new($request);
4d60aa90 263 }
264 else {
f4c0f6f7 265 $request = URI->new( 'http://localhost' . $request );
4d60aa90 266 }
267 }
268 unless ( ref $request eq 'HTTP::Request' ) {
269 $request = HTTP::Request->new( 'GET', $request );
270 }
4d60aa90 271 return $request;
272}
273
dd91afb5 274=head2 ensure_class_loaded($class_name, \%opts)
d9183506 275
276Loads the class unless it already has been loaded.
277
dd91afb5 278If $opts{ignore_loaded} is true always tries the require whether the package
279already exists or not. Only pass this if you're either (a) sure you know the
280file exists on disk or (b) have code to catch the file not found exception
281that will result if it doesn't.
282
d9183506 283=cut
284
285sub ensure_class_loaded {
286 my $class = shift;
d06051f7 287 my $opts = shift;
d9183506 288
5e5bd6df 289 croak "Malformed class Name $class"
290 if $class =~ m/(?:\b\:\b|\:{3,})/;
291
59ede84e 292 croak "Malformed class Name $class"
293 if $class =~ m/[^\w:]/;
294
295 croak "ensure_class_loaded should be given a classname, not a filename ($class)"
296 if $class =~ m/\.pm$/;
297
f55d1491 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.
d06051f7 301 return if !$opts->{ ignore_loaded }
e7399d8b 302 && is_class_loaded($class); # if a symbol entry exists we don't load again
fbedfd6b 303
d9183506 304 # this hack is so we don't overwrite $@ if the load did not generate an error
305 my $error;
306 {
307 local $@;
7a1958eb 308 my $file = $class . '.pm';
309 $file =~ s{::}{/}g;
310 eval { CORE::require($file) };
d9183506 311 $error = $@;
312 }
6bfff75e 313
d9183506 314 die $error if $error;
fbedfd6b 315
f55d1491 316 warn "require $class was successful but the package is not defined."
e7399d8b 317 unless is_class_loaded($class);
6bfff75e 318
319 return 1;
d9183506 320}
321
358e1592 322=head2 merge_hashes($hashref, $hashref)
323
324Base code to recursively merge two hashes together with right-hand precedence.
325
326=cut
327
328sub merge_hashes {
329 my ( $lefthash, $righthash ) = @_;
330
331 return $lefthash unless defined $righthash;
b0ad47c1 332
358e1592 333 my %merged = %$lefthash;
0ef447d8 334 for my $key ( keys %$righthash ) {
dd4530ec 335 my $right_ref = is_plain_hashref( $righthash->{ $key } );
336 my $left_ref = exists $lefthash->{ $key } && is_plain_hashref( $lefthash->{ $key } );
0ef447d8 337 if( $right_ref and $left_ref ) {
358e1592 338 $merged{ $key } = merge_hashes(
339 $lefthash->{ $key }, $righthash->{ $key }
0ef447d8 340 );
358e1592 341 }
342 else {
343 $merged{ $key } = $righthash->{ $key };
0ef447d8 344 }
358e1592 345 }
b0ad47c1 346
358e1592 347 return \%merged;
348}
349
cb69249e 350=head2 env_value($class, $key)
351
352Checks 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,
354looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
355
356=cut
357
358sub env_value {
359 my ( $class, $key ) = @_;
360
361 $key = uc($key);
362 my @prefixes = ( class2env($class), 'CATALYST' );
363
364 for my $prefix (@prefixes) {
365 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
366 return $value;
367 }
368 }
369
370 return;
371}
d9183506 372
39fc2ce1 373=head2 term_width
374
375Try to guess terminal width to use with formatting of debug output
376
377All you need to get this work, is:
378
3791) Install Term::Size::Any, or
380
b0ad47c1 3812) Export $COLUMNS from your shell.
39fc2ce1 382
383(Warning to bash users: 'echo $COLUMNS' may be showing you the bash
b0ad47c1 384variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
39fc2ce1 385that 'env' now lists COLUMNS.)
386
387As last resort, default value of 80 chars will be used.
388
ad9e8de9 389Calling C<term_width> with a true value will cause it to be recalculated; you
390can use this to cause it to get recalculated when your terminal is resized like
391this
392
393 $SIG{WINCH} = sub { Catalyst::Utils::term_width(1) };
394
39fc2ce1 395=cut
396
397my $_term_width;
398
399sub term_width {
ad9e8de9 400 my $force_reset = shift;
401
402 undef $_term_width if $force_reset;
403
39fc2ce1 404 return $_term_width if $_term_width;
405
ab61f021 406 my $width;
407 eval '
dfcb05ee 408 use Term::Size::Any;
409 ($width) = Term::Size::Any::chars;
410 1;
ab61f021 411 ' or do {
dfcb05ee 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";
414 } else {
415 warn "There was an error trying to detect your terminal size: $@\n";
416 }
417 warn 'Trouble trying to detect your terminal size, looking at $ENV{COLUMNS}'."\n";
39fc2ce1 418 $width = $ENV{COLUMNS}
419 if exists($ENV{COLUMNS})
420 && $ENV{COLUMNS} =~ m/^\d+$/;
ab61f021 421 };
39fc2ce1 422
dfcb05ee 423 do {
424 warn "Cannot determine desired terminal width, using default of 80 columns\n";
425 $width = 80 } unless ($width && $width >= 80);
39fc2ce1 426 return $_term_width = $width;
427}
428
17b3d800 429
430=head2 resolve_namespace
431
432Method which adds the namespace for plugins and actions.
433
434 __PACKAGE__->setup(qw(MyPlugin));
196932de 435
17b3d800 436 # will load Catalyst::Plugin::MyPlugin
437
438=cut
439
440
441sub resolve_namespace {
5d8129e9 442 my $appnamespace = shift;
17b3d800 443 my $namespace = shift;
444 my @classes = @_;
196932de 445 return String::RewritePrefix->rewrite({
446 q[] => qq[${namespace}::],
447 q[+] => q[],
448 (defined $appnamespace
449 ? (q[~] => qq[${appnamespace}::])
450 : ()
451 ),
452 }, @classes);
17b3d800 453}
454
3086ccde 455=head2 build_middleware (@args)
456
457Internal application that converts a single middleware definition (see
458L<Catalyst/psgi_middleware>) into an actual instance of middleware.
459
460=cut
461
462sub build_middleware {
463 my ($class, $namespace, @init_args) = @_;
464
465 if(
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
9b5bca00 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);
318213cd 477 } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
478 return "Plack::Middleware::$namespace"->new(@init_args);
d9c6a83f 479 } else {
480 die "Can't load middleware via '$namespace'. It's not ".$class."::Middleware::".$namespace." or Plack::Middleware::$namespace";
3086ccde 481 }
482 }
483
484 return; ## be sure we can count on a proper return when valid
485}
486
487=head2 apply_registered_middleware ($psgi)
488
489Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
490around it and return the wrapped version.
491
492This exists to deal with the fact Catalyst registered middleware can be
493either an object with a wrap method or a coderef.
494
495=cut
496
497sub 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);
504 }
505 return $new_psgi;
506}
17b3d800 507
ec4d7259 508=head2 inject_component
6adc45cf 509
ec4d7259 510Used to add components at runtime:
511
512 into The Catalyst package to inject into (e.g. My::App)
513 component The component package to inject
cbe627b9 514 traits (Optional) ArrayRef of L<Moose::Role>s that the componet should consume.
ec4d7259 515 as An optional moniker to use as the package name for the derived component
516
517For example:
518
519 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple )
520
521 The above will create 'My::App::Controller::Other::App::Controller::Apple'
522
523 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple, as => Apple )
524
525 The above will create 'My::App::Controller::Apple'
526
527 Catalyst::Utils::inject_component( into => $myapp, component => 'MyRootV', as => 'Controller::Root' );
528
529Will inject Controller, Model, and View components into your Catalyst application
530at setup (run)time. It does this by creating a new package on-the-fly, having that
531package extend the given component, and then having Catalyst setup the new component
532(via $app->setup_component).
533
534B<NOTE:> This is basically a core version of L<CatalystX::InjectComponent>. If you were using that
535you can now use this safely instead. Going forward changes required to make this work will be
536synchronized with the core method.
537
cbe627b9 538B<NOTE:> The 'traits' option is unique to the L<Catalyst::Utils> version of this feature.
539
540B<NOTE:> These injected components really need to be a L<Catalyst::Component> and a L<Moose>
541based class.
542
ec4d7259 543=cut
544
545sub inject_component {
546 my %given = @_;
547 my ($into, $component, $as) = @given{qw/into component as/};
548
549 croak "No Catalyst (package) given" unless $into;
550 croak "No component (package) given" unless $component;
551
552 Class::Load::load_class($component);
553
554 $as ||= $component;
555 unless ( $as =~ m/^(?:Controller|Model|View)::/ || $given{skip_mvc_renaming} ) {
556 my $category;
557 for (qw/ Controller Model View /) {
558 if ( $component->isa( "Catalyst::$_" ) ) {
559 $category = $_;
560 last;
561 }
562 }
563 croak "Don't know what kind of component \"$component\" is" unless $category;
564 $as = "${category}::$as";
565 }
566 my $component_package = join '::', $into, $as;
567
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\": $@";
cbe627b9 571 Moose::Util::apply_all_roles($component_package, @{$given{traits}}) if $given{traits};
ec4d7259 572 (my $file = "$component_package.pm") =~ s{::}{/}g;
573 $INC{$file} ||= 1;
574 }
575
576 my $_setup_component = sub {
577 my $into = shift;
578 my $component_package = shift;
e39312ba 579 $into->components->{$component_package} = $into->delayed_setup_component( $component_package );
ec4d7259 580 };
581
582 $_setup_component->( $into, $component_package );
ec4d7259 583}
6adc45cf 584
9c7b6768 585=head1 PSGI Helpers
586
587Utility functions to make it easier to work with PSGI applications under Catalyst
588
589=head2 env_at_path_prefix
590
591Localize C<$env> under the current controller path prefix:
592
593 package MyApp::Controller::User;
594
595 use Catalyst::Utils;
596
597 use base 'Catalyst::Controller';
598
599 sub name :Local {
600 my ($self, $c) = @_;
601 my $env = $c->Catalyst::Utils::env_at_path_prefix;
602 }
603
efa8265f 604Assuming you have a request like GET /user/name:
9c7b6768 605
606In the example case C<$env> will have PATH_INFO of '/name' instead of
607'/user/name' and SCRIPT_NAME will now be '/user'.
608
609=cut
610
611sub env_at_path_prefix {
612 my $ctx = shift;
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} || '');
617
618 $path_info =~ s/(^\/\Q$path_prefix\E)//;
619 $script_name = "$script_name$1";
620
621 return +{
622 %$env,
623 PATH_INFO => $path_info,
624 SCRIPT_NAME => $script_name };
625}
626
627=head2 env_at_action
628
4477b313 629Localize C<$env> under the current action namespace.
9c7b6768 630
631 package MyApp::Controller::User;
632
633 use Catalyst::Utils;
634
635 use base 'Catalyst::Controller';
636
637 sub name :Local {
638 my ($self, $c) = @_;
639 my $env = $c->Catalyst::Utils::env_at_action;
640 }
641
4477b313 642Assuming you have a request like GET /user/name:
9c7b6768 643
644In the example case C<$env> will have PATH_INFO of '/' instead of
645'/user/name' and SCRIPT_NAME will now be '/user/name'.
646
efa8265f 647Alternatively, assuming you have a request like GET /user/name/foo:
4477b313 648
649In 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'.
651
9c7b6768 652This is probably a common case where you want to mount a PSGI application
653under an action but let the Args fall through to the PSGI app.
654
655=cut
656
657sub env_at_action {
658 my $ctx = shift;
659 my $argpath = join '/', @{$ctx->request->arguments};
660 my $path = '/' . $ctx->request->path;
661
662 $path =~ s/\/?\Q$argpath\E\/?$//;
663
664 my $env = $ctx->request->env;
665 my $path_info = $env->{PATH_INFO};
666 my $script_name = ($env->{SCRIPT_NAME} || '');
667
668 $path_info =~ s/(^\Q$path\E)//;
669 $script_name = "$script_name$1";
670
671 return +{
672 %$env,
673 PATH_INFO => $path_info,
674 SCRIPT_NAME => $script_name };
675}
676
677=head2 env_at_request_uri
678
4477b313 679Localize C<$env> under the current request URI:
9c7b6768 680
681 package MyApp::Controller::User;
682
683 use Catalyst::Utils;
684
685 use base 'Catalyst::Controller';
686
687 sub name :Local Args(1) {
688 my ($self, $c, $id) = @_;
689 my $env = $c->Catalyst::Utils::env_at_request_uri
690 }
691
efa8265f 692Assuming you have a request like GET /user/name/hello:
9c7b6768 693
694In the example case C<$env> will have PATH_INFO of '/' instead of
695'/user/name' and SCRIPT_NAME will now be '/user/name/hello'.
696
697=cut
698
699sub env_at_request_uri {
700 my $ctx = shift;
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} || '');
705
706 $path_info =~ s/(^\Q$path\E)//;
707 $script_name = "$script_name$1";
708
709 return +{
710 %$env,
711 PATH_INFO => $path_info,
712 SCRIPT_NAME => $script_name };
713}
714
2f381252 715=head1 AUTHORS
f05af9ba 716
2f381252 717Catalyst Contributors, see Catalyst.pm
f05af9ba 718
719=head1 COPYRIGHT
720
536bee89 721This library is free software. You can redistribute it and/or modify it under
f05af9ba 722the same terms as Perl itself.
723
724=cut
725
7261;