merge trunk to moose-exporter branch
Dave Rolsky [Fri, 8 Aug 2008 16:15:58 +0000 (16:15 +0000)]
15 files changed:
Makefile.PL
lib/Moose.pm
lib/Moose/Cookbook/Extending/Recipe1.pod
lib/Moose/Cookbook/Extending/Recipe2.pod
lib/Moose/Exporter.pm [new file with mode: 0644]
lib/Moose/Role.pm
t/000_recipes/extending/001_base_class.t [new file with mode: 0644]
t/000_recipes/extending/002_metaclass_and_sugar.t [new file with mode: 0644]
t/010_basics/016_load_into_main.t
t/030_roles/002_role.t
t/030_roles/003_apply_role.t
t/030_roles/019_build.t
t/050_metaclasses/010_extending_and_embedding_back_compat.t [moved from t/050_metaclasses/010_extending_and_embedding.t with 100% similarity]
t/050_metaclasses/012_moose_exporter.t [new file with mode: 0644]
t/pod_coverage.t

index 4effc67..b54831a 100644 (file)
@@ -12,8 +12,9 @@ my $win32 = !! ( $^O eq 'Win32' or $^O eq 'cygwin' );
 # prereqs
 requires 'Scalar::Util' => $win32 ? '1.17' : '1.18';
 requires 'Carp';
-requires 'Class::MOP'    => '0.64';
-requires 'Sub::Exporter' => '0.972';
+requires 'Class::MOP'       => '0.64';
+requires 'List::MoreUtils';
+requires 'Sub::Exporter'    => '0.972';
 
 # only used by oose.pm, not Moose.pm :P
 requires 'Filter::Simple' => '0'; 
index a421c74..03e2a95 100644 (file)
@@ -10,7 +10,7 @@ our $AUTHORITY = 'cpan:STEVAN';
 use Scalar::Util 'blessed';
 use Carp         'confess', 'croak', 'cluck';
 
-use Sub::Exporter;
+use Moose::Exporter;
 
 use Class::MOP 0.64;
 
@@ -26,213 +26,124 @@ use Moose::Object;
 use Moose::Util::TypeConstraints;
 use Moose::Util ();
 
-{
-    my $CALLER;
-
-    my %exports = (
-        extends => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::extends' => sub (@) {
-                croak "Must derive at least one class" unless @_;
-        
-                my @supers = @_;
-                foreach my $super (@supers) {
-                    Class::MOP::load_class($super);
-                    croak "You cannot inherit from a Moose Role ($super)"
-                        if $super->can('meta')  && 
-                           blessed $super->meta &&
-                           $super->meta->isa('Moose::Meta::Role')
-                }
-
-
-
-                # this checks the metaclass to make sure
-                # it is correct, sometimes it can get out
-                # of sync when the classes are being built
-                my $meta = $class->meta->_fix_metaclass_incompatability(@supers);
-                $meta->superclasses(@supers);
-            });
-        },
-        with => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::with' => sub (@) {
-                Moose::Util::apply_all_roles($class->meta, @_)
-            });
-        },
-        has => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::has' => sub ($;%) {
-                my $name    = shift;
-                croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1;
-                my %options = @_;
-                my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ];
-                $class->meta->add_attribute( $_, %options ) for @$attrs;
-            });
-        },
-        before => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::before' => sub (@&) {
-                Moose::Util::add_method_modifier($class, 'before', \@_);
-            });
-        },
-        after => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::after' => sub (@&) {
-                Moose::Util::add_method_modifier($class, 'after', \@_);
-            });
-        },
-        around => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::around' => sub (@&) {
-                Moose::Util::add_method_modifier($class, 'around', \@_);
-            });
-        },
-        super => sub {
-            return Class::MOP::subname('Moose::super' => sub { 
-                return unless our $SUPER_BODY; $SUPER_BODY->(our @SUPER_ARGS) 
-            });
-        },
-        override => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::override' => sub ($&) {
-                my ( $name, $method ) = @_;
-                $class->meta->add_override_method_modifier( $name => $method );
-            });
-        },
-        inner => sub {
-            return Class::MOP::subname('Moose::inner' => sub {
-                my $pkg = caller();
-                our ( %INNER_BODY, %INNER_ARGS );
-
-                if ( my $body = $INNER_BODY{$pkg} ) {
-                    my @args = @{ $INNER_ARGS{$pkg} };
-                    local $INNER_ARGS{$pkg};
-                    local $INNER_BODY{$pkg};
-                    return $body->(@args);
-                } else {
-                    return;
-                }
-            });
-        },
-        augment => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::augment' => sub (@&) {
-                my ( $name, $method ) = @_;
-                $class->meta->add_augment_method_modifier( $name => $method );
-            });
-        },
-        make_immutable => sub {
-            my $class = $CALLER;
-            return Class::MOP::subname('Moose::make_immutable' => sub {
-                cluck "The make_immutable keyword has been deprecated, " . 
-                      "please go back to __PACKAGE__->meta->make_immutable\n";
-                $class->meta->make_immutable(@_);
-            });            
-        },        
-        confess => sub {
-            return \&Carp::confess;
-        },
-        blessed => sub {
-            return \&Scalar::Util::blessed;
-        },
-    );
-
-    my $exporter = Sub::Exporter::build_exporter(
-        {
-            exports => \%exports,
-            groups  => { default => [':all'] }
-        }
-    );
-
-    # 1 extra level because it's called by import so there's a layer of indirection
-    sub _get_caller{
-        my $offset = 1;
-        return
-            (ref $_[1] && defined $_[1]->{into})
-                ? $_[1]->{into}
-                : (ref $_[1] && defined $_[1]->{into_level})
-                    ? caller($offset + $_[1]->{into_level})
-                    : caller($offset);
-    }
-
-    sub import {
-        $CALLER = _get_caller(@_);
+sub extends {
+    my $class = shift;
 
-        # this works because both pragmas set $^H (see perldoc perlvar)
-        # which affects the current compilation - i.e. the file who use'd
-        # us - which is why we don't need to do anything special to make
-        # it affect that file rather than this one (which is already compiled)
+    croak "Must derive at least one class" unless @_;
 
-        strict->import;
-        warnings->import;
+    my @supers = @_;
+    foreach my $super (@supers) {
+        Class::MOP::load_class($super);
+        croak "You cannot inherit from a Moose Role ($super)"
+            if $super->can('meta')  && 
+               blessed $super->meta &&
+               $super->meta->isa('Moose::Meta::Role')
+    }
 
-        # we should never export to main
-        if ($CALLER eq 'main') {
-            warn qq{Moose does not export its sugar to the 'main' package.\n};
-            return;
-        }
 
-        init_meta( $CALLER, 'Moose::Object' );
 
-        goto $exporter;
-    }
-    
-    # NOTE:
-    # This is for special use by 
-    # some modules and stuff, I 
-    # dont know if it is sane enough
-    # to document actually.
-    # - SL
-    sub __CURRY_EXPORTS_FOR_CLASS__ {
-        $CALLER = shift;
-        ($CALLER ne 'Moose')
-            || croak "_import_into must be called a function, not a method";
-        ($CALLER->can('meta') && $CALLER->meta->isa('Class::MOP::Class'))
-            || croak "Cannot call _import_into on a package ($CALLER) without a metaclass";        
-        return map { $_ => $exports{$_}->() } (@_ ? @_ : keys %exports);
-    }
+    # this checks the metaclass to make sure
+    # it is correct, sometimes it can get out
+    # of sync when the classes are being built
+    my $meta = $class->meta->_fix_metaclass_incompatability(@supers);
+    $meta->superclasses(@supers);
+}
 
