handle IO slots
[gitmo/Package-Stash-PP.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, $io_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         { sigil => '',  type => 'IO',     name => $name },
231     );
232
233     my ($scalar, $array, $hash, $code, $io);
234     if ($type eq 'SCALAR') {
235         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
236         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
237         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
238         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
239     }
240     elsif ($type eq 'ARRAY') {
241         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
242         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
243         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
244         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
245     }
246     elsif ($type eq 'HASH') {
247         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
248         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
249         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
250         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
251     }
252     elsif ($type eq 'CODE') {
253         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
254         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
255         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
256         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
257     }
258     elsif ($type eq 'IO') {
259         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
260         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
261         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
262         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
263     }
264     else {
265         confess "This should never ever ever happen";
266     }
267
268     $self->remove_package_glob($name);
269
270     $self->add_package_symbol($scalar_desc => $scalar) if defined $scalar;
271     $self->add_package_symbol($array_desc  => $array)  if defined $array;
272     $self->add_package_symbol($hash_desc   => $hash)   if defined $hash;
273     $self->add_package_symbol($code_desc   => $code)   if defined $code;
274     $self->add_package_symbol($io_desc     => $io)     if defined $io;
275 }
276
277 =head2 list_all_package_symbols $type_filter
278
279 Returns a list of package variable names in the package, without sigils. If a
280 C<type_filter> is passed, it is used to select package variables of a given
281 type, where valid types are the slots of a typeglob ('SCALAR', 'CODE', 'HASH',
282 etc).
283
284 =cut
285
286 sub list_all_package_symbols {
287     my ($self, $type_filter) = @_;
288
289     my $namespace = $self->namespace;
290     return keys %{$namespace} unless defined $type_filter;
291
292     # NOTE:
293     # or we can filter based on 
294     # type (SCALAR|ARRAY|HASH|CODE)
295     if ($type_filter eq 'CODE') {
296         return grep {
297             (ref($namespace->{$_})
298                 ? (ref($namespace->{$_}) eq 'SCALAR')
299                 : (ref(\$namespace->{$_}) eq 'GLOB'
300                    && defined(*{$namespace->{$_}}{CODE})));
301         } keys %{$namespace};
302     } else {
303         return grep { *{$namespace->{$_}}{$type_filter} } keys %{$namespace};
304     }
305 }
306
307 =head1 BUGS
308
309 No known bugs.
310
311 Please report any bugs through RT: email
312 C<bug-stash-manip at rt.cpan.org>, or browse to
313 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Stash-Manip>.
314
315 =head1 SEE ALSO
316
317 L<Class::MOP::Package> - this module is a factoring out of code that used to
318 live here
319
320 =head1 SUPPORT
321
322 You can find this documentation for this module with the perldoc command.
323
324     perldoc Stash::Manip
325
326 You can also look for information at:
327
328 =over 4
329
330 =item * AnnoCPAN: Annotated CPAN documentation
331
332 L<http://annocpan.org/dist/Stash-Manip>
333
334 =item * CPAN Ratings
335
336 L<http://cpanratings.perl.org/d/Stash-Manip>
337
338 =item * RT: CPAN's request tracker
339
340 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Stash-Manip>
341
342 =item * Search CPAN
343
344 L<http://search.cpan.org/dist/Stash-Manip>
345
346 =back
347
348 =head1 AUTHOR
349
350   Jesse Luehrs <doy at tozt dot net>
351
352 Mostly copied from code from L<Class::MOP::Package>, by Stevan Little and the
353 Moose Cabal.
354
355 =head1 COPYRIGHT AND LICENSE
356
357 This software is copyright (c) 2010 by Jesse Luehrs.
358
359 This is free software; you can redistribute it and/or modify it under
360 the same terms as perl itself.
361
362 =cut
363
364 1;