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