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