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