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