initialize the ARRAY and HASH slots on get
[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     if (!exists $namespace->{$name}) {
188         my $initial = $type eq 'ARRAY' ? []
189                     : $type eq 'HASH'  ? {}
190                     : \undef;
191         $self->add_package_symbol($variable, $initial)
192     }
193
194     my $entry_ref = \$namespace->{$name};
195
196     if (ref($entry_ref) eq 'GLOB') {
197         return *{$entry_ref}{$type};
198     }
199     else {
200         if ($type eq 'CODE') {
201             no strict 'refs';
202             return \&{ $self->name . '::' . $name };
203         }
204         else {
205             return undef;
206         }
207     }
208 }
209
210 =head2 remove_package_symbol $variable
211
212 Removes the package variable described by C<$variable> (which includes the
213 sigil); other variables with the same name but different sigils will be
214 untouched.
215
216 =cut
217
218 sub remove_package_symbol {
219     my ($self, $variable) = @_;
220
221     my ($name, $sigil, $type) = ref $variable eq 'HASH'
222         ? @{$variable}{qw[name sigil type]}
223         : $self->_deconstruct_variable_name($variable);
224
225     # FIXME:
226     # no doubt this is grossly inefficient and 
227     # could be done much easier and faster in XS
228
229     my ($scalar_desc, $array_desc, $hash_desc, $code_desc, $io_desc) = (
230         { sigil => '$', type => 'SCALAR', name => $name },
231         { sigil => '@', type => 'ARRAY',  name => $name },
232         { sigil => '%', type => 'HASH',   name => $name },
233         { sigil => '&', type => 'CODE',   name => $name },
234         { sigil => '',  type => 'IO',     name => $name },
235     );
236
237     my ($scalar, $array, $hash, $code, $io);
238     if ($type eq 'SCALAR') {
239         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_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         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
243     }
244     elsif ($type eq 'ARRAY') {
245         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
246         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
247         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
248         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
249     }
250     elsif ($type eq 'HASH') {
251         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
252         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
253         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
254         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
255     }
256     elsif ($type eq 'CODE') {
257         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
258         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
259         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
260         $io     = $self->get_package_symbol($io_desc)     if $self->has_package_symbol($io_desc);
261     }
262     elsif ($type eq 'IO') {
263         $scalar = $self->get_package_symbol($scalar_desc) if $self->has_package_symbol($scalar_desc);
264         $array  = $self->get_package_symbol($array_desc)  if $self->has_package_symbol($array_desc);
265         $hash   = $self->get_package_symbol($hash_desc)   if $self->has_package_symbol($hash_desc);
266         $code   = $self->get_package_symbol($code_desc)   if $self->has_package_symbol($code_desc);
267     }
268     else {
269         confess "This should never ever ever happen";
270     }
271
272     $self->remove_package_glob($name);
273
274     $self->add_package_symbol($scalar_desc => $scalar) if defined $scalar;
275     $self->add_package_symbol($array_desc  => $array)  if defined $array;
276     $self->add_package_symbol($hash_desc   => $hash)   if defined $hash;
277     $self->add_package_symbol($code_desc   => $code)   if defined $code;
278     $self->add_package_symbol($io_desc     => $io)     if defined $io;
279 }
280
281 =head2 list_all_package_symbols $type_filter
282
283 Returns a list of package variable names in the package, without sigils. If a
284 C<type_filter> is passed, it is used to select package variables of a given
285 type, where valid types are the slots of a typeglob ('SCALAR', 'CODE', 'HASH',
286 etc).
287
288 =cut
289
290 sub list_all_package_symbols {
291     my ($self, $type_filter) = @_;
292
293     my $namespace = $self->namespace;
294     return keys %{$namespace} unless defined $type_filter;
295
296     # NOTE:
297     # or we can filter based on 
298     # type (SCALAR|ARRAY|HASH|CODE)
299     if ($type_filter eq 'CODE') {
300         return grep {
301             (ref($namespace->{$_})
302                 ? (ref($namespace->{$_}) eq 'SCALAR')
303                 : (ref(\$namespace->{$_}) eq 'GLOB'
304                    && defined(*{$namespace->{$_}}{CODE})));
305         } keys %{$namespace};
306     } else {
307         return grep { *{$namespace->{$_}}{$type_filter} } keys %{$namespace};
308     }
309 }
310
311 =head1 BUGS
312
313 No known bugs.
314
315 Please report any bugs through RT: email
316 C<bug-stash-manip at rt.cpan.org>, or browse to
317 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Stash-Manip>.
318
319 =head1 SEE ALSO
320
321 L<Class::MOP::Package> - this module is a factoring out of code that used to
322 live here
323
324 =head1 SUPPORT
325
326 You can find this documentation for this module with the perldoc command.
327
328     perldoc Stash::Manip
329
330 You can also look for information at:
331
332 =over 4
333
334 =item * AnnoCPAN: Annotated CPAN documentation
335
336 L<http://annocpan.org/dist/Stash-Manip>
337
338 =item * CPAN Ratings
339
340 L<http://cpanratings.perl.org/d/Stash-Manip>
341
342 =item * RT: CPAN's request tracker
343
344 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Stash-Manip>
345
346 =item * Search CPAN
347
348 L<http://search.cpan.org/dist/Stash-Manip>
349
350 =back
351
352 =head1 AUTHOR
353
354   Jesse Luehrs <doy at tozt dot net>
355
356 Mostly copied from code from L<Class::MOP::Package>, by Stevan Little and the
357 Moose Cabal.
358
359 =head1 COPYRIGHT AND LICENSE
360
361 This software is copyright (c) 2010 by Jesse Luehrs.
362
363 This is free software; you can redistribute it and/or modify it under
364 the same terms as perl itself.
365
366 =cut
367
368 1;