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