670353fb6fdd2c0c0ee3f63cf56ce1830a085d39
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Utils.pm
1 package Catalyst::Utils;
2
3 use strict;
4 use Catalyst::Exception;
5 use File::Spec;
6 use HTTP::Request;
7 use Path::Class;
8 use URI;
9 use Class::Inspector;
10 use Carp qw/croak/;
11
12 =head1 NAME
13
14 Catalyst::Utils - The Catalyst Utils
15
16 =head1 SYNOPSIS
17
18 See L<Catalyst>.
19
20 =head1 DESCRIPTION
21
22 =head1 METHODS
23
24 =head2 appprefix($class)
25
26     MyApp::Foo becomes myapp_foo
27
28 =cut
29
30 sub appprefix {
31     my $class = shift;
32     $class =~ s/::/_/g;
33     $class = lc($class);
34     return $class;
35 }
36
37 =head2 class2appclass($class);
38
39     MyApp::Controller::Foo::Bar becomes MyApp
40     My::App::Controller::Foo::Bar becomes My::App
41
42 =cut
43
44 sub class2appclass {
45     my $class = shift || '';
46     my $appname = '';
47     if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
48         $appname = $1;
49     }
50     return $appname;
51 }
52
53 =head2 class2classprefix($class);
54
55     MyApp::Controller::Foo::Bar becomes MyApp::Controller
56     My::App::Controller::Foo::Bar becomes My::App::Controller
57
58 =cut
59
60 sub class2classprefix {
61     my $class = shift || '';
62     my $prefix;
63     if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
64         $prefix = $1;
65     }
66     return $prefix;
67 }
68
69 =head2 class2classsuffix($class);
70
71     MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
72
73 =cut
74
75 sub class2classsuffix {
76     my $class = shift || '';
77     my $prefix = class2appclass($class) || '';
78     $class =~ s/$prefix\:://;
79     return $class;
80 }
81
82 =head2 class2env($class);
83
84 Returns the environment name for class.
85
86     MyApp becomes MYAPP
87     My::App becomes MY_APP
88
89 =cut
90
91 sub class2env {
92     my $class = shift || '';
93     $class =~ s/::/_/g;
94     return uc($class);
95 }
96
97 =head2 class2prefix( $class, $case );
98
99 Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
100
101     My::App::Controller::Foo::Bar becomes foo/bar
102
103 =cut
104
105 sub class2prefix {
106     my $class = shift || '';
107     my $case  = shift || 0;
108     my $prefix;
109     if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
110         $prefix = $case ? $2 : lc $2;
111         $prefix =~ s{::}{/}g;
112     }
113     return $prefix;
114 }
115
116 =head2 class2tempdir( $class [, $create ] );
117
118 Returns a tempdir for a class. If create is true it will try to create the path.
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
125 sub class2tempdir {
126     my $class  = shift || '';
127     my $create = shift || 0;
128     my @parts = split '::', lc $class;
129
130     my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
131
132     if ( $create && !-e $tmpdir ) {
133
134         eval { $tmpdir->mkpath };
135
136         if ($@) {
137             Catalyst::Exception->throw(
138                 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
139         }
140     }
141
142     return $tmpdir->stringify;
143 }
144
145 =head2 home($class)
146
147 Returns home directory for given class.
148
149 =cut
150
151 sub home {
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} ) {
158         {
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$//;
163             my $home = dir($path)->absolute->cleanup;
164
165             # pop off /lib and /blib if they're there
166             $home = $home->parent while $home =~ /b?lib$/;
167
168             # only return the dir if it has a Makefile.PL or Build.PL
169             if (-f $home->file("Makefile.PL") or -f $home->file("Build.PL")) {
170
171                 # clean up relative path:
172                 # MyApp/script/.. -> MyApp
173
174                 my ($lastdir) = $home->dir_list( -1, 1 );
175                 if ( $lastdir eq '..' ) {
176                     $home = dir($home)->parent->parent;
177                 }
178
179                 return $home->stringify;
180             }
181         }
182
183         {
184             # look for an installed Catalyst app
185
186             # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
187             ( my $path = $inc_entry) =~ s/\.pm$//;
188             my $home = dir($path)->absolute->cleanup;
189
190             # return if if it's a valid directory
191             return $home->stringify if -d $home;
192         }
193     }
194
195     # we found nothing
196     return 0;
197 }
198
199 =head2 prefix($class, $name);
200
201 Returns a prefixed action.
202
203     MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
204
205 =cut
206
207 sub prefix {
208     my ( $class, $name ) = @_;
209     my $prefix = &class2prefix($class);
210     $name = "$prefix/$name" if $prefix;
211     return $name;
212 }
213
214 =head2 request($uri)
215
216 Returns an L<HTTP::Request> object for a uri.
217
218 =cut
219
220 sub request {
221     my $request = shift;
222     unless ( ref $request ) {
223         if ( $request =~ m/^http/i ) {
224             $request = URI->new($request);
225         }
226         else {
227             $request = URI->new( 'http://localhost' . $request );
228         }
229     }
230     unless ( ref $request eq 'HTTP::Request' ) {
231         $request = HTTP::Request->new( 'GET', $request );
232     }
233     return $request;
234 }
235
236 =head2 ensure_class_loaded($class_name, \%opts)
237
238 Loads the class unless it already has been loaded.
239
240 If $opts{ignore_loaded} is true always tries the require whether the package
241 already exists or not. Only pass this if you're either (a) sure you know the
242 file exists on disk or (b) have code to catch the file not found exception
243 that will result if it doesn't.
244
245 =cut
246
247 sub ensure_class_loaded {
248     my $class = shift;
249     my $opts  = shift;
250
251     croak "Malformed class Name $class"
252         if $class =~ m/(?:\b\:\b|\:{3,})/;
253
254     croak "Malformed class Name $class"
255         if $class =~ m/[^\w:]/;
256
257     croak "ensure_class_loaded should be given a classname, not a filename ($class)"
258         if $class =~ m/\.pm$/;
259
260     return if !$opts->{ ignore_loaded }
261         && Class::Inspector->loaded( $class ); # if a symbol entry exists we don't load again
262
263     # this hack is so we don't overwrite $@ if the load did not generate an error
264     my $error;
265     {
266         local $@;
267         my $file = $class . '.pm';
268         $file =~ s{::}{/}g;
269         eval { CORE::require($file) };
270         $error = $@;
271     }
272
273     die $error if $error;
274     die "require $class was successful but the package is not defined"
275         unless Class::Inspector->loaded($class);
276
277     return 1;
278 }
279
280 =head2 merge_hashes($hashref, $hashref)
281
282 Base code to recursively merge two hashes together with right-hand precedence.
283
284 =cut
285
286 sub merge_hashes {
287     my ( $lefthash, $righthash ) = @_;
288
289     return $lefthash unless defined $righthash;
290     
291     my %merged = %$lefthash;
292     for my $key ( keys %$righthash ) {
293         my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH';
294         my $left_ref  = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH';
295         if( $right_ref and $left_ref ) {
296             $merged{ $key } = merge_hashes(
297                 $lefthash->{ $key }, $righthash->{ $key }
298             );
299         }
300         else {
301             $merged{ $key } = $righthash->{ $key };
302         }
303     }
304     
305     return \%merged;
306 }
307
308 =head2 env_value($class, $key)
309
310 Checks for and returns an environment value. For instance, if $key is
311 'home', then this method will check for and return the first value it finds,
312 looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
313
314 =cut
315
316 sub env_value {
317     my ( $class, $key ) = @_;
318
319     $key = uc($key);
320     my @prefixes = ( class2env($class), 'CATALYST' );
321
322     for my $prefix (@prefixes) {
323         if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
324             return $value;
325         }
326     }
327
328     return;
329 }
330
331 =head1 AUTHOR
332
333 Sebastian Riedel, C<sri@cpan.org>
334 Yuval Kogman, C<nothingmuch@woobling.org>
335
336 =head1 COPYRIGHT
337
338 This program is free software, you can redistribute it and/or modify it under
339 the same terms as Perl itself.
340
341 =cut
342
343 1;