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