Convert to Dist::Zilla
[p5sagit/namespace-clean.git] / lib / namespace / clean.pm
index 9d7268f..0d738ad 100644 (file)
@@ -1,26 +1,16 @@
 package namespace::clean;
-
-=head1 NAME
-
-namespace::clean - Keep imports and functions out of your namespace
-
-=cut
+# ABSTRACT: Keep imports and functions out of your namespace
 
 use warnings;
 use strict;
 
-use vars              qw( $VERSION $STORAGE_VAR );
-use Symbol            qw( qualify_to_ref );
-use Filter::EOF;
+use vars qw( $STORAGE_VAR $SCOPE_HOOK_KEY $SCOPE_EXPLICIT );
+use Sub::Name 0.04 qw(subname);
+use Sub::Identify 0.04 qw(sub_fullname);
+use Package::Stash 0.03;
+use B::Hooks::EndOfScope 0.07;
 
-=head1 VERSION
-
-0.04
-
-=cut
-
-$VERSION     = 0.04;
-$STORAGE_VAR = '__NAMESPACE_CLEAN_STORAGE';
+$STORAGE_VAR     = '__NAMESPACE_CLEAN_STORAGE';
 
 =head1 SYNOPSIS
 
@@ -55,10 +45,12 @@ $STORAGE_VAR = '__NAMESPACE_CLEAN_STORAGE';
 
 =head1 DESCRIPTION
 
+=head2 Keeping packages clean
+
 When you define a function, or import one, into a Perl package, it will
 naturally also be available as a method. This does not per se cause
 problems, but it can complicate subclassing and, for example, plugin
-classes that are included via multiple inheritance by loading them as 
+classes that are included via multiple inheritance by loading them as
 base classes.
 
 The C<namespace::clean> pragma will remove all previously declared or
@@ -70,74 +62,185 @@ By unimporting via C<no> you can tell C<namespace::clean> to start
 collecting functions for the next C<use namespace::clean;> specification.
 
 You can use the C<-except> flag to tell C<namespace::clean> that you
-don't want it to remove a certain function. A common use would be a 
-module exporting an C<import> method along with some functions:
+don't want it to remove a certain function or method. A common use would
+be a module exporting an C<import> method along with some functions:
 
   use ModuleExportingImport;
   use namespace::clean -except => [qw( import )];
 
-=head1 METHODS
+If you just want to C<-except> a single sub, you can pass it directly.
+For more than one value you have to use an array reference.
 
-You shouldn't need to call any of these. Just C<use> the package at the
-appropriate place.
+=head2 Explicitely removing functions when your scope is compiled
 
-=cut
+It is also possible to explicitely tell C<namespace::clean> what packages
+to remove when the surrounding scope has finished compiling. Here is an
+example:
 
-=head2 import
+  package Foo;
+  use strict;
 
-Makes a snapshot of the current defined functions and registers a 
-L<Filter::EOF> cleanup routine to remove those symbols at the end 
-of the compile-time.
+  # blessed NOT available
 