-    sub unimport {
-        my $class = _get_caller(@_);
+sub with {
+    my $class = shift;
+    Moose::Util::apply_all_roles($class->meta, @_);
+}
 
-        _remove_keywords(
-            source   => __PACKAGE__,
-            package  => $class,
-            keywords => [ keys %exports ],
-        );
-    }
+sub has {
+    my $class = shift;
+    my $name  = shift;
+    croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1;
+    my %options = @_;
+    my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ];
+    $class->meta->add_attribute( $_, %options ) for @$attrs;
+}
 
+sub before {
+    my $class = shift;
+    Moose::Util::add_method_modifier($class, 'before', \@_);
 }
 
-sub _remove_keywords {
-    my ( %args ) = @_;
+sub after {
+    my $class = shift;
+    Moose::Util::add_method_modifier($class, 'after', \@_);
+}
 
-    my $source  = $args{source};
-    my $package = $args{package};
+sub around {
+    my $class = shift;
+    Moose::Util::add_method_modifier($class, 'around', \@_);
+}
 
-    no strict 'refs';
+sub super {
+    return unless our $SUPER_BODY; $SUPER_BODY->(our @SUPER_ARGS);
+}
 
-    # loop through the keywords ...
-    foreach my $name ( @{ $args{keywords} } ) {
+sub override {
+    my $class = shift;
+    my ( $name, $method ) = @_;
+    $class->meta->add_override_method_modifier( $name => $method );
+}
 
-        # if we find one ...
-        if ( defined &{ $package . '::' . $name } ) {
-            my $keyword = \&{ $package . '::' . $name };
+sub inner {
+    my $pkg = caller();
+    our ( %INNER_BODY, %INNER_ARGS );
+
+    if ( my $body = $INNER_BODY{$pkg} ) {
+        my @args = @{ $INNER_ARGS{$pkg} };
+        local $INNER_ARGS{$pkg};
+        local $INNER_BODY{$pkg};
+        return $body->(@args);
+    } else {
+        return;
+    }
+}
 
-            # make sure it is from us
-            my ($pkg_name) = Class::MOP::get_code_info($keyword);
-            next if $pkg_name ne $source;
+sub augment {
+    my $class = shift;
+    my ( $name, $method ) = @_;
+    $class->meta->add_augment_method_modifier( $name => $method );
+}
 
-            # and if it is from us, then undef the slot
-            delete ${ $package . '::' }{$name};
-        }
-    }
+sub make_immutable {
+    my $class = shift;
+    cluck "The make_immutable keyword has been deprecated, " . 
+          "please go back to __PACKAGE__->meta->make_immutable\n";
+    $class->meta->make_immutable(@_);
 }
 
+my $exporter = Moose::Exporter->build_import_methods(
+    with_caller => [
+        qw( extends with has before after around override augment make_immutable )
+    ],
+    as_is => [
+        qw( super inner ),
+        \&Carp::confess,
+        \&Scalar::Util::blessed,
+    ],
+);
+
 sub init_meta {
-    my ( $class, $base_class, $metaclass ) = @_;
-    $base_class = 'Moose::Object'      unless defined $base_class;
-    $metaclass  = 'Moose::Meta::Class' unless defined $metaclass;
+    # This used to be called as a function. This hack preserves
+    # backwards compatibility.
+    if ( $_[0] ne __PACKAGE__ ) {
+        return __PACKAGE__->init_meta(
+            for_class  => $_[0],
+            base_class => $_[1],
+            metaclass  => $_[2],
+        );
+    }
+
+    shift;
+    my %args = @_;
+
+    my $class = $args{for_class}
+        or confess "Cannot call init_meta without specifying a for_class";
+    my $base_class = $args{base_class} || 'Moose::Object';
+    my $metaclass  = $args{metaclass}  || 'Moose::Meta::Class';
 
     confess
         "The Metaclass $metaclass must be a subclass of Moose::Meta::Class."
@@ -272,9 +183,15 @@ sub init_meta {
     $meta->superclasses($base_class)
       unless $meta->superclasses();
 
+
     return $meta;
 }
 
+# This may be used in some older MooseX extensions.
+sub _get_caller {
+    goto &Moose::Exporter::_get_caller;
+}
+
 ## make 'em all immutable
 
 $_->meta->make_immutable(
@@ -796,45 +713,72 @@ to work. Here is an example:
 
 =head1 EXTENDING AND EMBEDDING MOOSE
 
-Moose also offers some options for extending or embedding it into your own
-framework. The basic premise is to have something that sets up your class'
-metaclass and export the moose declarators (C<has>, C<with>, C<extends>,...).
-Here is an example:
+Moose also offers some options for extending or embedding it into your
+own framework. There are several things you might want to do as part
+of such a framework. First, you probably want to export Moose's sugar
+functions (C<has>, C<extends>, etc) for users of the
+framework. Second, you may want to provide additional sugar of your
+own. Third, you may want to provide your own object base class instead
+of L<Moose::Object>, and/or your own metaclass class instead of
+L<Moose::Meta::Class>.
 
-    package MyFramework;
-    use Moose;
+The exporting needs can be asily satisfied by using
+L<Moose::Exporter>, which is what C<Moose.pm> itself uses for
+exporting. L<Moose::Exporter> lets you "export like Moose".
+
+If you define an C<init_meta> method in a module that uses
+L<Moose::Exporter>, then this method will be called I<before>
+C<Moose.pm>'s own C<init_meta>. This gives you a chance to provide an
+alternate object base class or metaclass class.
+
+Here is a simple example:
 
-    sub import {
-        my $CALLER = caller();
+    package MyFramework;
 
-        strict->import;
-        warnings->import;
+    use strict;
+    use warnings;
 
-        # we should never export to main
-        return if $CALLER eq 'main';
-        Moose::init_meta( $CALLER, 'MyFramework::Base' );
-        Moose->import({into => $CALLER});
+    use Moose (); # no need to get Moose's exports
+    use Moose::Exporter;
 
-        # Do my custom framework stuff
+    Moose::Exporter->build_import_methods( also => 'Moose' );
 
-        return 1;
+    sub init_meta {
+        shift;
+        return Moose->init_meta( @_, base_class => 'MyFramework::Base' );
     }
 
-=head2 B<import>
+In this example, any class that includes C<use MyFramework> will get
+all of C<Moose.pm>'s sugar functions, and will have their superclass
+set to C<MyFramework::Base>.
 
-Moose's C<import> method supports the L<Sub::Exporter> form of C<{into =E<gt> $pkg}>
-and C<{into_level =E<gt> 1}>
+Additionally, that class can include C<no MyFramework> to unimport
 
-=head2 B<init_meta ($class, $baseclass, $metaclass)>
+=head2 B<< Moose->init_meta(for_class => $class, base_class => $baseclass, metaclass => $metaclass) >>
 
-Moose does some boot strapping: it creates a metaclass object for your class,
-and then injects a C<meta> accessor into your class to retrieve it. Then it
-sets your baseclass to Moose::Object or the value you pass in unless you already
-have one. This is all done via C<init_meta> which takes the name of your class
-and optionally a baseclass and a metaclass as arguments.
+The C<init_meta> method sets up the metaclass object for the class
+specified by C<for_class>. It also injects a a C<meta> accessor into
+the class so you can get at this object. It also sets the class's
+superclass to C<base_class>, with L<Moose::Object> as the default.
+
+You can specify an alternate metaclass with the C<metaclass> parameter.
 
 For more detail on this topic, see L<Moose::Cookbook::Extending::Recipe2>.
 
+This method used to be documented as a function which accepted
+positional parameters. This calling style will still work for
+backwards compatibility.
+
+=head2 B<import>
+
+Moose's C<import> method supports the L<Sub::Exporter> form of C<{into =E<gt> $pkg}>
+and C<{into_level =E<gt> 1}>.
+
+B<NOTE>: Doing this is more or less deprecated. Use L<Moose::Exporter>
+instead, which lets you stack multiple C<Moose.pm>-alike modules
+sanely. It handles getting the exported functions into the right place
+for you.
+
 =head1 CAVEATS
 
 =over 4
index 2be1127..8dc84aa 100644 (file)
@@ -18,23 +18,13 @@ Moose::Cookbook::Extending::Recipe1 - Providing an alternate base object class
 
   package MyApp::UseMyBase;
   use Moose ();
+  use Moose::Exporter;
 
-  sub import {
-    my $caller = caller();
+  Moose::Exporter->build_import_methods( also => 'Moose' );
 
-    return if $caller eq 'main';
-
-    Moose::init_meta( $caller,
-                      'MyApp::Object',
-                    );
-
-    Moose->import( { into => $caller }, @_ );
-  }
-
-  sub unimport {
-    my $caller = caller();
-
-    Moose->unimport( { into => $caller }, @_ );
+  sub init_meta {
+      shift;
+      Moose->init_meta( @_, base_class => 'MyApp::Object' );
   }
 
 =head1 DESCRIPTION
@@ -53,6 +43,32 @@ In this particular example, our base class issues some debugging
 output every time a new object is created, but you can surely think of
 some more interesting things to do with your own base class.
 
+This all works because of the magic of L<Moose::Exporter>. When we
+call C<< Moose::Exporter->build_import_methods( also => 'Moose' ) >>
+it builds an C<import> and C<unimport> method for you. The C<< also =>
+'Moose' >> bit says that we want to export everything that Moose does.
+
+The C<import> method that gets created will call our C<init_meta>
+method, passing it C<< for_caller => $caller >> as its arguments. The
+C<$caller> is set to the class that actually imported us in the first
+place.
+
+See the L<Moose::Exporter> docs for more details on its API.
+
+=head1 USING MyApp::UseMyBase
+
+To actually use our new base class, we simply use C<MyApp::UseMyBase>
+I<instead> of C<Moose>. We get all the Moose sugar plus our new base
+class.
+
+  package Foo;
+
+  use MyApp::UseMyBase;
+
+  has 'size' => ( is => 'rw' );
+
+  no MyApp::UseMyBase;
+
 =head1 AUTHOR
 
 Dave Rolsky E<lt>autarch@urth.orgE<gt>
index eeec31e..6f65169 100644 (file)
@@ -12,50 +12,27 @@ Moose::Cookbook::Extending::Recipe2 - Acting like Moose.pm and providing sugar M
   use strict;
   use warnings;
 
-  our @EXPORT = qw( has_table );
-
-  use base 'Exporter';
-  use Class::MOP;
   use Moose ();
+  use Moose::Exporter;
 
-  sub import {
-    my $caller = caller();
-
-    return if $caller eq 'main';
-
-    Moose::init_meta(
-        $caller,
-        undef,    # object base class
-        'MyApp::Meta::Class',
-    );
-
-    Moose->import( { into => $caller }, @_ );
-
-    __PACKAGE__->export_to_level( 1, @_ );
-  }
-
-  sub unimport {
-      my $caller = caller();
-
-      Moose::_remove_keywords(
-          source   => __PACKAGE__,
-          package  => $caller,
-          keywords => \@EXPORT,
-      );
+  Moose::Exporter->build_import_methods(
+      with_caller => ['has_table'],
+      also        => 'Moose',
+  );
 
-      Moose->unimport( { into_level => 1 } );
+  sub init_meta {
+      shift;
+      Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' );
   }
 
   sub has_table {
-      my $caller = caller();
-
+      my $caller = shift;
       $caller->meta()->table(shift);
   }
 
 =head1 DESCRIPTION
 
-The code above shows what it takes to provide an import-based
-interface just like C<Moose.pm>. This recipe builds on
+This recipe expands on the use of L<Moose::Exporter> we saw in
 L<Moose::Cookbook::Extending::Recipe1>. Instead of providing our own
 object base class, we provide our own metaclass class, and we also
 export a sugar subroutine C<has_table()>.
@@ -64,14 +41,14 @@ Given the above code, you can now replace all instances of C<use
 Moose> with C<use MyApp::Mooseish>. Similarly, C<no Moose> is now
 replaced with C<no MyApp::Mooseish>.
 
-=head1 WARNING
+The C<with_caller> parameter specifies a list of functions that should
+be wrapped before exporting. The wrapper simply ensures that the
+importing package name is the first argument to the function, so we
+can do C<S<my $caller = shift;>>.
 
-This recipe covers a fairly undocumented and ugly part of Moose, and
-the techniques described here may be deprecated in a future
-release. If this happens, there will be plenty of warning, as a number
-of C<MooseX> modules on CPAN already use these techniques.
+See the L<Moose::Exporter> docs for more details on its API.
 
-=head1 HOW IT IS USED
+=head1 USING MyApp::Mooseish
 
 The purpose of all this code is to provide a Moose-like
 interface. Here's what it would look like in actual use:
@@ -82,8 +59,8 @@ interface. Here's what it would look like in actual use:
 
   has_table 'User';
 
-  has 'username';
-  has 'password';
+  has 'username' => ( is => 'ro' );
+  has 'password' => ( is => 'ro' );
 
   sub login { ... }
 
@@ -92,47 +69,6 @@ interface. Here's what it would look like in actual use:
 All of the normal Moose sugar (C<has()>, C<with()>, etc) is available
 when you C<use MyApp::Mooseish>.
 
-=head1 DISSECTION
-
-The first bit of magic is the call to C<Moose::init_meta()>. What this
-does is create a metaclass for the specified class. Normally, this is
-called by C<Moose.pm> in its own C<import()> method. However, we can
-call it first in order to provide an alternate metaclass class. We
-could also provide an alternate base object class to replace
-C<Moose::Object> (see L<Moose::Cookbook::Extending::Recipe1> for an
-example).
-
-The C<Moose::init_meta()> call takes three parameters. The first is
-the class for which we are initializing a metaclass object. The second
-is the base object, which is L<Moose::Object> by default. The third
-argument is the metaclass class, which is C<Moose::Meta::Class> by
-default.
-
-The next bit of magic is this:
-
-  Moose->import( { into => $caller } );
-
-This use of "into" is actually part of the C<Sub::Exporter> API, which
-C<Moose.pm> uses internally to export things like C<has()> and
-C<extends()>.
-
-Finally, we call C<< __PACKAGE__->export_to_level() >>. This method
-actually comes from C<Exporter>.
-
-This is all a bit fragile since it doesn't stack terribly well. You
-can basically only have one Moose-alike module. This may be fixed in
-the still-notional C<MooseX::Exporter> module someday.
-
-The C<unimport()> subroutine calls the C<_remove_keywords> function
-from Moose.  This function removes only the keywords exported by
-this module. More precisely, C<_remove_keywords> removes from the
-C<package> package the keywords given by the C<keywords> argument
-that were created in the C<source> package. This functionality may
-be deprecated if L<Sub::Exporter> begins providing it.
-
-Finally, we have our C<has_table()> subroutine. This provides a bit of
-sugar that looks a lot like C<has()>.
-
 =head1 AUTHOR
 
 Dave Rolsky E<lt>autarch@urth.orgE<gt>
diff --git a/lib/Moose/Exporter.pm b/lib/Moose/Exporter.pm
new file mode 100644 (file)
index 0000000..3b90584
--- /dev/null
@@ -0,0 +1,366 @@
+package Moose::Exporter;
+
+use strict;
+use warnings;
+
+use Class::MOP;
+use List::MoreUtils qw( uniq );
+use Sub::Exporter;
+
+
+my %EXPORT_SPEC;
+
+sub build_import_methods {
+    my $class = shift;
+    my %args  = @_;
+
+    my $exporting_package = caller();
+
+    $EXPORT_SPEC{$exporting_package} = \%args;
+
+    my @exports_from = $class->_follow_also( $exporting_package );
+
+    my $exports
+        = $class->_process_exports( $exporting_package, @exports_from );
+
+    my $exporter = Sub::Exporter::build_exporter(
+        {
+            exports => $exports,
+            groups  => { default => [':all'] }
+        }
+    );
+
+    my $import = $class->_make_import_sub( $exporter, \@exports_from );
+
+    my $unimport = $class->_make_unimport_sub( \@exports_from, [ keys %{$exports} ] );
+
+    no strict 'refs';
+    *{ $exporting_package . '::import' }   = $import;
+    *{ $exporting_package . '::unimport' } = $unimport;
+}
+
+{
+    my %seen;
+
+    sub _follow_also {
+        my $class             = shift;
+        my $exporting_package = shift;
+
+        %seen = ( $exporting_package => 1 );
+
+        return uniq( _follow_also_real($exporting_package) );
+    }
+
+    sub _follow_also_real {
+        my $exporting_package = shift;
+
+        die "Package in also ($exporting_package) does not seem to use MooseX::Exporter"
+            unless exists $EXPORT_SPEC{$exporting_package};
+
+        my $also = $EXPORT_SPEC{$exporting_package}{also};
+
+        return unless defined $also;
+
+        my @also = ref $also ? @{$also} : $also;
+
+        for my $package (@also)
+        {
+            die "Circular reference in also parameter to MooseX::Exporter between $exporting_package and $package"
+                if $seen{$package};
+
+            $seen{$package} = 1;
+        }
+
+        return @also, map { _follow_also_real($_) } @also;
+    }
+}
+
+sub _process_exports {
+    my $class    = shift;
+    my @packages = @_;
+
+    my %exports;
+
+    for my $package (@packages) {
+        my $args = $EXPORT_SPEC{$package}
+            or die "The $package package does not use Moose::Exporter\n";
+
+        for my $name ( @{ $args->{with_caller} } ) {
+            my $sub = do {
+                no strict 'refs';
+                \&{ $package . '::' . $name };
+            };
+
+            $exports{$name} = $class->_make_wrapped_sub(
+                $package,
+                $name,
+                $sub
+            );
+        }
+
+        for my $name ( @{ $args->{as_is} } ) {
+            my $sub;
+
+            if ( ref $name ) {
+                $sub  = $name;
+                $name = ( Class::MOP::get_code_info($name) )[1];
+            }
+            else {
+                $sub = do {
+                    no strict 'refs';
+                    \&{ $package . '::' . $name };
+                };
+            }
+
+            $exports{$name} = sub {$sub};
+        }
+    }
+
+    return \%exports;
+}
+
+{
+    # This variable gets closed over in each export _generator_. Then
+    # in the generator we grab the value and close over it _again_ in
+    # the real export, so it gets captured each time the generator
+    # runs.
+    #
+    # In the meantime, we arrange for the import method we generate to
+    # set this variable to the caller each time it is called.
+    #
+    # This is all a bit confusing, but it works.
+    my $CALLER;
+
+    sub _make_wrapped_sub {
+        my $class             = shift;
+        my $exporting_package = shift;
+        my $name              = shift;
+        my $sub               = shift;
+
+        # We need to set the package at import time, so that when
+        # package Foo imports has(), we capture "Foo" as the
+        # package. This lets other packages call Foo::has() and get
+        # the right package. This is done for backwards compatibility
+        # with existing production code, not because this is a good
+        # idea ;)
+        return sub {
+            my $caller = $CALLER;
+            Class::MOP::subname( $exporting_package . '::'
+                    . $name => sub { $sub->( $caller, @_ ) } );
+        };
+    }
+
+    sub _make_import_sub {
+        shift;
+        my $exporter     = shift;
+        my $exports_from = shift;
+
+        return sub {
+
+            # It's important to leave @_ as-is for the benefit of
+            # Sub::Exporter.
+            my $class = $_[0];
+
+            $CALLER = Moose::Exporter::_get_caller(@_);
+
+            # this works because both pragmas set $^H (see perldoc
+            # perlvar) which affects the current compilation -
+            # i.e. the file who use'd us - which is why we don't need
+            # to do anything special to make it affect that file
+            # rather than this one (which is already compiled)
+
+            strict->import;
+            warnings->import;
+
+            # we should never export to main
+            if ( $CALLER eq 'main' ) {
+                warn
+                    qq{$class does not export its sugar to the 'main' package.\n};
+                return;
+            }
+
+            for my $c (grep { $_->can('init_meta') } $class, @{$exports_from} ) {
+
+                $c->init_meta( for_class => $CALLER );
+            }
+
+            goto $exporter;
+        };
+    }
+}
+
+sub _get_caller {
+    # 1 extra level because it's called by import so there's a layer
+    # of indirection
+    my $offset = 1;
+
+    return
+          ( ref $_[1] && defined $_[1]->{into} ) ? $_[1]->{into}
+        : ( ref $_[1] && defined $_[1]->{into_level} )
+        ? caller( $offset + $_[1]->{into_level} )
+        : caller($offset);
+}
+
+sub _make_unimport_sub {
+    shift;
+    my $sources  = shift;
+    my $keywords = shift;
+
+    return sub {
+        my $class  = shift;
+        my $caller = scalar caller();
+        Moose::Exporter->_remove_keywords(
+            $caller,
+            [ $class, @{$sources} ],
+            $keywords
+        );
+    };
+}
+
+sub _remove_keywords {
+    shift;
+    my $package  = shift;
+    my $sources  = shift;
+    my $keywords = shift;
+
+    my %sources = map { $_ => 1 } @{$sources};
+
+    no strict 'refs';
+
+    # loop through the keywords ...
+    foreach my $name ( @{$keywords} ) {
+
+        # if we find one ...
+        if ( defined &{ $package . '::' . $name } ) {
+            my $keyword = \&{ $package . '::' . $name };
+
+            # make sure it is from us
+            my ($pkg_name) = Class::MOP::get_code_info($keyword);
+            next unless $sources{$pkg_name};
+
+            # and if it is from us, then undef the slot
+            delete ${ $package . '::' }{$name};
+        }
+    }
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Moose::Exporter - make an import() and unimport() just like Moose.pm
+
+=head1 SYNOPSIS
+
+  package MyApp::Moose;
+
+  use strict;
+  use warnings;
+
+  use Moose ();
+  use Moose::Exporter;
+
+  Moose::Exporter->build_export_methods(
+      export         => [ 'sugar1', 'sugar2', \&Some::Random::thing ],
+      init_meta_args => { metaclass_class => 'MyApp::Meta::Class' ],
+  );
+
+  # then later ...
+  package MyApp::User;
+
+  use MyApp::Moose;
+
+  has 'name';
+  sugar1 'do your thing';
+  thing;
+
+  no MyApp::Moose;
+
+=head1 DESCRIPTION
+
+This module encapsulates the logic to export sugar functions like
+C<Moose.pm>. It does this by building custom C<import> and C<unimport>
+methods for your module, based on a spec your provide.
+
+It also lets your "stack" Moose-alike modules so you can export
+Moose's sugar as well as your own, along with sugar from any random
+C<MooseX> module, as long as they all use C<Moose::Exporter>.
+
+=head1 METHODS
+
+This module provides exactly one public method:
+
+=head2 Moose::Exporter->build_import_methods(...)
+
+When you call this method, C<Moose::Exporter> build custom C<import>
+and C<unimport> methods for your module. The import method will export
+the functions you specify, and you can also tell it to export
+functions exported by some other module (like C<Moose.pm>).
+
+The C<unimport> method cleans the callers namespace of all the
+exported functions.
+
+This method accepts the following parameters:
+
+=over 4
+
+=item * with_caller => [ ... ]
+
+This a list of function I<names only> to be exported wrapped and then
+exported. The wrapper will pass the name of the calling package as the
+first argument to the function. Many sugar functions need to know
+their caller so they can get the calling package's metaclass object.
+
+=item * as_is => [ ... ]
+
+This a list of function names or sub references to be exported
+as-is. You can identify a subroutine by reference, which is handy to
+re-export some other module's functions directly by reference
+(C<\&Some::Package::function>).
+
+=item * also => $name or \@names
+
+This is a list of modules which contain functions that the caller
+wants to export. These modules must also use C<Moose::Exporter>. The
+most common use case will be to export the functions from C<Moose.pm>.
+
+C<Moose::Exporter> also makes sure all these functions get removed
+when C<unimport> is called.
+
+=back
+
+=head1 IMPORTING AND init_meta
+
+If you want to set an alternative base object class or metaclass
+class, simply define an C<init_meta> method in your class. The
+C<import> method that C<Moose::Exporter> generates for you will call
+this method (if it exists). It will always pass the caller to this
+method via the C<for_class> parameter.
+
+Most of the time, your C<init_meta> method will probably just call C<<
+Moose->init_meta >> to do the real work:
+
+  sub init_meta {
+      shift; # our class name
+      return Moose->init_meta( @_, metaclass => 'My::Metaclass' );
+  }
+
+=head1 AUTHOR
+
+Dave Rolsky E<lt>autarch@urth.orgE<gt>
+
+This is largely a reworking of code in Moose.pm originally written by
+Stevan Little and others.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
index de4b698..254c978 100644 (file)
@@ -19,188 +19,132 @@ use Moose::Util ();
 use Moose::Meta::Role;
 use Moose::Util::TypeConstraints;
 
-{
-    my ( $CALLER, %METAS );
+sub extends {
+    croak "Roles do not currently support 'extends'";
+}
 
-    sub _find_meta {
-        my $role = $CALLER;
+sub with {
+    Moose::Util::apply_all_roles( shift->meta(), @_ );
+}
 
-        return $METAS{$role} if exists $METAS{$role};
+sub requires {
+    my $meta = shift->meta();
+    croak "Must specify at least one method" unless @_;
+    $meta->add_required_methods(@_);
+}
 
-        # make a subtype for each Moose class
-        role_type $role unless find_type_constraint($role);
+sub excludes {
+    my $meta = shift->meta();
+    croak "Must specify at least one role" unless @_;
+    $meta->add_excluded_roles(@_);
+}
 
-        my $meta;
-        if ($role->can('meta')) {
-            $meta = $role->meta();
-            (blessed($meta) && $meta->isa('Moose::Meta::Role'))
-                || confess "You already have a &meta function, but it does not return a Moose::Meta::Role";
-        }
-        else {
-            $meta = Moose::Meta::Role->initialize($role);
-            $meta->alias_method('meta' => sub { $meta });
-        }
+sub has {
+    my $meta = shift->meta();
+    my $name = shift;
+    croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1;
+    my %options = @_;
+    my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ];
+    $meta->add_attribute( $_, %options ) for @$attrs;
+}
 
-        return $METAS{$role} = $meta;
+sub before {
+    my $meta = shift->meta();
+    my $code = pop @_;
+
+    for (@_) {
+        croak "Moose::Role do not currently support "
+            . ref($_)
+            . " references for before method modifiers"
+            if ref $_;
+        $meta->add_before_method_modifier( $_, $code );
     }
+}
 
+sub after {
+    my $meta = shift->meta();
 
-    my %exports = (
-        extends => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::extends' => sub {
-                croak "Roles do not currently support 'extends'"
-            });
-        },
-        with => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::with' => sub (@) {
-                Moose::Util::apply_all_roles($meta, @_)
-            });
-        },
-        requires => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::requires' => sub (@) {
-                croak "Must specify at least one method" unless @_;
-                $meta->add_required_methods(@_);
-            });
-        },
-        excludes => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::excludes' => sub (@) {
-                croak "Must specify at least one role" unless @_;
-                $meta->add_excluded_roles(@_);
-            });
-        },
-        has => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::has' => sub ($;%) {
-                my $name = shift;
-                croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1;
-                my %options = @_;
-                my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ];
-                $meta->add_attribute( $_, %options ) for @$attrs;
-            });
-        },
-        before => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::before' => sub (@&) {
-                my $code = pop @_;
-                do {
-                    croak "Moose::Role do not currently support " 
-                          . ref($_) 
-                          . " references for before method modifiers" 
-                    if ref $_;
-                    $meta->add_before_method_modifier($_, $code) 
-                } for @_;
-            });
-        },
-        after => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::after' => sub (@&) {
-                my $code = pop @_;
-                do {
-                    croak "Moose::Role do not currently support " 
-                          . ref($_) 
-                          . " references for after method modifiers" 
-                    if ref $_;                
-                    $meta->add_after_method_modifier($_, $code) 
-                } for @_;
-            });
-        },
-        around => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::around' => sub (@&) {
-                my $code = pop @_;
-                do {
-                    croak "Moose::Role do not currently support " 
-                          . ref($_) 
-                          . " references for around method modifiers" 
-                    if ref $_;                
-                    $meta->add_around_method_modifier($_, $code) 
-                } for @_;  
-            });
-        },
-        # see Moose.pm for discussion
-        super => sub {
-            return Class::MOP::subname('Moose::Role::super' => sub { 
-                return unless $Moose::SUPER_BODY; $Moose::SUPER_BODY->(@Moose::SUPER_ARGS) 
-            });
-        },
-        override => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::override' => sub ($&) {
-                my ($name, $code) = @_;
-                $meta->add_override_method_modifier($name, $code);
-            });
-        },
-        inner => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::inner' => sub {
-                croak "Moose::Role cannot support 'inner'";
-            });
-        },
-        augment => sub {
-            my $meta = _find_meta();
-            return Class::MOP::subname('Moose::Role::augment' => sub {
-                croak "Moose::Role cannot support 'augment'";
-            });
-        },
-        confess => sub {
-            return \&Carp::confess;
-        },
-        blessed => sub {
-            return \&Scalar::Util::blessed;
-        }
-    );
+    my $code = pop @_;
+    for (@_) {
+        croak "Moose::Role do not currently support "
+            . ref($_)
+            . " references for after method modifiers"
+            if ref $_;
+        $meta->add_after_method_modifier( $_, $code );
+    }
+}
 
