File Coverage

blib/lib/Symbol/Get.pm
Criterion Covered Total %
statement 48 60 80.0
branch 9 20 45.0
condition 5 11 45.4
subroutine 9 11 81.8
pod 0 3 0.0
total 71 105 67.6


line stmt bran cond sub pod time code
1             package Symbol::Get;
2              
3 2     2   37609 use strict;
  2         642  
  2         52  
4 2     2   7 use warnings;
  2         2  
  2         42  
5              
6 2     2   763 use Call::Context ();
  2         402  
  2         70  
7              
8             our $VERSION = 0.07;
9              
10             =encoding utf-8
11              
12             =head1 NAME
13              
14             Symbol::Get - Read Perl’s symbol table programmatically
15              
16             =head1 SYNOPSIS
17              
18             package Foo;
19              
20             our $name = 'haha';
21             our @list = ( 1, 2, 3 );
22             our %hash = ( foo => 1, bar => 2 );
23              
24             use constant my_const => 'haha';
25              
26             sub doit { ... }
27              
28             my $name_sr = Symbol::Get::get('$Foo::name'); # \$name
29             my $list_ar = Symbol::Get::get('$Foo::list'); # \@list
30             my $hash_hr = Symbol::Get::get('$Foo::hash'); $ \%hash
31              
32             #Defaults to __PACKAGE__ if none is given:
33             my $doit_cr = Symbol::Get::get('&doit');
34              
35             #Constants:
36             my $const_val = Symbol::Get::copy_constant('Foo::my_const');
37             my @const_list = Symbol::Get::copy_constant('Foo::my_const_list');
38              
39             #The below return the same results since get_names() defaults
40             #to the current package if none is given.
41             my @names = Symbol::Get::get_names('Foo'); # keys %Foo::
42             my @names = Symbol::Get::get_names();
43              
44             =head1 DESCRIPTION
45              
46             Occasionally I have need to reference a variable programmatically.
47             This module facilitates that by providing an easy, syntactic-sugar-y,
48             read-only interface to the symbol table.
49              
50             The SYNOPSIS above should pretty well cover usage.
51              
52             =head1 ABOUT PERL CONSTANTS
53              
54             Previous versions of this module endorsed constructions like:
55              
56             my $const_sr = Symbol::Get::get('Foo::my_const');
57             my $const_ar = Symbol::Get::get('Foo::my_const_list');
58              
59             … to read constants from the symbol table. This isn’t reliable across
60             Perl versions, though, so don’t do it; instead, use C.
61              
62             =head1 SEE ALSO
63              
64             =over 4
65              
66             =item * L
67              
68             =back
69              
70             =head1 LICENSE
71              
72             This module is licensed under the same license as Perl.
73              
74             =cut
75              
76 2     2   7 use constant MIN_LIST_CONSTANT_PERL_VERSION => v5.20.0;
  2         3  
  2         1100  
77              
78             my %_sigil_to_type = qw(
79             $ SCALAR
80             @ ARRAY
81             % HASH
82             & CODE
83             );
84              
85             my $sigils_re_txt = join('|', keys %_sigil_to_type);
86              
87             sub get {
88 10     10 0 167 my ($var) = @_;
89              
90 10 50       20 die 'Need a variable or constant name!' if !length $var;
91              
92 10         13 my $sigil = substr($var, 0, 1);
93              
94 10 50       19 goto \&_get_constant if $sigil =~ tr<>;
95              
96 10 50       19 my $type = $_sigil_to_type{$sigil} or die "Unrecognized sigil: “$sigil”";
97              
98 10         19 my $table_hr = _get_table_hr( substr($var, 1) );
99 10   100     27 return $table_hr && *{$table_hr}{$type};
100             }
101              
102             sub copy_constant {
103 5     5 0 24517 my ($var) = @_;
104              
105 5         8 my $ref = _get_table_hr($var);
106              
107 5         6 my @value;
108              
109 5 50       14 if ('SCALAR' eq ref $ref) {
    50          
110 0         0 @value = ($$ref);
111             }
112             elsif ('ARRAY' eq ref $ref) {
113 0         0 @value = @$ref;
114             }
115             else {
116 5         4 @value = *{$ref}{'CODE'}->();
  5         20  
117             }
118              
119 5 100       13 if (@value > 1) {
120 4         8 Call::Context::must_be_list();
121 2         42 return @value;
122             }
123              
124 1         5 return $value[0];
125             }
126              
127             #Referenced in tests.
128 0     0   0 sub _perl_supports_getting_list_constant_ref { return $^V ge MIN_LIST_CONSTANT_PERL_VERSION() }
129              
130             sub _get_constant {
131 0     0   0 my ($var) = @_;
132              
133 0         0 my $ref = _get_table_hr($var);
134              
135 0 0 0     0 if ('SCALAR' ne ref($ref) && 'ARRAY' ne ref($ref)) {
136 0         0 my $msg = "$var is a regular symbol table entry, not a constant.";
137              
138 0 0       0 if ( !_perl_supports_getting_list_constant_ref() ) {
139 0         0 $msg .= " Your Perl version ($^V) stores list constants in the symbol table as CODE references rather than ARRAYs; maybe that’s the issue?";
140             }
141              
142 0         0 die $msg;
143             }
144              
145 0         0 return $ref;
146             }
147              
148             sub get_names {
149 1     1 0 11 my ($module) = @_;
150              
151 1   33     6 $module ||= (caller 0)[0];
152              
153 1         14 Call::Context::must_be_list();
154              
155 1         11 my $table_hr = _get_module_table_hr($module);
156              
157 1 50       3 die "Unknown namespace: “$module”" if !$table_hr;
158              
159 1         28 return keys %$table_hr;
160             }
161              
162             #----------------------------------------------------------------------
163             # To be completed if needed:
164             #
165             #sub list_sigils {
166             # my ($full_name) = @_;
167             #
168             # Call::Context::must_be_list();
169             #
170             # my ($module, $name) = ($full_name =~ m<(?:(.+)::)?(.+)>);
171             #
172             # my $table_hr = _get_table_for_module_name($module);
173             # my $glob = *{$table_hr};
174             #
175             # return
176             #}
177             #
178              
179              
180             #----------------------------------------------------------------------
181              
182             sub _get_module_table_hr {
183 16     16   14 my ($module) = @_;
184              
185 16         33 my @nodes = split m<::>, $module;
186              
187 16         17 my $table_hr = \%main::;
188              
189 16         17 my $pkg = q<>;
190              
191 16         19 for my $n (@nodes) {
192 47         85 $table_hr = $table_hr->{"$n\::"};
193 47         49 $pkg .= "$n\::";
194             }
195              
196 16         26 return $table_hr;
197             }
198              
199             sub _get_table_hr {
200 15     15   21 my ($name) = @_;
201              
202 15 50       71 $name =~ m<\A (?: (.+) ::)? ([^:]+ (?: ::)?) \z>x or do {
203 0         0 die "Invalid variable name: “$name”";
204             };
205              
206 15   66     42 my $module = $1 || (caller 1)[0];
207              
208 15         125 my $table_hr = _get_module_table_hr($module);
209              
210 15         46 return $table_hr->{$2};
211             }
212              
213             1;