-=cut
+  sub my_class {
+      use Scalar::Util qw( blessed );
+      use namespace::clean qw( blessed );
 
-sub import {
-    my ($pragma, %args) = @_;
+      # blessed available
+      return blessed shift;
+  }
 
-    # calling class, all current functions and our storage
-    my $cleanee   = caller;
-    my $functions = $pragma->get_functions($cleanee);
-    my $store     = $pragma->get_class_store($cleanee);
+  # blessed NOT available
 
-    my %except    = map {( $_ => 1 )} @{ $args{ -except } || [] };
+=head2 Moose
 
-    # register symbols for removal, if they have a CODE entry
-    for my $f (keys %$functions) {
-        next if     $except{ $f };
-        next unless    $functions->{ $f } 
-                and *{ $functions->{ $f } }{CODE};
-        $store->{remove}{ $f } = 1;
-    }
+When using C<namespace::clean> together with L<Moose> you want to keep
+the installed C<meta> method. So your classes should look like:
+
+  package Foo;
+  use Moose;
+  use namespace::clean -except => 'meta';
+  ...
+
+Same goes for L<Moose::Role>.
+
+=head2 Cleaning other packages
+
+You can tell C<namespace::clean> that you want to clean up another package
+instead of the one importing. To do this you have to pass in the C<-cleanee>
+option like this:
+
+  package My::MooseX::namespace::clean;
+  use strict;
 
-    # register EOF handler on first call to import
-    unless ($store->{handler_is_installed}) {
-        Filter::EOF->on_eof_call(sub {
-          SYMBOL:
-            for my $f (keys %{ $store->{remove} }) {
-
-                # ignore already removed symbols
-                next SYMBOL if $store->{exclude}{ $f };
-                no strict 'refs';
-
-                # keep original value to restore non-code slots
-                local *__tmp = *{ ${ "${cleanee}::" }{ $f } };
-                delete ${ "${cleanee}::" }{ $f };
-
-              SLOT:
-                # restore non-code slots to symbol
-                for my $t (qw( SCALAR ARRAY HASH IO FORMAT )) {
-                    next SLOT unless defined *__tmp{ $t };
-                    *{ "${cleanee}::$f" } = *__tmp{ $t };
-                }
+  use namespace::clean (); # no cleanup, just load
+
+  sub import {
+      namespace::clean->import(
+        -cleanee => scalar(caller),
+        -except  => 'meta',
+      );
+  }
+
+If you don't care about C<namespace::clean>s discover-and-C<-except> logic, and
+just want to remove subroutines, try L</clean_subroutines>.
+
+=method clean_subroutines
+
+This exposes the actual subroutine-removal logic.
+
+  namespace::clean->clean_subroutines($cleanee, qw( subA subB ));
+
+will remove C<subA> and C<subB> from C<$cleanee>. Note that this will remove the
+subroutines B<immediately> and not wait for scope end. If you want to have this
+effect at a specific time (e.g. C<namespace::clean> acts on scope compile end)
+it is your responsibility to make sure it runs at that time.
+
+=cut
+
+my $RemoveSubs = sub {
+
+    my $cleanee = shift;
+    my $store   = shift;
+    my $cleanee_stash = Package::Stash->new($cleanee);
+    my $deleted_stash = Package::Stash->new("namespace::clean::deleted::$cleanee");
+  SYMBOL:
+    for my $f (@_) {
+        my $variable = "&$f";
+        # ignore already removed symbols
+        next SYMBOL if $store->{exclude}{ $f };
+
+        next SYMBOL unless $cleanee_stash->has_package_symbol($variable);
+
+        if (ref(\$cleanee_stash->namespace->{$f}) eq 'GLOB') {
+            # convince the Perl debugger to work
+            # it assumes that sub_fullname($sub) can always be used to find the CV again
+            # since we are deleting the glob where the subroutine was originally
+            # defined, that assumption no longer holds, so we need to move it
+            # elsewhere and point the CV's name to the new glob.
+            my $sub = $cleanee_stash->get_package_symbol($variable);
+            if ( sub_fullname($sub) eq ($cleanee_stash->name . "::$f") ) {
+                my $new_fq = $deleted_stash->name . "::$f";
+                subname($new_fq, $sub);
+                $deleted_stash->add_package_symbol($variable, $sub);
             }
-        });
-        $store->{handler_is_installed} = 1;
+        }
+
+        $cleanee_stash->remove_package_symbol($variable);
     }
+};
 
-    return 1;
+sub clean_subroutines {
+    my ($nc, $cleanee, @subs) = @_;
+    $RemoveSubs->($cleanee, {}, @subs);
 }
 
-=head2 unimport
+=method import
+
+Makes a snapshot of the current defined functions and installs a
+L<B::Hooks::EndOfScope> hook in the current scope to invoke the cleanups.
+
+=cut
+
+sub import {
+    my ($pragma, @args) = @_;
+
+    my (%args, $is_explicit);
+
+  ARG:
+    while (@args) {
+
+        if ($args[0] =~ /^\-/) {
+            my $key = shift @args;
+            my $value = shift @args;
+            $args{ $key } = $value;
+        }
+        else {
+            $is_explicit++;
+            last ARG;
+        }
+    }
+
+    my $cleanee = exists $args{ -cleanee } ? $args{ -cleanee } : scalar caller;
+    if ($is_explicit) {
+        on_scope_end {
+            $RemoveSubs->($cleanee, {}, @args);
+        };
+    }
+    else {
+
+        # calling class, all current functions and our storage
+        my $functions = $pragma->get_functions($cleanee);
+        my $store     = $pragma->get_class_store($cleanee);
+        my $stash     = Package::Stash->new($cleanee);
+
+        # except parameter can be array ref or single value
+        my %except = map {( $_ => 1 )} (
+            $args{ -except }
+            ? ( ref $args{ -except } eq 'ARRAY' ? @{ $args{ -except } } : $args{ -except } )
+            : ()
+        );
+
+        # register symbols for removal, if they have a CODE entry
+        for my $f (keys %$functions) {
+            next if     $except{ $f };
+            next unless $stash->has_package_symbol("&$f");
+            $store->{remove}{ $f } = 1;
+        }
+
+        # register EOF handler on first call to import
+        unless ($store->{handler_is_installed}) {
+            on_scope_end {
+                $RemoveSubs->($cleanee, $store, keys %{ $store->{remove} });
+            };
+            $store->{handler_is_installed} = 1;
+        }
+
+        return 1;
+    }
+}
+
+=method unimport
 
 This method will be called when you do a
 
@@ -148,10 +251,10 @@ It will start a new section of code that defines functions to clean up.
 =cut
 
 sub unimport {
-    my ($pragma) = @_;
+    my ($pragma, %args) = @_;
 
     # the calling class, the current functions and our storage
-    my $cleanee   = caller;
+    my $cleanee   = exists $args{ -cleanee } ? $args{ -cleanee } : scalar caller;
     my $functions = $pragma->get_functions($cleanee);
     my $store     = $pragma->get_class_store($cleanee);
 
@@ -165,20 +268,20 @@ sub unimport {
     return 1;
 }
 
-=head2 get_class_store
+=method get_class_store
 
-This returns a reference to a hash in a passed package containing 
+This returns a reference to a hash in a passed package containing
 information about function names included and excluded from removal.
 
 =cut
 
 sub get_class_store {
     my ($pragma, $class) = @_;
-    no strict 'refs';
-    return \%{ "${class}::${STORAGE_VAR}" };
+    my $stash = Package::Stash->new($class);
+    return $stash->get_package_symbol("%$STORAGE_VAR");
 }
 
-=head2 get_functions
+=method get_functions
 
 Takes a class as argument and returns all currently defined functions
 in it as a hash reference with the function name as key and a typeglob
@@ -189,24 +292,23 @@ reference to the symbol as value.
 sub get_functions {
     my ($pragma, $class) = @_;
 
+    my $stash = Package::Stash->new($class);
     return {
-        map  { @$_ }                                        # key => value
-        grep { *{ $_->[1] }{CODE} }                         # only functions
-        map  { [$_, qualify_to_ref( $_, $class )] }         # get globref
-        grep { $_ !~ /::$/ }                                # no packages
-        do   { no strict 'refs'; keys %{ "${class}::" } }   # symbol entries
+        map { $_ => $stash->get_package_symbol("&$_") }
+            $stash->list_all_package_symbols('CODE')
     };
 }
 
 =head1 IMPLEMENTATION DETAILS
 
-This module works through the effect that a 
+This module works through the effect that a
 
   delete $SomePackage::{foo};
 
 will remove the C<foo> symbol from C<$SomePackage> for run time lookups
 (e.g., method calls) but will leave the entry alive to be called by
-already resolved names in the package itself.
+already resolved names in the package itself. C<namespace::clean> will
+restore and therefor in effect keep all glob slots that aren't C<CODE>.
 
 A test file has been added to the perl core to ensure that this behaviour
 will be stable in future releases.
@@ -216,18 +318,13 @@ use C<undef> instead.
 
 =head1 SEE ALSO
 
-L<Filter::EOF>
-
-=head1 AUTHOR AND COPYRIGHT
-
-Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>, with many thanks to
-Matt S Trout for the inspiration on the whole idea.
+L<B::Hooks::EndOfScope>
 
-=head1 LICENSE
+=head1 THANKS
 
-This program is free software; you can redistribute it and/or modify 
-it under the same terms as perl itself.
+Many thanks to Matt S Trout for the inspiration on the whole idea.
 
 =cut
 
-1;
+no warnings;
+'Danger! Laws of Thermodynamics may not apply.'