docs
[gitmo/Package-Stash.git] / lib / Stash / Manip.pm
1 package Stash::Manip;
2 use strict;
3 use warnings;
4
5 use Carp qw(confess);
6 use Scalar::Util qw(reftype);
7
8 =head1 NAME
9
10 Stash::Manip - routines for manipulating stashes
11
12 =head1 SYNOPSIS
13
14   my $stash = Stash::Manip->new('Foo');
15   $stash->add_package_symbol('%foo', {bar => 1});
16   # $Foo::foo{bar} == 1
17   $stash->has_package_symbol('$foo') # false
18   my $namespace = $stash->namespace;
19   *{ $namespace->{foo} }{HASH} # {bar => 1}
20
21 =head1 DESCRIPTION
22
23 Manipulating stashes (Perl's symbol tables) is occasionally necessary, but
24 incredibly messy, and easy to get wrong. This module hides all of that behind a
25 simple API.
26
27 =head1 METHODS
28
29 =cut
30
31 =head2 new $package_name
32
33 Creates a new C<Stash::Manip> object, for the package given as the only
34 argument.
35
36 =cut
37
38 sub new {
39     my $class = shift;
40     my ($namespace) = @_;
41     return bless { package => $namespace }, $class;
42 }
43
44 =head2 name
45
46 Returns the name of the package that this object represents.
47
48 =cut
49
50 sub name {
51     return $_[0]->{package};
52 }
53
54 =head2 namespace
55
56 Returns the raw stash itself.
57
58 =cut
59
60 sub namespace {
61     # NOTE:
62     # because of issues with the Perl API 
63     # to the typeglob in some versions, we 
64     # need to just always grab a new 
65     # reference to the hash here. Ideally 
66     # we could just store a ref and it would
67     # Just Work, but oh well :\    
68     no strict 'refs';
69     return \%{$_[0]->name . '::'};
70 }
71
72 {
73     my %SIGIL_MAP = (
74         '$' => 'SCALAR',
75         '@' => 'ARRAY',
76         '%' => 'HASH',
77         '&' => 'CODE',
78     );
79
80     sub _deconstruct_variable_name {
81         my ($self, $variable) = @_;
82
83         (defined $variable)
84             || confess "You must pass a variable name";
85
86         my $sigil = substr($variable, 0, 1, '');
87
88         (defined $sigil)
89             || confess "The variable name must include a sigil";
90
91         (exists $SIGIL_MAP{$sigil})
92             || confess "I do not recognize that sigil '$sigil'";
93
94         return ($variable, $sigil, $SIGIL_MAP{$sigil});
95     }
96 }
97
98 =head2 add_package_symbol $variable $value
99
100 Adds a new package symbol, for the symbol given as C<$variable>, and optionally
101 gives it an initial value of C<$value>. C<$variable> should be the name of
102 variable including the sigil, so
103
104   Stash::Manip->new('Foo')->add_package_symbol('%foo')
105
106 will create C<%Foo::foo>.
107
108 =cut
109
110 sub add_package_symbol {
111     my ($self, $variable, $initial_value) = @_;
112
113     my ($name, $sigil, $type) = ref $variable eq 'HASH'
114         ? @{$variable}{qw[name sigil type]}
115         : $self->_deconstruct_variable_name($variable);
116
117     my $pkg = $self->name;
118
119     no strict 'refs';
120     no warnings 'redefine', 'misc', 'prototype';
121     *{$pkg . '::' . $name} = ref $initial_value ? $initial_value : \$initial_value;
122 }
123
124 =head2 remove_package_glob $name
125
126 Removes all package variables with the given name, regardless of sigil.
127
128 =cut
129
130 sub remove_package_glob {
131     my ($self, $name) = @_;
132     no strict 'refs';
133     delete ${$self->name . '::'}{$name};
134 }
135
136 # ... these functions deal with stuff on the namespace level
137
138 =head2 has_package_symbol $variable
139
140 Returns whether or not the given package variable (including sigil) exists.
141
142 =cut
143
144 sub has_package_symbol {
145     my ($self, $variable) = @_;
146
147     my ($name, $sigil, $type) = ref $variable eq 'HASH'
148         ? @{$variable}{qw[name sigil type]}
149         : $self->_deconstruct_variable_name($variable);
150
151     my $namespace = $self->namespace;
152
153     return unless exists $namespace->{$name};
154
155     my $entry_ref = \$namespace->{$name};
156     if (reftype($entry_ref) eq 'GLOB') {
157         if ( $type eq 'SCALAR' ) {
158             return defined ${ *{$entry_ref}{SCALAR} };
159         }
160         else {
161             return defined *{$entry_ref}{$type};
162         }
163     }
164     else {
165         # a symbol table entry can be -1 (stub), string (stub with prototype),
166         # or reference (constant)
167         return $type eq 'CODE';
168     }
169 }
170
171 =head2 get_package_symbol $variable
172
173 Returns the value of the given package variable (including sigil).
174
175 =cut
176
177 sub get_package_symbol {
178     my ($self, $variable) = @_;
179
180     my ($name, $sigil, $type) = ref $variable eq 'HASH'
181         ? @{$variable}{qw[name sigil type]}
182         : $self->_deconstruct_variable_name($variable);
183
184     my $namespace = $self->namespace;
185
186     # FIXME
187     $self->add_package_symbol($variable)
188         unless exists $namespace->{$name};
189
190     my $entry_ref = \$namespace->{$name};
191
192     if (ref($entry_ref) eq 'GLOB') {
193         return *{$entry_ref}{$type};
194     }
195     else {
196         if ($type eq 'CODE') {
197             no strict 'refs';
198             return \&{ $self->name . '::' . $name };
199         }
200         else {
201             return undef;
202         }
203     }
204 }
205
206 =head2 remove_package_symbol $variable
207
208 Removes the package variable described by C<$variable> (which includes the
209 sigil); other variables with the same name but different sigils will be
210 untouched.
211
212 =cut
213
214 sub remove_package_symbol {
215     my ($self, $variable) = @_;
216
217     my ($name, $sigil, $type) = ref $variable eq 'HASH'
218         ? @{$variable}{qw[name sigil type]}
219         : $self->_deconstruct_variable_name($variable);
220
221     # FIXME:
222     # no doubt this is grossly inefficient and 
223     # could be done much easier and faster in XS
224
225     my ($scalar_desc, $array_desc, $hash_desc, $code_desc) = (
226         { sigil => '$', type => 'SCALAR', name => $name },
227         { sigil => '@', type => 'ARRAY',  name => $name },
228         { sigil => '%', type => 'HASH',   name => $name },
229         { sigil => '&', type => 'CODE',   name => $name },
230     );
231
232     my ($scalar, $array, $hash, $code);
233     if ($type eq 'SCALAR') {
234         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
235         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
236         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
237     }
238     elsif ($type eq 'ARRAY') {
239         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
240         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
241         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
242     }
243     elsif ($type eq 'HASH') {
244         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
245         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
246         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
247     }
248     elsif ($type eq 'CODE') {
249         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
250         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
251         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
252     }
253     else {
254         confess "This should never ever ever happen";
255     }
256
257     $self->remove_package_glob($name);
258
259     $self->add_package_symbol($scalar_desc => $scalar) if defined $scalar;
260     $self->add_package_symbol($array_desc  => $array)  if defined $array;
261     $self->add_package_symbol($hash_desc   => $hash)   if defined $hash;
262     $self->add_package_symbol($code_desc   => $code)   if defined $code;
263 }
264
265 =head2 list_all_package_symbols $type_filter
266
267 Returns a list of package variable names in the package, without sigils. If a
268 C<type_filter> is passed, it is used to select package variables of a given
269 type, where valid types are the slots of a typeglob ('SCALAR', 'CODE', 'HASH',
270 etc).
271
272 =cut
273
274 sub list_all_package_symbols {
275     my ($self, $type_filter) = @_;
276
277     my $namespace = $self->namespace;
278     return keys %{$namespace} unless defined $type_filter;
279
280     # NOTE:
281     # or we can filter based on 
282     # type (SCALAR|ARRAY|HASH|CODE)
283     if ($type_filter eq 'CODE') {
284         return grep {
285             (ref($namespace->{$_})
286                 ? (ref($namespace->{$_}) eq 'SCALAR')
287                 : (ref(\$namespace->{$_}) eq 'GLOB'
288                    && defined(*{$namespace->{$_}}{CODE})));
289         } keys %{$namespace};
290     } else {
291         return grep { *{$namespace->{$_}}{$type_filter} } keys %{$namespace};
292     }
293 }
294
295 =head1 BUGS
296
297 No known bugs.
298
299 Please report any bugs through RT: email
300 C<bug-stash-manip at rt.cpan.org>, or browse to
301 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Stash-Manip>.
302
303 =head1 SEE ALSO
304
305 L<Class::MOP::Package> - this module is a factoring out of code that used to
306 live here
307
308 =head1 SUPPORT
309
310 You can find this documentation for this module with the perldoc command.
311
312     perldoc Stash::Manip
313
314 You can also look for information at:
315
316 =over 4
317
318 =item * AnnoCPAN: Annotated CPAN documentation
319
320 L<http://annocpan.org/dist/Stash-Manip>
321
322 =item * CPAN Ratings
323
324 L<http://cpanratings.perl.org/d/Stash-Manip>
325
326 =item * RT: CPAN's request tracker
327
328 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Stash-Manip>
329
330 =item * Search CPAN
331
332 L<http://search.cpan.org/dist/Stash-Manip>
333
334 =back
335
336 =head1 AUTHOR
337
338   Jesse Luehrs <doy at tozt dot net>
339
340 Mostly copied from code from L<Class::MOP::Package>, by Stevan Little and the
341 Moose Cabal.
342
343 =head1 COPYRIGHT AND LICENSE
344
345 This software is copyright (c) 2010 by Jesse Luehrs.
346
347 This is free software; you can redistribute it and/or modify it under
348 the same terms as perl itself.
349
350 =cut
351
352 1;