work with new package::stash api
[p5sagit/namespace-clean.git] / lib / namespace / clean.pm
CommitLineData
40aef9d6 1package namespace::clean;
04312494 2# ABSTRACT: Keep imports and functions out of your namespace
40aef9d6 3
4use warnings;
5use strict;
6
8177960a 7use vars qw( $STORAGE_VAR );
04312494 8use Sub::Name 0.04 qw(subname);
9use Sub::Identify 0.04 qw(sub_fullname);
10use Package::Stash 0.03;
11use B::Hooks::EndOfScope 0.07;
40aef9d6 12
5da81a06 13my ($ADD_SYMBOL, $HAS_SYMBOL, $GET_SYMBOL, $LIST_ALL_SYMBOLS, $REMOVE_GLOB);
14BEGIN {
15 if ($Package::Stash::VERSION > 0.13) {
16 $ADD_SYMBOL = 'add_symbol';
17 $HAS_SYMBOL = 'has_symbol';
18 $GET_SYMBOL = 'get_symbol';
19 $LIST_ALL_SYMBOLS = 'list_all_symbols';
20 $REMOVE_GLOB = 'remove_glob';
21 }
22 else {
23 $ADD_SYMBOL = 'add_package_symbol';
24 $HAS_SYMBOL = 'has_package_symbol';
25 $GET_SYMBOL = 'get_package_symbol';
26 $LIST_ALL_SYMBOLS = 'list_all_package_symbols';
27 $REMOVE_GLOB = 'remove_package_glob';
28 }
29}
30
8177960a 31$STORAGE_VAR = '__NAMESPACE_CLEAN_STORAGE';
40aef9d6 32
33=head1 SYNOPSIS
34
35 package Foo;
36 use warnings;
37 use strict;
38
6c0ece9b 39 use Carp qw(croak); # 'croak' will be removed
40aef9d6 40
6c0ece9b 41 sub bar { 23 } # 'bar' will be removed
40aef9d6 42
6c0ece9b 43 # remove all previously defined functions
40aef9d6 44 use namespace::clean;
45
6c0ece9b 46 sub baz { bar() } # 'baz' still defined, 'bar' still bound
40aef9d6 47
6c0ece9b 48 # begin to collection function names from here again
9b680ffe 49 no namespace::clean;
50
6c0ece9b 51 sub quux { baz() } # 'quux' will be removed
9b680ffe 52
6c0ece9b 53 # remove all functions defined after the 'no' unimport
9b680ffe 54 use namespace::clean;
55
6c0ece9b 56 # Will print: 'No', 'No', 'Yes' and 'No'
40aef9d6 57 print +(__PACKAGE__->can('croak') ? 'Yes' : 'No'), "\n";
58 print +(__PACKAGE__->can('bar') ? 'Yes' : 'No'), "\n";
59 print +(__PACKAGE__->can('baz') ? 'Yes' : 'No'), "\n";
9b680ffe 60 print +(__PACKAGE__->can('quux') ? 'Yes' : 'No'), "\n";
40aef9d6 61
62 1;
63
64=head1 DESCRIPTION
65
de673bbf 66=head2 Keeping packages clean
67
40aef9d6 68When you define a function, or import one, into a Perl package, it will
69naturally also be available as a method. This does not per se cause
70problems, but it can complicate subclassing and, for example, plugin
04312494 71classes that are included via multiple inheritance by loading them as
6c0ece9b 72base classes.
40aef9d6 73
74The C<namespace::clean> pragma will remove all previously declared or
75imported symbols at the end of the current package's compile cycle.
6c0ece9b 76Functions called in the package itself will still be bound by their
77name, but they won't show up as methods on your class or instances.
78
79By unimporting via C<no> you can tell C<namespace::clean> to start
80collecting functions for the next C<use namespace::clean;> specification.
40aef9d6 81
53e92ec5 82You can use the C<-except> flag to tell C<namespace::clean> that you
472d4b1e 83don't want it to remove a certain function or method. A common use would
84be a module exporting an C<import> method along with some functions:
53e92ec5 85
86 use ModuleExportingImport;
87 use namespace::clean -except => [qw( import )];
88
472d4b1e 89If you just want to C<-except> a single sub, you can pass it directly.
90For more than one value you have to use an array reference.
91
de673bbf 92=head2 Explicitely removing functions when your scope is compiled
93
94It is also possible to explicitely tell C<namespace::clean> what packages
95to remove when the surrounding scope has finished compiling. Here is an
96example:
97
98 package Foo;
99 use strict;
100
101 # blessed NOT available
102
103 sub my_class {
104 use Scalar::Util qw( blessed );
105 use namespace::clean qw( blessed );
106
107 # blessed available
108 return blessed shift;
109 }
110
111 # blessed NOT available
112
1a1be5dc 113=head2 Moose
114
115When using C<namespace::clean> together with L<Moose> you want to keep
116the installed C<meta> method. So your classes should look like:
117
118 package Foo;
119 use Moose;
120 use namespace::clean -except => 'meta';
121 ...
122
123Same goes for L<Moose::Role>.
124
fcfe7810 125=head2 Cleaning other packages
126
127You can tell C<namespace::clean> that you want to clean up another package
128instead of the one importing. To do this you have to pass in the C<-cleanee>
129option like this:
130
131 package My::MooseX::namespace::clean;
132 use strict;
133
134 use namespace::clean (); # no cleanup, just load
135
136 sub import {
137 namespace::clean->import(
138 -cleanee => scalar(caller),
139 -except => 'meta',
140 );
141 }
142
143If you don't care about C<namespace::clean>s discover-and-C<-except> logic, and
144just want to remove subroutines, try L</clean_subroutines>.
145
04312494 146=method clean_subroutines
40aef9d6 147
fcfe7810 148This exposes the actual subroutine-removal logic.
149
150 namespace::clean->clean_subroutines($cleanee, qw( subA subB ));
151
152will remove C<subA> and C<subB> from C<$cleanee>. Note that this will remove the
153subroutines B<immediately> and not wait for scope end. If you want to have this
154effect at a specific time (e.g. C<namespace::clean> acts on scope compile end)
155it is your responsibility to make sure it runs at that time.
40aef9d6 156
157=cut
158
de673bbf 159my $RemoveSubs = sub {
fcfe7810 160
de673bbf 161 my $cleanee = shift;
162 my $store = shift;
16d8eca5 163 my $cleanee_stash = Package::Stash->new($cleanee);
164 my $deleted_stash = Package::Stash->new("namespace::clean::deleted::$cleanee");
de673bbf 165 SYMBOL:
166 for my $f (@_) {
d6aecfbf 167 my $variable = "&$f";
de673bbf 168 # ignore already removed symbols
169 next SYMBOL if $store->{exclude}{ $f };
de673bbf 170
5da81a06 171 next SYMBOL unless $cleanee_stash->$HAS_SYMBOL($variable);
226432f6 172
d6aecfbf 173 if (ref(\$cleanee_stash->namespace->{$f}) eq 'GLOB') {
226432f6 174 # convince the Perl debugger to work
175 # it assumes that sub_fullname($sub) can always be used to find the CV again
176 # since we are deleting the glob where the subroutine was originally
177 # defined, that assumption no longer holds, so we need to move it
178 # elsewhere and point the CV's name to the new glob.
5da81a06 179 my $sub = $cleanee_stash->$GET_SYMBOL($variable);
d6aecfbf 180 if ( sub_fullname($sub) eq ($cleanee_stash->name . "::$f") ) {
181 my $new_fq = $deleted_stash->name . "::$f";
226432f6 182 subname($new_fq, $sub);
5da81a06 183 $deleted_stash->$ADD_SYMBOL($variable, $sub);
226432f6 184 }
de673bbf 185 }
d6aecfbf 186
ad4b1a60 187 my ($scalar, $array, $hash, $io) = map {
5da81a06 188 $cleanee_stash->$GET_SYMBOL($_ . $f)
ad4b1a60 189 } '$', '@', '%', '';
5da81a06 190 $cleanee_stash->$REMOVE_GLOB($f);
ad4b1a60 191 for my $var (['$', $scalar], ['@', $array], ['%', $hash], ['', $io]) {
192 next unless defined $var->[1];
5da81a06 193 $cleanee_stash->$ADD_SYMBOL($var->[0] . $f, $var->[1]);
ad4b1a60 194 }
de673bbf 195 }
196};
53e92ec5 197
fcfe7810 198sub clean_subroutines {
199 my ($nc, $cleanee, @subs) = @_;
200 $RemoveSubs->($cleanee, {}, @subs);
201}
202
04312494 203=method import
fcfe7810 204
205Makes a snapshot of the current defined functions and installs a
206L<B::Hooks::EndOfScope> hook in the current scope to invoke the cleanups.
207
208=cut
209
de673bbf 210sub import {
211 my ($pragma, @args) = @_;
53e92ec5 212
de673bbf 213 my (%args, $is_explicit);
fcfe7810 214
215 ARG:
216 while (@args) {
217
218 if ($args[0] =~ /^\-/) {
219 my $key = shift @args;
220 my $value = shift @args;
221 $args{ $key } = $value;
222 }
223 else {
224 $is_explicit++;
225 last ARG;
226 }
9b680ffe 227 }
228
fcfe7810 229 my $cleanee = exists $args{ -cleanee } ? $args{ -cleanee } : scalar caller;
de673bbf 230 if ($is_explicit) {
aa2aafae 231 on_scope_end {
de673bbf 232 $RemoveSubs->($cleanee, {}, @args);
aa2aafae 233 };
9b680ffe 234 }
de673bbf 235 else {
236
237 # calling class, all current functions and our storage
238 my $functions = $pragma->get_functions($cleanee);
239 my $store = $pragma->get_class_store($cleanee);
16d8eca5 240 my $stash = Package::Stash->new($cleanee);
de673bbf 241
242 # except parameter can be array ref or single value
243 my %except = map {( $_ => 1 )} (
244 $args{ -except }
245 ? ( ref $args{ -except } eq 'ARRAY' ? @{ $args{ -except } } : $args{ -except } )
246 : ()
247 );
248
249 # register symbols for removal, if they have a CODE entry
250 for my $f (keys %$functions) {
251 next if $except{ $f };
5da81a06 252 next unless $stash->$HAS_SYMBOL("&$f");
de673bbf 253 $store->{remove}{ $f } = 1;
254 }
255
256 # register EOF handler on first call to import
257 unless ($store->{handler_is_installed}) {
aa2aafae 258 on_scope_end {
de673bbf 259 $RemoveSubs->($cleanee, $store, keys %{ $store->{remove} });
aa2aafae 260 };
de673bbf 261 $store->{handler_is_installed} = 1;
262 }
263
264 return 1;
265 }
9b680ffe 266}
267
04312494 268=method unimport
9b680ffe 269
270This method will be called when you do a
271
272 no namespace::clean;
273
274It will start a new section of code that defines functions to clean up.
275
276=cut
277
278sub unimport {
fcfe7810 279 my ($pragma, %args) = @_;
9b680ffe 280
6c0ece9b 281 # the calling class, the current functions and our storage
fcfe7810 282 my $cleanee = exists $args{ -cleanee } ? $args{ -cleanee } : scalar caller;
9b680ffe 283 my $functions = $pragma->get_functions($cleanee);
284 my $store = $pragma->get_class_store($cleanee);
285
6c0ece9b 286 # register all unknown previous functions as excluded
9b680ffe 287 for my $f (keys %$functions) {
288 next if $store->{remove}{ $f }
289 or $store->{exclude}{ $f };
290 $store->{exclude}{ $f } = 1;
291 }
292
293 return 1;
294}
295
04312494 296=method get_class_store
9b680ffe 297
04312494 298This returns a reference to a hash in a passed package containing
6c0ece9b 299information about function names included and excluded from removal.
9b680ffe 300
301=cut
302
303sub get_class_store {
304 my ($pragma, $class) = @_;
16d8eca5 305 my $stash = Package::Stash->new($class);
5460fcfb 306 my $var = "%$STORAGE_VAR";
5da81a06 307 $stash->$ADD_SYMBOL($var, {})
308 unless $stash->$HAS_SYMBOL($var);
309 return $stash->$GET_SYMBOL($var);
40aef9d6 310}
311
04312494 312=method get_functions
40aef9d6 313
314Takes a class as argument and returns all currently defined functions
315in it as a hash reference with the function name as key and a typeglob
316reference to the symbol as value.
317
318=cut
319
320sub get_functions {
321 my ($pragma, $class) = @_;
322
16d8eca5 323 my $stash = Package::Stash->new($class);
40aef9d6 324 return {
5da81a06 325 map { $_ => $stash->$GET_SYMBOL("&$_") }
326 $stash->$LIST_ALL_SYMBOLS('CODE')
40aef9d6 327 };
328}
329
6c0ece9b 330=head1 IMPLEMENTATION DETAILS
331
04312494 332This module works through the effect that a
6c0ece9b 333
334 delete $SomePackage::{foo};
335
336will remove the C<foo> symbol from C<$SomePackage> for run time lookups
337(e.g., method calls) but will leave the entry alive to be called by
472d4b1e 338already resolved names in the package itself. C<namespace::clean> will
339restore and therefor in effect keep all glob slots that aren't C<CODE>.
6c0ece9b 340
341A test file has been added to the perl core to ensure that this behaviour
342will be stable in future releases.
343
344Just for completeness sake, if you want to remove the symbol completely,
345use C<undef> instead.
346
40aef9d6 347=head1 SEE ALSO
348
705fe1b1 349L<B::Hooks::EndOfScope>
40aef9d6 350
04312494 351=head1 THANKS
40aef9d6 352
04312494 353Many thanks to Matt S Trout for the inspiration on the whole idea.
40aef9d6 354
355=cut
356
de673bbf 357no warnings;
358'Danger! Laws of Thermodynamics may not apply.'