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