Fix for tests
[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/;
808db1d6 9use Class::MOP;
17b3d800 10use String::RewritePrefix;
0f519d62 11use List::MoreUtils qw/ any /;
202d7d9f 12use Cwd qw/ cwd /;
17b3d800 13
0db6e9d5 14use namespace::clean;
15
f05af9ba 16=head1 NAME
17
18Catalyst::Utils - The Catalyst Utils
19
20=head1 SYNOPSIS
21
22See L<Catalyst>.
23
24=head1 DESCRIPTION
25
b0ad47c1 26Catalyst Utilities.
39fc2ce1 27
f05af9ba 28=head1 METHODS
29
b5ecfcf0 30=head2 appprefix($class)
41ca9ba7 31
85d9fce6 32 MyApp::Foo becomes myapp_foo
41ca9ba7 33
34=cut
35
36sub appprefix {
37 my $class = shift;
0ef447d8 38 $class =~ s/::/_/g;
41ca9ba7 39 $class = lc($class);
40 return $class;
41}
42
b5ecfcf0 43=head2 class2appclass($class);
84cf74e7 44
0ef447d8 45 MyApp::Controller::Foo::Bar becomes MyApp
46 My::App::Controller::Foo::Bar becomes My::App
2d90477f 47
84cf74e7 48=cut
49
50sub class2appclass {
51 my $class = shift || '';
52 my $appname = '';
0ef447d8 53 if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
84cf74e7 54 $appname = $1;
55 }
56 return $appname;
57}
58
b5ecfcf0 59=head2 class2classprefix($class);
2930d610 60
0ef447d8 61 MyApp::Controller::Foo::Bar becomes MyApp::Controller
62 My::App::Controller::Foo::Bar becomes My::App::Controller
2d90477f 63
2930d610 64=cut
65
66sub class2classprefix {
67 my $class = shift || '';
68 my $prefix;
0ef447d8 69 if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
2930d610 70 $prefix = $1;
71 }
72 return $prefix;
73}
74
b5ecfcf0 75=head2 class2classsuffix($class);
84cf74e7 76
0ef447d8 77 MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
2d90477f 78
84cf74e7 79=cut
80
81sub class2classsuffix {
82 my $class = shift || '';
83 my $prefix = class2appclass($class) || '';
0ef447d8 84 $class =~ s/$prefix\:://;
84cf74e7 85 return $class;
86}
87
b5ecfcf0 88=head2 class2env($class);
3ad654e0 89
26e73131 90Returns the environment name for class.
3ad654e0 91
92 MyApp becomes MYAPP
93 My::App becomes MY_APP
94
95=cut
96
97sub class2env {
98 my $class = shift || '';
0ef447d8 99 $class =~ s/::/_/g;
3ad654e0 100 return uc($class);
101}
102
b5ecfcf0 103=head2 class2prefix( $class, $case );
f05af9ba 104
e2cc89a9 105Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
f05af9ba 106
0ef447d8 107 My::App::Controller::Foo::Bar becomes foo/bar
2d90477f 108
f05af9ba 109=cut
110
111sub class2prefix {
112 my $class = shift || '';
e494bd6b 113 my $case = shift || 0;
f05af9ba 114 my $prefix;
0ef447d8 115 if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
e494bd6b 116 $prefix = $case ? $2 : lc $2;
0ef447d8 117 $prefix =~ s{::}{/}g;
f05af9ba 118 }
119 return $prefix;
120}
121
b5ecfcf0 122=head2 class2tempdir( $class [, $create ] );
37a3ac5c 123
e2cc89a9 124Returns a tempdir for a class. If create is true it will try to create the path.
37a3ac5c 125
126 My::App becomes /tmp/my/app
7d7519a4 127 My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar
37a3ac5c 128
129=cut
130
131sub class2tempdir {
132 my $class = shift || '';
133 my $create = shift || 0;
4be535b1 134 my @parts = split '::', lc $class;
37a3ac5c 135
136 my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
137
4be535b1 138 if ( $create && !-e $tmpdir ) {
37a3ac5c 139
140 eval { $tmpdir->mkpath };
141
4be535b1 142 if ($@) {
41a8bf1f 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;
37a3ac5c 150 Catalyst::Exception->throw(
4be535b1 151 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
37a3ac5c 152 }
153 }
154
155 return $tmpdir->stringify;
156}
157
0f519d62 158=head2 dist_indicator_file_list
159
160Returns a list of files which can be tested to check if you're inside a checkout
161
162=cut
163
164sub dist_indicator_file_list {
f7ac80ef 165 qw/ Makefile.PL Build.PL dist.ini /;
0f519d62 166}
167
b5ecfcf0 168=head2 home($class)
812a28c9 169
170Returns home directory for given class.
171
03fb1bee 172Note that the class must be loaded for the home directory to be found using this function.
173
812a28c9 174=cut
175
176sub home {
51f412bd 177 my $class = shift;
178
179 # make an $INC{ $key } style string from the class name
180 (my $file = "$class.pm") =~ s{::}{/}g;
181
182 if ( my $inc_entry = $INC{$file} ) {
51452916 183 {
51f412bd 184 # look for an uninstalled Catalyst app
185
186 # find the @INC entry in which $file was found
187 (my $path = $inc_entry) =~ s/$file$//;
03fb1bee 188 my $home = find_home_unloaded_in_checkout($path);
189 return $home if $home;
51452916 190 }
4be535b1 191
51f412bd 192 {
193 # look for an installed Catalyst app
194
195 # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
196 ( my $path = $inc_entry) =~ s/\.pm$//;
197 my $home = dir($path)->absolute->cleanup;
198
199 # return if if it's a valid directory
200 return $home->stringify if -d $home;
62459712 201 }
812a28c9 202 }
51f412bd 203
204 # we found nothing
c47c7517 205 return;
812a28c9 206}
207
40e6e221 208=head2 find_home_unloaded_in_checkout ($path)
209
b9b257cc 210Tries to determine if C<$path> (or the current working directory if not supplied)
f7ac80ef 211looks like a checkout. Any leading lib, script or blib components
40e6e221 212will be removed, then the directory produced will be checked
d17ae380 213for the existence of a C<< dist_indicator_file_list() >>.
40e6e221 214
215If one is found, the directory will be returned, otherwise false.
216
217=cut
218
b9b257cc 219# XXX - Is this actually sane - should we just split into two simpler routines
220# one for when we do have an @INC entry and one for when we don't?
03fb1bee 221sub find_home_unloaded_in_checkout {
222 my ($path) = @_;
202d7d9f 223 $path ||= cwd() if !defined $path || !length $path;
03fb1bee 224 my $home = dir($path)->absolute->cleanup;
c47c7517 225 my $last_home;
202d7d9f 226 do {
227 # only return the dir if it has a Makefile.PL or Build.PL or dist.ini
228 if (any { $_ } map { -f $home->file($_) } dist_indicator_file_list()) {
229 # clean up relative path:
230 # MyApp/script/.. -> MyApp
231
232 my $dir;
233 my @dir_list = $home->dir_list();
234 while (($dir = pop(@dir_list)) && $dir eq '..') {
235 $home = dir($home)->parent->parent;
236 }
237 return $home->stringify;
03fb1bee 238 }
c47c7517 239 $last_home = $home;
202d7d9f 240 $home = $home->parent;
03fb1bee 241 }
202d7d9f 242 while # pop off /lib and /blib or /script or /t/ if they're there
c47c7517 243 ($last_home =~ /b?lib$/ || $last_home =~ /script$/ || $last_home =~ /\/t(\/|$)/);
03fb1bee 244}
245
b5ecfcf0 246=head2 prefix($class, $name);
812a28c9 247
248Returns a prefixed action.
249
0ef447d8 250 MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
812a28c9 251
252=cut
253
254sub prefix {
255 my ( $class, $name ) = @_;
256 my $prefix = &class2prefix($class);
257 $name = "$prefix/$name" if $prefix;
258 return $name;
259}
260
b5ecfcf0 261=head2 request($uri)
4d60aa90 262
e2cc89a9 263Returns an L<HTTP::Request> object for a uri.
4d60aa90 264
265=cut
266
267sub request {
268 my $request = shift;
269 unless ( ref $request ) {
a88c7ec8 270 if ( $request =~ m/^http/i ) {
f4c0f6f7 271 $request = URI->new($request);
4d60aa90 272 }
273 else {
f4c0f6f7 274 $request = URI->new( 'http://localhost' . $request );
4d60aa90 275 }
276 }
277 unless ( ref $request eq 'HTTP::Request' ) {
278 $request = HTTP::Request->new( 'GET', $request );
279 }
4d60aa90 280 return $request;
281}
282
dd91afb5 283=head2 ensure_class_loaded($class_name, \%opts)
d9183506 284
285Loads the class unless it already has been loaded.
286
dd91afb5 287If $opts{ignore_loaded} is true always tries the require whether the package
288already exists or not. Only pass this if you're either (a) sure you know the
289file exists on disk or (b) have code to catch the file not found exception
290that will result if it doesn't.
291
d9183506 292=cut
293
294sub ensure_class_loaded {
295 my $class = shift;
d06051f7 296 my $opts = shift;
d9183506 297
5e5bd6df 298 croak "Malformed class Name $class"
299 if $class =~ m/(?:\b\:\b|\:{3,})/;
300
59ede84e 301 croak "Malformed class Name $class"
302 if $class =~ m/[^\w:]/;
303
304 croak "ensure_class_loaded should be given a classname, not a filename ($class)"
305 if $class =~ m/\.pm$/;
306
f55d1491 307 # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even
308 # if it already has symbol table entries. This is to support things like Schema::Loader, which
309 # part-generate classes in memory, but then also load some of their contents from disk.
d06051f7 310 return if !$opts->{ ignore_loaded }
fbedfd6b 311 && Class::MOP::is_class_loaded($class); # if a symbol entry exists we don't load again
312
d9183506 313 # this hack is so we don't overwrite $@ if the load did not generate an error
314 my $error;
315 {
316 local $@;
7a1958eb 317 my $file = $class . '.pm';
318 $file =~ s{::}{/}g;
319 eval { CORE::require($file) };
d9183506 320 $error = $@;
321 }
6bfff75e 322
d9183506 323 die $error if $error;
fbedfd6b 324
f55d1491 325 warn "require $class was successful but the package is not defined."
fbedfd6b 326 unless Class::MOP::is_class_loaded($class);
6bfff75e 327
328 return 1;
d9183506 329}
330
358e1592 331=head2 merge_hashes($hashref, $hashref)
332
333Base code to recursively merge two hashes together with right-hand precedence.
334
335=cut
336
337sub merge_hashes {
338 my ( $lefthash, $righthash ) = @_;
339
340 return $lefthash unless defined $righthash;
b0ad47c1 341
358e1592 342 my %merged = %$lefthash;
0ef447d8 343 for my $key ( keys %$righthash ) {
344 my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH';
345 my $left_ref = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH';
346 if( $right_ref and $left_ref ) {
358e1592 347 $merged{ $key } = merge_hashes(
348 $lefthash->{ $key }, $righthash->{ $key }
0ef447d8 349 );
358e1592 350 }
351 else {
352 $merged{ $key } = $righthash->{ $key };
0ef447d8 353 }
358e1592 354 }
b0ad47c1 355
358e1592 356 return \%merged;
357}
358
cb69249e 359=head2 env_value($class, $key)
360
361Checks for and returns an environment value. For instance, if $key is
362'home', then this method will check for and return the first value it finds,
363looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
364
365=cut
366
367sub env_value {
368 my ( $class, $key ) = @_;
369
370 $key = uc($key);
371 my @prefixes = ( class2env($class), 'CATALYST' );
372
373 for my $prefix (@prefixes) {
374 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
375 return $value;
376 }
377 }
378
379 return;
380}
d9183506 381
39fc2ce1 382=head2 term_width
383
384Try to guess terminal width to use with formatting of debug output
385
386All you need to get this work, is:
387
3881) Install Term::Size::Any, or
389
b0ad47c1 3902) Export $COLUMNS from your shell.
39fc2ce1 391
392(Warning to bash users: 'echo $COLUMNS' may be showing you the bash
b0ad47c1 393variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
39fc2ce1 394that 'env' now lists COLUMNS.)
395
396As last resort, default value of 80 chars will be used.
397
398=cut
399
400my $_term_width;
401
402sub term_width {
403 return $_term_width if $_term_width;
404
405 my $width = eval '
406 use Term::Size::Any;
407 my ($columns, $rows) = Term::Size::Any::chars;
408 return $columns;
409 ';
410
411 if ($@) {
412 $width = $ENV{COLUMNS}
413 if exists($ENV{COLUMNS})
414 && $ENV{COLUMNS} =~ m/^\d+$/;
415 }
416
417 $width = 80 unless ($width && $width >= 80);
418 return $_term_width = $width;
419}
420
17b3d800 421
422=head2 resolve_namespace
423
424Method which adds the namespace for plugins and actions.
425
426 __PACKAGE__->setup(qw(MyPlugin));
196932de 427
17b3d800 428 # will load Catalyst::Plugin::MyPlugin
429
430=cut
431
432
433sub resolve_namespace {
5d8129e9 434 my $appnamespace = shift;
17b3d800 435 my $namespace = shift;
436 my @classes = @_;
196932de 437 return String::RewritePrefix->rewrite({
438 q[] => qq[${namespace}::],
439 q[+] => q[],
440 (defined $appnamespace
441 ? (q[~] => qq[${appnamespace}::])
442 : ()
443 ),
444 }, @classes);
17b3d800 445}
446
447
2f381252 448=head1 AUTHORS
f05af9ba 449
2f381252 450Catalyst Contributors, see Catalyst.pm
f05af9ba 451
452=head1 COPYRIGHT
453
536bee89 454This library is free software. You can redistribute it and/or modify it under
f05af9ba 455the same terms as Perl itself.
456
457=cut
458
4591;