X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FUtils.pm;h=31773b7ce725226a17eac120cfa97206f3a0c266;hp=c3145d3f8d03e5b726ef2e5cd8c99678ca73d166;hb=35b3434762d426e0cd5e04eb735291f8ca7ea08e;hpb=e2cc89a98938400ae5953b6d0ee79742768ed7df diff --git a/lib/Catalyst/Utils.pm b/lib/Catalyst/Utils.pm index c3145d3..31773b7 100644 --- a/lib/Catalyst/Utils.pm +++ b/lib/Catalyst/Utils.pm @@ -1,11 +1,17 @@ package Catalyst::Utils; use strict; -use Catalyst::Exception; use File::Spec; use HTTP::Request; use Path::Class; use URI; +use Carp qw/croak/; +use Cwd; +use Class::Load 'is_class_loaded'; +use String::RewritePrefix; +use Class::Load (); + +use namespace::clean; =head1 NAME @@ -17,69 +23,69 @@ See L. =head1 DESCRIPTION -=head1 METHODS +Catalyst Utilities. -=over 4 +=head1 METHODS -=item appprefix($class) +=head2 appprefix($class) - MyApp::Foo becomes myapp_foo + MyApp::Foo becomes myapp_foo =cut sub appprefix { my $class = shift; - $class =~ s/\:\:/_/g; + $class =~ s/::/_/g; $class = lc($class); return $class; } -=item class2appclass($class); +=head2 class2appclass($class); - MyApp::C::Foo::Bar becomes MyApp - My::App::C::Foo::Bar becomes My::App + MyApp::Controller::Foo::Bar becomes MyApp + My::App::Controller::Foo::Bar becomes My::App =cut sub class2appclass { my $class = shift || ''; my $appname = ''; - if ( $class =~ /^(.*)::([MVC]|Model|View|Controller)?::.*$/ ) { + if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) { $appname = $1; } return $appname; } -=item class2classprefix($class); +=head2 class2classprefix($class); - MyApp::C::Foo::Bar becomes MyApp::C - My::App::C::Foo::Bar becomes My::App::C + MyApp::Controller::Foo::Bar becomes MyApp::Controller + My::App::Controller::Foo::Bar becomes My::App::Controller =cut sub class2classprefix { my $class = shift || ''; my $prefix; - if ( $class =~ /^(.*::[MVC]|Model|View|Controller)?::.*$/ ) { + if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) { $prefix = $1; } return $prefix; } -=item class2classsuffix($class); +=head2 class2classsuffix($class); - MyApp::C::Foo::Bar becomes C::Foo::Bar + MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar =cut sub class2classsuffix { my $class = shift || ''; my $prefix = class2appclass($class) || ''; - $class =~ s/$prefix\:\://; + $class =~ s/$prefix\:://; return $class; } -=item class2env($class); +=head2 class2env($class); Returns the environment name for class. @@ -90,15 +96,15 @@ Returns the environment name for class. sub class2env { my $class = shift || ''; - $class =~ s/\:\:/_/g; + $class =~ s/::/_/g; return uc($class); } -=item class2prefix( $class, $case ); +=head2 class2prefix( $class, $case ); Returns the uri prefix for a class. If case is false the prefix is converted to lowercase. - My::App::C::Foo::Bar becomes foo/bar + My::App::Controller::Foo::Bar becomes foo/bar =cut @@ -106,19 +112,19 @@ sub class2prefix { my $class = shift || ''; my $case = shift || 0; my $prefix; - if ( $class =~ /^.*::([MVC]|Model|View|Controller)?::(.*)$/ ) { + if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) { $prefix = $case ? $2 : lc $2; - $prefix =~ s/\:\:/\//g; + $prefix =~ s{::}{/}g; } return $prefix; } -=item class2tempdir( $class [, $create ] ); +=head2 class2tempdir( $class [, $create ] ); Returns a tempdir for a class. If create is true it will try to create the path. My::App becomes /tmp/my/app - My::App::C::Foo::Bar becomes /tmp/my/app/c/foo/bar + My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar =cut @@ -131,9 +137,15 @@ sub class2tempdir { if ( $create && !-e $tmpdir ) { - eval { $tmpdir->mkpath }; - - if ($@) { + eval { $tmpdir->mkpath; 1 } + or do { + # don't load Catalyst::Exception as a BEGIN in Utils, + # because Utils often gets loaded before MyApp.pm, and if + # Catalyst::Exception is loaded before MyApp.pm, it does + # not honor setting + # $Catalyst::Exception::CATALYST_EXCEPTION_CLASS in + # MyApp.pm + require Catalyst::Exception; Catalyst::Exception->throw( message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ ); } @@ -142,44 +154,89 @@ sub class2tempdir { return $tmpdir->stringify; } -=item home($class) +=head2 home($class) Returns home directory for given class. +=head2 dist_indicator_file_list + +Returns a list of files which can be tested to check if you're inside +a CPAN distribution which is not yet installed. + +These are: + +=over + +=item Makefile.PL + +=item Build.PL + +=item dist.ini + +=item L + +=back + =cut +sub dist_indicator_file_list { + qw{Makefile.PL Build.PL dist.ini cpanfile}; +} + sub home { - my $name = shift; - $name =~ s/\:\:/\//g; - my $home = 0; - if ( my $path = $INC{"$name.pm"} ) { - $home = file($path)->absolute->dir; - $name =~ /(\w+)$/; - my $append = $1; - my $subdir = dir($home)->subdir($append); - for ( split '/', $name ) { $home = dir($home)->parent } - if ( $home =~ /blib$/ ) { $home = dir($home)->parent } - elsif (!-f file( $home, 'Makefile.PL' ) - && !-f file( $home, 'Build.PL' ) ) + my $class = shift; + + # make an $INC{ $key } style string from the class name + (my $file = "$class.pm") =~ s{::}{/}g; + + if ( my $inc_entry = $INC{$file} ) { { - $home = $subdir; + # look for an uninstalled Catalyst app + + # find the @INC entry in which $file was found + (my $path = $inc_entry) =~ s/$file$//; + $path ||= cwd() if !defined $path || !length $path; + my $home = dir($path)->absolute->cleanup; + + # pop off /lib and /blib if they're there + $home = $home->parent while $home =~ /b?lib$/; + + # only return the dir if it has a Makefile.PL or Build.PL or dist.ini + if (grep { -f $home->file($_) } dist_indicator_file_list()) { + # clean up relative path: + # MyApp/script/.. -> MyApp + + my $dir; + my @dir_list = $home->dir_list(); + while (($dir = pop(@dir_list)) && $dir eq '..') { + $home = dir($home)->parent->parent; + } + + return $home->stringify; + } } - # clean up relative path: - # MyApp/script/.. -> MyApp - my ($lastdir) = $home->dir_list( -1, 1 ); - if ( $lastdir eq '..' ) { - $home = dir($home)->parent->parent; + { + # look for an installed Catalyst app + + # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ ) + ( my $path = $inc_entry) =~ s/\.pm$//; + my $home = dir($path)->absolute->cleanup; + + # return if if it's a valid directory + return $home->stringify if -d $home; } } - return $home; + + # we found nothing + return 0; } -=item prefix($class, $name); +=head2 prefix($class, $name); Returns a prefixed action. - MyApp::C::Foo::Bar, yada becomes foo/bar/yada + MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada =cut @@ -190,7 +247,7 @@ sub prefix { return $name; } -=item request($uri) +=head2 request($uri) Returns an L object for a uri. @@ -199,11 +256,11 @@ Returns an L object for a uri. sub request { my $request = shift; unless ( ref $request ) { - if ( $request =~ m/http/i ) { - $request = URI->new($request)->canonical; + if ( $request =~ m/^http/i ) { + $request = URI->new($request); } else { - $request = URI->new( 'http://localhost' . $request )->canonical; + $request = URI->new( 'http://localhost' . $request ); } } unless ( ref $request eq 'HTTP::Request' ) { @@ -212,15 +269,228 @@ sub request { return $request; } -=back +=head2 ensure_class_loaded($class_name, \%opts) + +Loads the class unless it already has been loaded. + +If $opts{ignore_loaded} is true always tries the require whether the package +already exists or not. Only pass this if you're either (a) sure you know the +file exists on disk or (b) have code to catch the file not found exception +that will result if it doesn't. + +=cut + +sub ensure_class_loaded { + my $class = shift; + my $opts = shift; + + croak "Malformed class Name $class" + if $class =~ m/(?:\b\:\b|\:{3,})/; + + croak "Malformed class Name $class" + if $class =~ m/[^\w:]/; + + croak "ensure_class_loaded should be given a classname, not a filename ($class)" + if $class =~ m/\.pm$/; + + # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even + # if it already has symbol table entries. This is to support things like Schema::Loader, which + # part-generate classes in memory, but then also load some of their contents from disk. + return if !$opts->{ ignore_loaded } + && is_class_loaded($class); # if a symbol entry exists we don't load again + + # this hack is so we don't overwrite $@ if the load did not generate an error + my $error; + { + local $@; + my $file = $class . '.pm'; + $file =~ s{::}{/}g; + eval { CORE::require($file) }; + $error = $@; + } + + die $error if $error; + + warn "require $class was successful but the package is not defined." + unless is_class_loaded($class); + + return 1; +} + +=head2 merge_hashes($hashref, $hashref) + +Base code to recursively merge two hashes together with right-hand precedence. + +=cut + +sub merge_hashes { + my ( $lefthash, $righthash ) = @_; + + return $lefthash unless defined $righthash; + + my %merged = %$lefthash; + for my $key ( keys %$righthash ) { + my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH'; + my $left_ref = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH'; + if( $right_ref and $left_ref ) { + $merged{ $key } = merge_hashes( + $lefthash->{ $key }, $righthash->{ $key } + ); + } + else { + $merged{ $key } = $righthash->{ $key }; + } + } + + return \%merged; +} + +=head2 env_value($class, $key) + +Checks for and returns an environment value. For instance, if $key is +'home', then this method will check for and return the first value it finds, +looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}. + +=cut + +sub env_value { + my ( $class, $key ) = @_; + + $key = uc($key); + my @prefixes = ( class2env($class), 'CATALYST' ); + + for my $prefix (@prefixes) { + if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) { + return $value; + } + } + + return; +} + +=head2 term_width + +Try to guess terminal width to use with formatting of debug output + +All you need to get this work, is: + +1) Install Term::Size::Any, or + +2) Export $COLUMNS from your shell. + +(Warning to bash users: 'echo $COLUMNS' may be showing you the bash +variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see +that 'env' now lists COLUMNS.) + +As last resort, default value of 80 chars will be used. + +=cut + +my $_term_width; + +sub term_width { + return $_term_width if $_term_width; + + my $width; + eval ' + require Term::Size::Any; + my ($columns, $rows) = Term::Size::Any::chars; + $width = $columns; + 1; + ' or do { + $width = $ENV{COLUMNS} + if exists($ENV{COLUMNS}) + && $ENV{COLUMNS} =~ m/^\d+$/; + }; + + $width = 80 unless ($width && $width >= 80); + return $_term_width = $width; +} + + +=head2 resolve_namespace + +Method which adds the namespace for plugins and actions. + + __PACKAGE__->setup(qw(MyPlugin)); + + # will load Catalyst::Plugin::MyPlugin + +=cut + + +sub resolve_namespace { + my $appnamespace = shift; + my $namespace = shift; + my @classes = @_; + return String::RewritePrefix->rewrite({ + q[] => qq[${namespace}::], + q[+] => q[], + (defined $appnamespace + ? (q[~] => qq[${appnamespace}::]) + : () + ), + }, @classes); +} + +=head2 build_middleware (@args) + +Internal application that converts a single middleware definition (see +L) into an actual instance of middleware. + +=cut + +sub build_middleware { + my ($class, $namespace, @init_args) = @_; + + if( + $namespace =~s/^\+// || + $namespace =~/^Plack::Middleware/ || + $namespace =~/^$class/ + ) { ## the string is a full namespace + return Class::Load::try_load_class($namespace) ? + $namespace->new(@init_args) : + die "Can't load class $namespace"; + } else { ## the string is a partial namespace + if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace + my $ns = $class .'::Middleware::'. $namespace; + return $ns->new(@init_args); + } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder + return "Plack::Middleware::$namespace"->new(@init_args); + } + } + + return; ## be sure we can count on a proper return when valid +} + +=head2 apply_registered_middleware ($psgi) + +Given a $psgi reference, wrap all the L +around it and return the wrapped version. + +This exists to deal with the fact Catalyst registered middleware can be +either an object with a wrap method or a coderef. + +=cut + +sub apply_registered_middleware { + my ($class, $psgi) = @_; + my $new_psgi = $psgi; + foreach my $middleware ($class->registered_middlewares) { + $new_psgi = Scalar::Util::blessed $middleware ? + $middleware->wrap($new_psgi) : + $middleware->($new_psgi); + } + return $new_psgi; +} -=head1 AUTHOR +=head1 AUTHORS -Sebastian Riedel, C +Catalyst Contributors, see Catalyst.pm =head1 COPYRIGHT -This program is free software, you can redistribute it and/or modify it under +This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut