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