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