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