# Copyright (C) 2008-2009, Sebastian Riedel. package Mojo::Loader; use strict; use warnings; use base 'Mojo::Base'; use Carp qw/carp croak/; use File::Basename; use File::Spec; use constant DEBUG => $ENV{MOJO_LOADER_DEBUG} || 0; __PACKAGE__->attr([qw/base namespace/] => (chained => 1)); __PACKAGE__->attr(modules => (chained => 1, default => sub { [] })); my $STATS = {}; BEGIN { # Debugger sub tracking $^P |= 0x10; # Bug in pre-5.8.7 perl # http://rt.perl.org/rt3/Ticket/Display.html?id=35059 eval 'sub DB::sub' if $] < 5.008007; } # Homer no function beer well without. sub new { my ($class, $namespace) = @_; my $self = $class->SUPER::new(); $self->namespace($namespace); $self->search if $namespace; return $self; } sub build { my $self = shift; # Load and instantiate my @instances; foreach my $module (@{$self->modules}) { eval { if (my $base = $self->base) { die "SHORTCUT\n" unless $module->isa($base); } my $instance = $module->new(@_); push @instances, $instance; }; croak qq/Couldn't instantiate module "$module": $@/ if $@ && $@ ne "SHORTCUT\n"; } return \@instances; } sub load { my ($self, @modules) = @_; $self->modules(\@modules) if @modules; for my $module (@{$self->modules}) { # Shortcut next if $module->can('isa'); # Load eval "require $module"; croak qq/Couldn't load module "$module": $@/ if $@; } return $self; } sub load_build { my $self = shift; # Instantiate self $self = $self->new unless ref $self; # Load $self->load(shift); # Build my $instances = $self->build(@_); return $instances->[0]; } sub reload { while (my ($key, $file) = each %INC) { # Modified time my $mtime = (stat $file)[9]; # Startup time as default $STATS->{$file} = $^T unless defined $STATS->{$file}; # Modified? if ($mtime > $STATS->{$file}) { warn "\n$key -> $file modified, reloading!\n" if DEBUG; # Unload delete $INC{$key}; my @subs = grep { index($DB::sub{$_}, "$file:") == 0 } keys %DB::sub; for my $sub (@subs) { eval { undef &$sub }; carp "Can't unload sub '$sub' in '$file': $@" if $@; delete $DB::sub{$sub}; } # Reload eval { require $key }; carp "Can't reload '$file': $@" if $@; $STATS->{$file} = $mtime; } } } sub search { my ($self, $namespace) = @_; $namespace ||= $self->namespace; $self->namespace($namespace); # Directories my @directories = exists $INC{'blib.pm'} ? grep {/blib/} @INC : @INC; # Scan my %found; foreach my $directory (@directories) { my $path = File::Spec->catdir($directory, (split /::/, $namespace)); next unless (-e $path && -d $path); # Find opendir(my $dir, $path); my @files = grep /\.pm$/, readdir($dir); closedir($dir); for my $file (@files) { my $full = File::Spec->catfile(File::Spec->splitdir($path), $file); next if -d $full; my $name = File::Basename::fileparse($file, qr/\.pm/); my $class = "$namespace\::$name"; push @{$self->{modules}}, $class unless $found{$class}; $found{$class} ||= 1; } } return $self; } 1; __END__ =head1 NAME Mojo::Loader - Loader =head1 SYNOPSIS use Mojo::Loader; # Long my @instances = Mojo::Loader->new ->namespace('Some::Namespace') ->search ->load ->base('Some::Module') ->build; # Short my $something = Mojo::Loader->load_build('Some::Namespace'); # Reload Mojo::Loader->reload; =head1 DESCRIPTION L is a class loader and plugin framework. =head1 ATTRIBUTES =head2 C my $base = $loader->base; $loader = $loader->base('MyApp::Base'); =head2 C my $modules = $loader->modules; $loader = $loader->modules([qw/MyApp::Foo MyApp::Bar/]); =head2 C my $namespace = $loader->namespace; $loader = $loader->namespace('MyApp::Namespace'); =head1 METHODS L inherits all methods from L and implements the following new ones. =head2 C my $loader = Mojo::Loader->new; my $loader = Mojo::Loader->new('MyApp::Namespace'); =head2 C my $instances = $loader->build; my $instances = $loader->build(qw/foo bar baz/); =head2 C $loader = $loader->load; =head2 C my $instance = Mojo::Loader->load_build('MyApp'); my $instance = $loader->load_build('MyApp'); my $instance = Mojo::Loader->load_build('MyApp', qw/some args/); my $instance = $loader->load_build('MyApp', qw/some args/); =head2 C Mojo::Loader->reload; =head2 C $loader = $loader->search; $loader = $loader->search('MyApp::Namespace'); =cut