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