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