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