31773b7ce725226a17eac120cfa97206f3a0c266
[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
14 use namespace::clean;
15
16 =head1 NAME
17
18 Catalyst::Utils - The Catalyst Utils
19
20 =head1 SYNOPSIS
21
22 See L<Catalyst>.
23
24 =head1 DESCRIPTION
25
26 Catalyst Utilities.
27
28 =head1 METHODS
29
30 =head2 appprefix($class)
31
32     MyApp::Foo becomes myapp_foo
33
34 =cut
35
36 sub appprefix {
37     my $class = shift;
38     $class =~ s/::/_/g;
39     $class = lc($class);
40     return $class;
41 }
42
43 =head2 class2appclass($class);
44
45     MyApp::Controller::Foo::Bar becomes MyApp
46     My::App::Controller::Foo::Bar becomes My::App
47
48 =cut
49
50 sub class2appclass {
51     my $class = shift || '';
52     my $appname = '';
53     if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
54         $appname = $1;
55     }
56     return $appname;
57 }
58
59 =head2 class2classprefix($class);
60
61     MyApp::Controller::Foo::Bar becomes MyApp::Controller
62     My::App::Controller::Foo::Bar becomes My::App::Controller
63
64 =cut
65
66 sub class2classprefix {
67     my $class = shift || '';
68     my $prefix;
69     if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
70         $prefix = $1;
71     }
72     return $prefix;
73 }
74
75 =head2 class2classsuffix($class);
76
77     MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
78
79 =cut
80
81 sub class2classsuffix {
82     my $class = shift || '';
83     my $prefix = class2appclass($class) || '';
84     $class =~ s/$prefix\:://;
85     return $class;
86 }
87
88 =head2 class2env($class);
89
90 Returns the environment name for class.
91
92     MyApp becomes MYAPP
93     My::App becomes MY_APP
94
95 =cut
96
97 sub class2env {
98     my $class = shift || '';
99     $class =~ s/::/_/g;
100     return uc($class);
101 }
102
103 =head2 class2prefix( $class, $case );
104
105 Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
106
107     My::App::Controller::Foo::Bar becomes foo/bar
108
109 =cut
110
111 sub class2prefix {
112     my $class = shift || '';
113     my $case  = shift || 0;
114     my $prefix;
115     if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
116         $prefix = $case ? $2 : lc $2;
117         $prefix =~ s{::}{/}g;
118     }
119     return $prefix;
120 }
121
122 =head2 class2tempdir( $class [, $create ] );
123
124 Returns a tempdir for a class. If create is true it will try to create the path.
125
126     My::App becomes /tmp/my/app
127     My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar
128
129 =cut
130
131 sub class2tempdir {
132     my $class  = shift || '';
133     my $create = shift || 0;
134     my @parts = split '::', lc $class;
135
136     my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
137
138     if ( $create && !-e $tmpdir ) {
139
140         eval { $tmpdir->mkpath; 1 }
141         or do {
142             # don't load Catalyst::Exception as a BEGIN in Utils,
143             # because Utils often gets loaded before MyApp.pm, and if
144             # Catalyst::Exception is loaded before MyApp.pm, it does
145             # not honor setting
146             # $Catalyst::Exception::CATALYST_EXCEPTION_CLASS in
147             # MyApp.pm
148             require Catalyst::Exception;
149             Catalyst::Exception->throw(
150                 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
151         }
152     }
153
154     return $tmpdir->stringify;
155 }
156
157 =head2 home($class)
158
159 Returns home directory for given class.
160
161 =head2 dist_indicator_file_list
162
163 Returns a list of files which can be tested to check if you're inside
164 a CPAN distribution which is not yet installed.
165
166 These are:
167
168 =over
169
170 =item Makefile.PL
171
172 =item Build.PL
173
174 =item dist.ini
175
176 =item L<cpanfile>
177
178 =back
179
180 =cut
181
182 sub dist_indicator_file_list {
183     qw{Makefile.PL Build.PL dist.ini cpanfile};
184 }
185
186 sub home {
187     my $class = shift;
188
189     # make an $INC{ $key } style string from the class name
190     (my $file = "$class.pm") =~ s{::}{/}g;
191
192     if ( my $inc_entry = $INC{$file} ) {
193         {
194             # look for an uninstalled Catalyst app
195
196             # find the @INC entry in which $file was found
197             (my $path = $inc_entry) =~ s/$file$//;
198             $path ||= cwd() if !defined $path || !length $path;
199             my $home = dir($path)->absolute->cleanup;
200
201             # pop off /lib and /blib if they're there
202             $home = $home->parent while $home =~ /b?lib$/;
203
204             # only return the dir if it has a Makefile.PL or Build.PL or dist.ini
205             if (grep { -f $home->file($_) } dist_indicator_file_list()) {
206                 # clean up relative path:
207                 # MyApp/script/.. -> MyApp
208
209                 my $dir;
210                 my @dir_list = $home->dir_list();
211                 while (($dir = pop(@dir_list)) && $dir eq '..') {
212                     $home = dir($home)->parent->parent;
213                 }
214
215                 return $home->stringify;
216             }
217         }
218
219         {
220             # look for an installed Catalyst app
221
222             # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
223             ( my $path = $inc_entry) =~ s/\.pm$//;
224             my $home = dir($path)->absolute->cleanup;
225
226             # return if if it's a valid directory
227             return $home->stringify if -d $home;
228         }
229     }
230
231     # we found nothing
232     return 0;
233 }
234
235 =head2 prefix($class, $name);
236
237 Returns a prefixed action.
238
239     MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
240
241 =cut
242
243 sub prefix {
244     my ( $class, $name ) = @_;
245     my $prefix = &class2prefix($class);
246     $name = "$prefix/$name" if $prefix;
247     return $name;
248 }
249
250 =head2 request($uri)
251
252 Returns an L<HTTP::Request> object for a uri.
253
254 =cut
255
256 sub request {
257     my $request = shift;
258     unless ( ref $request ) {
259         if ( $request =~ m/^http/i ) {
260             $request = URI->new($request);
261         }
262         else {
263             $request = URI->new( 'http://localhost' . $request );
264         }
265     }
266     unless ( ref $request eq 'HTTP::Request' ) {
267         $request = HTTP::Request->new( 'GET', $request );
268     }
269     return $request;
270 }
271
272 =head2 ensure_class_loaded($class_name, \%opts)
273
274 Loads the class unless it already has been loaded.
275
276 If $opts{ignore_loaded} is true always tries the require whether the package
277 already exists or not. Only pass this if you're either (a) sure you know the
278 file exists on disk or (b) have code to catch the file not found exception
279 that will result if it doesn't.
280
281 =cut
282
283 sub ensure_class_loaded {
284     my $class = shift;
285     my $opts  = shift;
286
287     croak "Malformed class Name $class"
288         if $class =~ m/(?:\b\:\b|\:{3,})/;
289
290     croak "Malformed class Name $class"
291         if $class =~ m/[^\w:]/;
292
293     croak "ensure_class_loaded should be given a classname, not a filename ($class)"
294         if $class =~ m/\.pm$/;
295
296     # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even
297     # if it already has symbol table entries. This is to support things like Schema::Loader, which
298     # part-generate classes in memory, but then also load some of their contents from disk.
299     return if !$opts->{ ignore_loaded }
300         && is_class_loaded($class); # if a symbol entry exists we don't load again
301
302     # this hack is so we don't overwrite $@ if the load did not generate an error
303     my $error;
304     {
305         local $@;
306         my $file = $class . '.pm';
307         $file =~ s{::}{/}g;
308         eval { CORE::require($file) };
309         $error = $@;
310     }
311
312     die $error if $error;
313
314     warn "require $class was successful but the package is not defined."
315         unless is_class_loaded($class);
316
317     return 1;
318 }
319
320 =head2 merge_hashes($hashref, $hashref)
321
322 Base code to recursively merge two hashes together with right-hand precedence.
323
324 =cut
325
326 sub merge_hashes {
327     my ( $lefthash, $righthash ) = @_;
328
329     return $lefthash unless defined $righthash;
330
331     my %merged = %$lefthash;
332     for my $key ( keys %$righthash ) {
333         my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH';
334         my $left_ref  = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH';
335         if( $right_ref and $left_ref ) {
336             $merged{ $key } = merge_hashes(
337                 $lefthash->{ $key }, $righthash->{ $key }
338             );
339         }
340         else {
341             $merged{ $key } = $righthash->{ $key };
342         }
343     }
344
345     return \%merged;
346 }
347
348 =head2 env_value($class, $key)
349
350 Checks for and returns an environment value. For instance, if $key is
351 'home', then this method will check for and return the first value it finds,
352 looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
353
354 =cut
355
356 sub env_value {
357     my ( $class, $key ) = @_;
358
359     $key = uc($key);
360     my @prefixes = ( class2env($class), 'CATALYST' );
361
362     for my $prefix (@prefixes) {
363         if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
364             return $value;
365         }
366     }
367
368     return;
369 }
370
371 =head2 term_width
372
373 Try to guess terminal width to use with formatting of debug output
374
375 All you need to get this work, is:
376
377 1) Install Term::Size::Any, or
378
379 2) Export $COLUMNS from your shell.
380
381 (Warning to bash users: 'echo $COLUMNS' may be showing you the bash
382 variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
383 that 'env' now lists COLUMNS.)
384
385 As last resort, default value of 80 chars will be used.
386
387 =cut
388
389 my $_term_width;
390
391 sub term_width {
392     return $_term_width if $_term_width;
393
394     my $width;
395     eval '
396         require Term::Size::Any;
397         my ($columns, $rows) = Term::Size::Any::chars;
398         $width = $columns;
399         1;
400     ' or do {
401         $width = $ENV{COLUMNS}
402             if exists($ENV{COLUMNS})
403             && $ENV{COLUMNS} =~ m/^\d+$/;
404     };
405
406     $width = 80 unless ($width && $width >= 80);
407     return $_term_width = $width;
408 }
409
410
411 =head2 resolve_namespace
412
413 Method which adds the namespace for plugins and actions.
414
415   __PACKAGE__->setup(qw(MyPlugin));
416
417   # will load Catalyst::Plugin::MyPlugin
418
419 =cut
420
421
422 sub resolve_namespace {
423     my $appnamespace = shift;
424     my $namespace = shift;
425     my @classes = @_;
426     return String::RewritePrefix->rewrite({
427         q[]  => qq[${namespace}::],
428         q[+] => q[],
429         (defined $appnamespace
430             ? (q[~] => qq[${appnamespace}::])
431             : ()
432         ),
433     }, @classes);
434 }
435
436 =head2 build_middleware (@args)
437
438 Internal application that converts a single middleware definition (see
439 L<Catalyst/psgi_middleware>) into an actual instance of middleware.
440
441 =cut
442
443 sub build_middleware {
444     my ($class, $namespace, @init_args) = @_;
445
446     if(
447       $namespace =~s/^\+// ||
448       $namespace =~/^Plack::Middleware/ ||
449       $namespace =~/^$class/
450     ) {  ## the string is a full namespace
451         return Class::Load::try_load_class($namespace) ?
452           $namespace->new(@init_args) :
453             die "Can't load class $namespace";
454     } else { ## the string is a partial namespace
455       if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace
456           my $ns = $class .'::Middleware::'. $namespace;
457           return $ns->new(@init_args);
458         } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
459           return "Plack::Middleware::$namespace"->new(@init_args);
460         }
461     }
462
463     return; ## be sure we can count on a proper return when valid
464 }
465
466 =head2 apply_registered_middleware ($psgi)
467
468 Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
469 around it and return the wrapped version.
470
471 This exists to deal with the fact Catalyst registered middleware can be
472 either an object with a wrap method or a coderef.
473
474 =cut
475
476 sub apply_registered_middleware {
477     my ($class, $psgi) = @_;
478     my $new_psgi = $psgi;
479     foreach my $middleware ($class->registered_middlewares) {
480         $new_psgi = Scalar::Util::blessed $middleware ?
481           $middleware->wrap($new_psgi) :
482             $middleware->($new_psgi);
483     }
484     return $new_psgi;
485 }
486
487 =head1 AUTHORS
488
489 Catalyst Contributors, see Catalyst.pm
490
491 =head1 COPYRIGHT
492
493 This library is free software. You can redistribute it and/or modify it under
494 the same terms as Perl itself.
495
496 =cut
497
498 1;