-    my $exporter = Sub::Exporter::build_exporter({
-        exports => \%exports,
-        groups  => {
-            default => [':all']
-        }
-    });
+sub around {
+    my $meta = shift->meta();
+    my $code = pop @_;
+    for (@_) {
+        croak "Moose::Role do not currently support "
+            . ref($_)
+            . " references for around method modifiers"
+            if ref $_;
+        $meta->add_around_method_modifier( $_, $code );
+    }
+}
+
+# see Moose.pm for discussion
+sub super {
+    return unless $Moose::SUPER_BODY;
+    $Moose::SUPER_BODY->(@Moose::SUPER_ARGS);
+}
 
-    sub import {
-        $CALLER =
-            ref $_[1] && defined $_[1]->{into} ? $_[1]->{into}
-          : ref $_[1]
-          && defined $_[1]->{into_level} ? caller( $_[1]->{into_level} )
-          :                                caller();
+sub override {
+    my $meta = shift->meta();
+    my ( $name, $code ) = @_;
+    $meta->add_override_method_modifier( $name, $code );
+}
 
-        # this works because both pragmas set $^H (see perldoc perlvar)
-        # which affects the current compilation - i.e. the file who use'd
-        # us - which is why we don't need to do anything special to make
-        # it affect that file rather than this one (which is already compiled)
+sub inner {
+    croak "Moose::Role cannot support 'inner'";
+}
 
-        strict->import;
-        warnings->import;
+sub augment {
+    croak "Moose::Role cannot support 'augment'";
+}
 
-        # we should never export to main
-        return if $CALLER eq 'main';
+my $exporter = Moose::Exporter->build_import_methods(
+    with_caller => [
+        qw( with requires excludes has before after around override make_immutable )
+    ],
+    as_is => [
+        qw( extends super inner augment ),
+        \&Carp::confess,
+        \&Scalar::Util::blessed,
+    ],
+);
 
-        goto $exporter;
-    };
+{
+    my %METAS;
 
-    sub unimport {
-        no strict 'refs';
-        my $class = Moose::_get_caller(@_);
+    sub init_meta {
+        shift;
+        my %args = @_;
 
-        # loop through the exports ...
-        foreach my $name ( keys %exports ) {
+        my $role = $args{for_class}
+            or confess
+            "Cannot call init_meta without specifying a for_class";
 
-            # if we find one ...
-            if ( defined &{ $class . '::' . $name } ) {
-                my $keyword = \&{ $class . '::' . $name };
+        return $METAS{$role} if exists $METAS{$role};
 
-                # make sure it is from Moose::Role
-                my ($pkg_name) = Class::MOP::get_code_info($keyword);
-                next if $pkg_name ne 'Moose::Role';
+        # make a subtype for each Moose class
+        role_type $role unless find_type_constraint($role);
 
-                # and if it is from Moose::Role then undef the slot
-                delete ${ $class . '::' }{$name};
-            }
+        my $meta;
+        if ($role->can('meta')) {
+            $meta = $role->meta();
+            (blessed($meta) && $meta->isa('Moose::Meta::Role'))
+                || confess "You already have a &meta function, but it does not return a Moose::Meta::Role";
         }
+        else {
+            $meta = Moose::Meta::Role->initialize($role);
+            $meta->alias_method('meta' => sub { $meta });
+        }
+
+        return $METAS{$role} = $meta;
     }
 }
 
diff --git a/t/000_recipes/extending/001_base_class.t b/t/000_recipes/extending/001_base_class.t
new file mode 100644 (file)
index 0000000..2e305d3
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+
+BEGIN {
+    unless ( eval 'use Test::Warn; 1' )  {
+        plan skip_all => 'These tests require Test::Warn';
+    }
+    else {
+        plan tests => 4;
+    }
+}
+
+{
+    package MyApp::Base;
+    use Moose;
+
+    extends 'Moose::Object';
+
+    before 'new' => sub { warn "Making a new " . $_[0] };
+
+    no Moose;
+}
+
+{
+    package MyApp::UseMyBase;
+    use Moose ();
+    use Moose::Exporter;
+
+    Moose::Exporter->build_import_methods( also => 'Moose' );
+
+    sub init_meta {
+        shift;
+        Moose->init_meta( @_, base_class => 'MyApp::Base' );
+    }
+}
+
+{
+    package Foo;
+
+    MyApp::UseMyBase->import;
+
+    has( 'size' => ( is => 'rw' ) );
+}
+
+ok( Foo->isa('MyApp::Base'),
+    'Foo isa MyApp::Base' );
+
+ok( Foo->can('size'),
+    'Foo has a size method' );
+
+my $foo;
+warning_is( sub { $foo = Foo->new( size => 2 ) },
+            'Making a new Foo',
+            'got expected warning when calling Foo->new' );
+
+is( $foo->size(), 2, '$foo->size is 2' );
+
diff --git a/t/000_recipes/extending/002_metaclass_and_sugar.t b/t/000_recipes/extending/002_metaclass_and_sugar.t
new file mode 100644 (file)
index 0000000..fc930f8
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+
+
+{
+    package MyApp::Meta::Class;
+    use Moose;
+
+    extends 'Moose::Meta::Class';
+
+    has 'table' => ( is => 'rw' );
+
+    no Moose;
+
+    package MyApp::Mooseish;
+
+    use strict;
+    use warnings;
+
+    use Moose ();
+    use Moose::Exporter;
+
+    Moose::Exporter->build_import_methods(
+        with_caller => ['has_table'],
+        also        => 'Moose',
+    );
+
+    sub init_meta {
+        shift;
+        Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' );
+    }
+
+    sub has_table {
+        my $caller = shift;
+        $caller->meta()->table(shift);
+    }
+}
+
+{
+    package MyApp::User;
+
+    MyApp::Mooseish->import;
+
+    has_table( 'User' );
+
+    has( 'username' => ( is => 'ro' ) );
+    has( 'password' => ( is => 'ro' ) );
+
+    sub login { }
+}
+
+isa_ok( MyApp::User->meta, 'MyApp::Meta::Class' );
+is( MyApp::User->meta->table, 'User',
+    'MyApp::User->meta->table returns User' );
+ok( MyApp::User->can('username'),
+    'MyApp::User has username method' );
index c8cc9fd..683ae4b 100644 (file)
@@ -8,9 +8,13 @@ use Test::More;
 BEGIN {
     eval "use Test::Output;";
     plan skip_all => "Test::Output is required for this test" if $@;
-    plan tests => 1;
+    plan tests => 2;
 }
 
 stderr_is( sub { package main; eval 'use Moose' },
            "Moose does not export its sugar to the 'main' package.\n",
            'Moose warns when loaded from the main package' );
+
+stderr_is( sub { package main; eval 'use Moose::Role' },
+           "Moose::Role does not export its sugar to the 'main' package.\n",
+           'Moose::Role warns when loaded from the main package' );
index 3b99de3..b03027d 100644 (file)
@@ -3,13 +3,9 @@
 use strict;
 use warnings;
 
-use Test::More tests => 37;
+use Test::More tests => 36;
 use Test::Exception;
 
-BEGIN {  
-    use_ok('Moose::Role');               
-}
-
 =pod
 
 NOTE:
index 4be0dfa..d105868 100644 (file)
@@ -3,13 +3,9 @@
 use strict;
 use warnings;
 
-use Test::More tests => 87;
+use Test::More tests => 86;
 use Test::Exception;
 
-BEGIN {  
-    use_ok('Moose::Role');               
-}
-
 {
     package FooRole;
     use Moose::Role;
index fe38736..03b149a 100644 (file)
@@ -1,17 +1,13 @@
 #!/usr/bin/env perl
 use strict;
 use warnings;
-use Test::More tests => 7;
+use Test::More tests => 6;
 
 # this test script ensures that my idiom of:
 # role: sub BUILD, after BUILD
 # continues to work to run code after object initialization, whether the class
 # has a BUILD method or not
 
-BEGIN {
-    use_ok('Moose::Role');
-}
-
 my @CALLS;
 
 do {
diff --git a/t/050_metaclasses/012_moose_exporter.t b/t/050_metaclasses/012_moose_exporter.t
new file mode 100644 (file)
index 0000000..8965d1c
--- /dev/null
@@ -0,0 +1,241 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+
+BEGIN {
+    unless ( eval 'use Test::Warn; 1' )  {
+        plan skip_all => 'These tests require Test::Warn';
+    }
+    else {
+        plan tests => 40;
+    }
+}
+
+
+{
+    package HasOwnImmutable;
+
+    use Moose;
+
+    no Moose;
+
+    ::warning_is( sub { eval q[sub make_immutable { return 'foo' }] },
+                  '',
+                  'no warning when defining our own make_immutable sub' );
+}
+
+{
+    is( HasOwnImmutable->make_immutable(), 'foo',
+        'HasOwnImmutable->make_immutable does not get overwritten' );
+}
+
+{
+    package MooseX::Empty;
+
+    use Moose ();
+    Moose::Exporter->build_import_methods( also => 'Moose' );
+}
+
+{
+    package WantsMoose;
+
+    MooseX::Empty->import();
+
+    sub foo { 1 }
+
+    ::can_ok( 'WantsMoose', 'has' );
+    ::can_ok( 'WantsMoose', 'with' );
+    ::can_ok( 'WantsMoose', 'foo' );
+
+    MooseX::Empty->unimport();
+}
+
+{
+    # Note: it's important that these methods be out of scope _now_,
+    # after unimport was called. We tried a
+    # namespace::clean(0.08)-based solution, but had to abandon it
+    # because it cleans the namespace _later_ (when the file scope
+    # ends).
+    ok( ! WantsMoose->can('has'),  'WantsMoose::has() has been cleaned' );
+    ok( ! WantsMoose->can('with'), 'WantsMoose::with() has been cleaned' );
+    can_ok( 'WantsMoose', 'foo' );
+
+    # This makes sure that Moose->init_meta() happens properly
+    isa_ok( WantsMoose->meta(), 'Moose::Meta::Class' );
+    isa_ok( WantsMoose->new(), 'Moose::Object' );
+
+}
+
+{
+    package MooseX::Sugar;
+
+    use Moose ();
+
+    sub wrapped1 {
+        my $caller = shift;
+        return $caller . ' called wrapped1';
+    }
+
+    Moose::Exporter->build_import_methods(
+        with_caller => ['wrapped1'],
+        also        => 'Moose',
+    );
+}
+
+{
+    package WantsSugar;
+
+    MooseX::Sugar->import();
+
+    sub foo { 1 }
+
+    ::can_ok( 'WantsSugar', 'has' );
+    ::can_ok( 'WantsSugar', 'with' );
+    ::can_ok( 'WantsSugar', 'wrapped1' );
+    ::can_ok( 'WantsSugar', 'foo' );
+    ::is( wrapped1(), 'WantsSugar called wrapped1',
+          'wrapped1 identifies the caller correctly' );
+
+    MooseX::Sugar->unimport();
+}
+
+{
+    ok( ! WantsSugar->can('has'),  'WantsSugar::has() has been cleaned' );
+    ok( ! WantsSugar->can('with'), 'WantsSugar::with() has been cleaned' );
+    ok( ! WantsSugar->can('wrapped1'), 'WantsSugar::wrapped1() has been cleaned' );
+    can_ok( 'WantsSugar', 'foo' );
+}
+
+{
+    package MooseX::MoreSugar;
+
+    use Moose ();
+
+    sub wrapped2 {
+        my $caller = shift;
+        return $caller . ' called wrapped2';
+    }
+
+    sub as_is1 {
+        return 'as_is1';
+    }
+
+    Moose::Exporter->build_import_methods(
+        with_caller => ['wrapped2'],
+        as_is       => ['as_is1'],
+        also        => 'MooseX::Sugar',
+    );
+}
+
+{
+    package WantsMoreSugar;
+
+    MooseX::MoreSugar->import();
+
+    sub foo { 1 }
+
+    ::can_ok( 'WantsMoreSugar', 'has' );
+    ::can_ok( 'WantsMoreSugar', 'with' );
+    ::can_ok( 'WantsMoreSugar', 'wrapped1' );
+    ::can_ok( 'WantsMoreSugar', 'wrapped2' );
+    ::can_ok( 'WantsMoreSugar', 'as_is1' );
+    ::can_ok( 'WantsMoreSugar', 'foo' );
+    ::is( wrapped1(), 'WantsMoreSugar called wrapped1',
+          'wrapped1 identifies the caller correctly' );
+    ::is( wrapped2(), 'WantsMoreSugar called wrapped2',
+          'wrapped2 identifies the caller correctly' );
+    ::is( as_is1(), 'as_is1',
+          'as_is1 works as expected' );
+
+    MooseX::MoreSugar->unimport();
+}
+
+{
+    ok( ! WantsMoreSugar->can('has'),  'WantsMoreSugar::has() has been cleaned' );
+    ok( ! WantsMoreSugar->can('with'), 'WantsMoreSugar::with() has been cleaned' );
+    ok( ! WantsMoreSugar->can('wrapped1'), 'WantsMoreSugar::wrapped1() has been cleaned' );
+    ok( ! WantsMoreSugar->can('wrapped2'), 'WantsMoreSugar::wrapped2() has been cleaned' );
+    ok( ! WantsMoreSugar->can('as_is1'), 'WantsMoreSugar::as_is1() has been cleaned' );
+    can_ok( 'WantsMoreSugar', 'foo' );
+}
+
+{
+    package My::Metaclass;
+    use Moose;
+    BEGIN { extends 'Moose::Meta::Class' }
+
+    package My::Object;
+    use Moose;
+    BEGIN { extends 'Moose::Object' }
+
+    package HasInitMeta;
+
+    use Moose ();
+
+    sub init_meta {
+        shift;
+        return Moose->init_meta( @_,
+                                 metaclass  => 'My::Metaclass',
+                                 base_class => 'My::Object',
+                               );
+    }
+
+    Moose::Exporter->build_import_methods( also => 'Moose' );
+}
+
+{
+    package NewMeta;
+
+    HasInitMeta->import();
+}
+
+{
+    isa_ok( NewMeta->meta(), 'My::Metaclass' );
+    isa_ok( NewMeta->new(), 'My::Object' );
+}
+
+{
+    package MooseX::CircularAlso;
+
+    use Moose ();
+
+    ::dies_ok(
+        sub {
+            Moose::Exporter->build_import_methods(
+                also => [ 'Moose', 'MooseX::CircularAlso' ],
+            );
+        },
+        'a circular reference in also dies with an error'
+    );
+
+    ::like(
+        $@,
+        qr/\QCircular reference in also parameter to MooseX::Exporter between MooseX::CircularAlso and MooseX::CircularAlso/,
+        'got the expected error from circular reference in also'
+    );
+}
+
+{
+    package MooseX::CircularAlso;
+
+    use Moose ();
+
+    ::dies_ok(
+        sub {
+            Moose::Exporter->build_import_methods(
+                also => [ 'NoSuchThing' ],
+            );
+        },
+        'a package which does not use Moose::Exporter in also dies with an error'
+    );
+
+    ::like(
+        $@,
+        qr/\QPackage in also (NoSuchThing) does not seem to use MooseX::Exporter/,
+        'got the expected error from a reference in also to a package which does not use Moose::Exporter'
+    );
+}
index dcedee4..1f6b5ba 100644 (file)
@@ -8,4 +8,30 @@ use Test::More;
 eval "use Test::Pod::Coverage 1.04";
 plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@;
 
-all_pod_coverage_ok( { trustme => [ qr/intialize_body/ ] } );
+# This is a stripped down version of all_pod_coverage_ok which lets us
+# vary the trustme parameter per module.
+my @modules = all_modules();
+plan tests => scalar @modules;
+
+my %trustme = (
+    'Moose' => ['make_immutable'],
+    'Moose::Meta::Method::Constructor' =>
+        [qw( initialize_body intialize_body)],
+    'Moose::Meta::Method::Destructor' => ['initialize_body'],
+    'Moose::Role'                     => [
+        qw( after around augment before extends has inner make_immutable override super with )
+    ],
+);
+
+for my $module ( sort @modules ) {
+    my $trustme = [];
+    if ( $trustme{$module} ) {
+        my $methods = join '|', @{ $trustme{$module} };
+        $trustme = [qr/$methods/];
+    }
+
+    pod_coverage_ok(
+        $module, { trustme => $trustme },
+        "Pod coverage for $module"
+    );
+}