merge master
[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/;
2f381252 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
b5ecfcf0 171=head2 home($class)
812a28c9 172
173Returns home directory for given class.
174
175=cut
176
177sub home {
51f412bd 178 my $class = shift;
179
180 # make an $INC{ $key } style string from the class name
181 (my $file = "$class.pm") =~ s{::}{/}g;
182
183 if ( my $inc_entry = $INC{$file} ) {
51452916 184 {
51f412bd 185 # look for an uninstalled Catalyst app
186
187 # find the @INC entry in which $file was found
188 (my $path = $inc_entry) =~ s/$file$//;
2f381252 189 $path ||= cwd() if !defined $path || !length $path;
51f412bd 190 my $home = dir($path)->absolute->cleanup;
191
192 # pop off /lib and /blib if they're there
193 $home = $home->parent while $home =~ /b?lib$/;
194
b39b4848 195 # only return the dir if it has a Makefile.PL or Build.PL or dist.ini
196 if (-f $home->file("Makefile.PL") or -f $home->file("Build.PL")
197 or -f $home->file("dist.ini")) {
c09c6cd7 198
199 # clean up relative path:
200 # MyApp/script/.. -> MyApp
201
059c085b 202 my $dir;
203 my @dir_list = $home->dir_list();
204 while (($dir = pop(@dir_list)) && $dir eq '..') {
c09c6cd7 205 $home = dir($home)->parent->parent;
206 }
207
208 return $home->stringify;
209 }
51452916 210 }
4be535b1 211
51f412bd 212 {
213 # look for an installed Catalyst app
214
215 # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
216 ( my $path = $inc_entry) =~ s/\.pm$//;
217 my $home = dir($path)->absolute->cleanup;
218
219 # return if if it's a valid directory
220 return $home->stringify if -d $home;
62459712 221 }
812a28c9 222 }
51f412bd 223
224 # we found nothing
225 return 0;
812a28c9 226}
227
b5ecfcf0 228=head2 prefix($class, $name);
812a28c9 229
230Returns a prefixed action.
231
0ef447d8 232 MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
812a28c9 233
234=cut
235
236sub prefix {
237 my ( $class, $name ) = @_;
238 my $prefix = &class2prefix($class);
239 $name = "$prefix/$name" if $prefix;
240 return $name;
241}
242
b5ecfcf0 243=head2 request($uri)
4d60aa90 244
e2cc89a9 245Returns an L<HTTP::Request> object for a uri.
4d60aa90 246
247=cut
248
249sub request {
250 my $request = shift;
251 unless ( ref $request ) {
a88c7ec8 252 if ( $request =~ m/^http/i ) {
f4c0f6f7 253 $request = URI->new($request);
4d60aa90 254 }
255 else {
f4c0f6f7 256 $request = URI->new( 'http://localhost' . $request );
4d60aa90 257 }
258 }
259 unless ( ref $request eq 'HTTP::Request' ) {
260 $request = HTTP::Request->new( 'GET', $request );
261 }
4d60aa90 262 return $request;
263}
264
dd91afb5 265=head2 ensure_class_loaded($class_name, \%opts)
d9183506 266
267Loads the class unless it already has been loaded.
268
dd91afb5 269If $opts{ignore_loaded} is true always tries the require whether the package
270already exists or not. Only pass this if you're either (a) sure you know the
271file exists on disk or (b) have code to catch the file not found exception
272that will result if it doesn't.
273
d9183506 274=cut
275
276sub ensure_class_loaded {
277 my $class = shift;
d06051f7 278 my $opts = shift;
d9183506 279
5e5bd6df 280 croak "Malformed class Name $class"
281 if $class =~ m/(?:\b\:\b|\:{3,})/;
282
59ede84e 283 croak "Malformed class Name $class"
284 if $class =~ m/[^\w:]/;
285
286 croak "ensure_class_loaded should be given a classname, not a filename ($class)"
287 if $class =~ m/\.pm$/;
288
f55d1491 289 # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even
290 # if it already has symbol table entries. This is to support things like Schema::Loader, which
291 # part-generate classes in memory, but then also load some of their contents from disk.
d06051f7 292 return if !$opts->{ ignore_loaded }
fbedfd6b 293 && Class::MOP::is_class_loaded($class); # if a symbol entry exists we don't load again
294
d9183506 295 # this hack is so we don't overwrite $@ if the load did not generate an error
296 my $error;
297 {
298 local $@;
7a1958eb 299 my $file = $class . '.pm';
300 $file =~ s{::}{/}g;
301 eval { CORE::require($file) };
d9183506 302 $error = $@;
303 }
6bfff75e 304
d9183506 305 die $error if $error;
fbedfd6b 306
f55d1491 307 warn "require $class was successful but the package is not defined."
fbedfd6b 308 unless Class::MOP::is_class_loaded($class);
6bfff75e 309
310 return 1;
d9183506 311}
312
358e1592 313=head2 merge_hashes($hashref, $hashref)
314
315Base code to recursively merge two hashes together with right-hand precedence.
316
317=cut
318
319sub merge_hashes {
320 my ( $lefthash, $righthash ) = @_;
321
322 return $lefthash unless defined $righthash;
b0ad47c1 323
358e1592 324 my %merged = %$lefthash;
0ef447d8 325 for my $key ( keys %$righthash ) {
326 my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH';
327 my $left_ref = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH';
328 if( $right_ref and $left_ref ) {
358e1592 329 $merged{ $key } = merge_hashes(
330 $lefthash->{ $key }, $righthash->{ $key }
0ef447d8 331 );
358e1592 332 }
333 else {
334 $merged{ $key } = $righthash->{ $key };
0ef447d8 335 }
358e1592 336 }
b0ad47c1 337
358e1592 338 return \%merged;
339}
340
cb69249e 341=head2 env_value($class, $key)
342
343Checks for and returns an environment value. For instance, if $key is
344'home', then this method will check for and return the first value it finds,
345looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
346
347=cut
348
349sub env_value {
350 my ( $class, $key ) = @_;
351
352 $key = uc($key);
353 my @prefixes = ( class2env($class), 'CATALYST' );
354
355 for my $prefix (@prefixes) {
356 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
357 return $value;
358 }
359 }
360
361 return;
362}
d9183506 363
39fc2ce1 364=head2 term_width
365
366Try to guess terminal width to use with formatting of debug output
367
368All you need to get this work, is:
369
3701) Install Term::Size::Any, or
371
b0ad47c1 3722) Export $COLUMNS from your shell.
39fc2ce1 373
374(Warning to bash users: 'echo $COLUMNS' may be showing you the bash
b0ad47c1 375variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
39fc2ce1 376that 'env' now lists COLUMNS.)
377
378As last resort, default value of 80 chars will be used.
379
380=cut
381
382my $_term_width;
383
384sub term_width {
385 return $_term_width if $_term_width;
386
387 my $width = eval '
388 use Term::Size::Any;
389 my ($columns, $rows) = Term::Size::Any::chars;
390 return $columns;
391 ';
392
393 if ($@) {
394 $width = $ENV{COLUMNS}
395 if exists($ENV{COLUMNS})
396 && $ENV{COLUMNS} =~ m/^\d+$/;
397 }
398
399 $width = 80 unless ($width && $width >= 80);
400 return $_term_width = $width;
401}
402
17b3d800 403
404=head2 resolve_namespace
405
406Method which adds the namespace for plugins and actions.
407
408 __PACKAGE__->setup(qw(MyPlugin));
196932de 409
17b3d800 410 # will load Catalyst::Plugin::MyPlugin
411
412=cut
413
414
415sub resolve_namespace {
5d8129e9 416 my $appnamespace = shift;
17b3d800 417 my $namespace = shift;
418 my @classes = @_;
196932de 419 return String::RewritePrefix->rewrite({
420 q[] => qq[${namespace}::],
421 q[+] => q[],
422 (defined $appnamespace
423 ? (q[~] => qq[${appnamespace}::])
424 : ()
425 ),
426 }, @classes);
17b3d800 427}
428
429
2f381252 430=head1 AUTHORS
f05af9ba 431
2f381252 432Catalyst Contributors, see Catalyst.pm
f05af9ba 433
434=head1 COPYRIGHT
435
536bee89 436This library is free software. You can redistribute it and/or modify it under
f05af9ba 437the same terms as Perl itself.
438
439=cut
440
4411;