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