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