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