inject_component can now compose roles
[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         warn 'Trouble trying to detect your terminal size, looking at $ENV{COLUMNS}'."\n";
417         $width = $ENV{COLUMNS}
418             if exists($ENV{COLUMNS})
419             && $ENV{COLUMNS} =~ m/^\d+$/;
420     };
421
422     do {
423       warn "Cannot determine desired terminal width, using default of 80 columns\n";
424       $width = 80 } unless ($width && $width >= 80);
425     return $_term_width = $width;
426 }
427
428
429 =head2 resolve_namespace
430
431 Method which adds the namespace for plugins and actions.
432
433   __PACKAGE__->setup(qw(MyPlugin));
434
435   # will load Catalyst::Plugin::MyPlugin
436
437 =cut
438
439
440 sub resolve_namespace {
441     my $appnamespace = shift;
442     my $namespace = shift;
443     my @classes = @_;
444     return String::RewritePrefix->rewrite({
445         q[]  => qq[${namespace}::],
446         q[+] => q[],
447         (defined $appnamespace
448             ? (q[~] => qq[${appnamespace}::])
449             : ()
450         ),
451     }, @classes);
452 }
453
454 =head2 build_middleware (@args)
455
456 Internal application that converts a single middleware definition (see
457 L<Catalyst/psgi_middleware>) into an actual instance of middleware.
458
459 =cut
460
461 sub build_middleware {
462     my ($class, $namespace, @init_args) = @_;
463
464     if(
465       $namespace =~s/^\+// ||
466       $namespace =~/^Plack::Middleware/ ||
467       $namespace =~/^$class/
468     ) {  ## the string is a full namespace
469         return Class::Load::try_load_class($namespace) ?
470           $namespace->new(@init_args) :
471             die "Can't load class $namespace";
472     } else { ## the string is a partial namespace
473       if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace
474           my $ns = $class .'::Middleware::'. $namespace;
475           return $ns->new(@init_args);
476         } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
477           return "Plack::Middleware::$namespace"->new(@init_args);
478         } else {
479           die "Can't load middleware via '$namespace'.  It's not ".$class."::Middleware::".$namespace." or Plack::Middleware::$namespace";
480         }
481     }
482
483     return; ## be sure we can count on a proper return when valid
484 }
485
486 =head2 apply_registered_middleware ($psgi)
487
488 Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
489 around it and return the wrapped version.
490
491 This exists to deal with the fact Catalyst registered middleware can be
492 either an object with a wrap method or a coderef.
493
494 =cut
495
496 sub apply_registered_middleware {
497     my ($class, $psgi) = @_;
498     my $new_psgi = $psgi;
499     foreach my $middleware ($class->registered_middlewares) {
500         $new_psgi = Scalar::Util::blessed $middleware ?
501           $middleware->wrap($new_psgi) :
502             $middleware->($new_psgi);
503     }
504     return $new_psgi;
505 }
506
507 =head2 inject_component
508
509 Used to add components at runtime:
510
511     into        The Catalyst package to inject into (e.g. My::App)
512     component   The component package to inject
513     traits      (Optional) ArrayRef of L<Moose::Role>s that the componet should consume.
514     as          An optional moniker to use as the package name for the derived component
515
516 For example:
517
518     Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple )
519
520         The above will create 'My::App::Controller::Other::App::Controller::Apple'
521
522     Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple, as => Apple )
523
524         The above will create 'My::App::Controller::Apple'
525
526     Catalyst::Utils::inject_component( into => $myapp, component => 'MyRootV', as => 'Controller::Root' );
527
528 Will inject Controller, Model, and View components into your Catalyst application
529 at setup (run)time. It does this by creating a new package on-the-fly, having that
530 package extend the given component, and then having Catalyst setup the new component
531 (via $app->setup_component).
532
533 B<NOTE:> This is basically a core version of L<CatalystX::InjectComponent>.  If you were using that
534 you can now use this safely instead.  Going forward changes required to make this work will be
535 synchronized with the core method.
536
537 B<NOTE:> The 'traits' option is unique to the L<Catalyst::Utils> version of this feature.
538
539 B<NOTE:> These injected components really need to be a L<Catalyst::Component> and a L<Moose>
540 based class.
541
542 =cut
543
544 sub inject_component {
545     my %given = @_;
546     my ($into, $component, $as) = @given{qw/into component as/};
547
548     croak "No Catalyst (package) given" unless $into;
549     croak "No component (package) given" unless $component;
550
551     Class::Load::load_class($component);
552
553     $as ||= $component;
554     unless ( $as =~ m/^(?:Controller|Model|View)::/ || $given{skip_mvc_renaming} ) {
555         my $category;
556         for (qw/ Controller Model View /) {
557             if ( $component->isa( "Catalyst::$_" ) ) {
558                 $category = $_;
559                 last;
560             }
561         }
562         croak "Don't know what kind of component \"$component\" is" unless $category;
563         $as = "${category}::$as";
564     }
565     my $component_package = join '::', $into, $as;
566
567     unless ( Class::Load::is_class_loaded $component_package ) {
568         eval "package $component_package; use base qw/$component/; 1;" or
569             croak "Unable to build component package for \"$component_package\": $@";
570         Moose::Util::apply_all_roles($component_package, @{$given{traits}}) if $given{traits};
571         (my $file = "$component_package.pm") =~ s{::}{/}g;
572         $INC{$file} ||= 1;    
573     }
574
575     my $_setup_component = sub {
576       my $into = shift;
577       my $component_package = shift;
578       $into->components->{$component_package} = $into->setup_component( $component_package );
579     };
580
581     $_setup_component->( $into, $component_package );
582     for my $inner_component_package ( Devel::InnerPackage::list_packages( $component_package ) ) {
583         $_setup_component->( $into, $inner_component_package );
584     }
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;