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