File Coverage

inc/Exporter/Tiny.pm
Criterion Covered Total %
statement 99 224 44.2
branch 24 124 19.3
condition 5 37 13.5
subroutine 18 31 58.0
pod 0 2 0.0
total 146 418 34.9


line stmt bran cond sub pod time code
1             #line 1
2             package Exporter::Tiny;
3 6     6   102  
  6         24  
4 6     6   33 use 5.006001;
  6         11  
  6         122  
5 6     6   30 use strict;
  6     6   15  
  6         197  
  6         46  
  6         22  
  6         3253  
6             use warnings; no warnings qw(void once uninitialized numeric redefine);
7              
8             our $AUTHORITY = 'cpan:TOBYINK';
9             our $VERSION = '1.002001';
10             our @EXPORT_OK = qw< mkopt mkopt_hash _croak _carp >;
11 0     0   0  
  0         0  
  0         0  
  0         0  
12 0     0   0 sub _croak ($;@) { require Carp; my $fmt = shift; @_ = sprintf($fmt, @_); goto \&Carp::croak }
  0         0  
  0         0  
  0         0  
13             sub _carp ($;@) { require Carp; my $fmt = shift; @_ = sprintf($fmt, @_); goto \&Carp::carp }
14              
15             my $_process_optlist = sub
16             {
17             my $class = shift;
18             my ($global_opts, $opts, $want, $not_want) = @_;
19            
20             while (@$opts)
21             {
22             my $opt = shift @{$opts};
23             my ($name, $value) = @$opt;
24            
25             ($name =~ m{\A\!(/.+/[msixpodual]+)\z}) ?
26             do {
27             my @not = $class->_exporter_expand_regexp($1, $value, $global_opts);
28             ++$not_want->{$_->[0]} for @not;
29             } :
30             ($name =~ m{\A\!(.+)\z}) ?
31             (++$not_want->{$1}) :
32             ($name =~ m{\A[:-](.+)\z}) ?
33             push(@$opts, $class->_exporter_expand_tag($1, $value, $global_opts)) :
34             ($name =~ m{\A/.+/[msixpodual]+\z}) ?
35             push(@$opts, $class->_exporter_expand_regexp($name, $value, $global_opts)) :
36             # else ?
37             push(@$want, $opt);
38             }
39             };
40              
41             sub import
42 1     1   2 {
43 1 50 33     8 my $class = shift;
  0         0  
44 1 50       5 my $global_opts = +{ @_ && ref($_[0]) eq q(HASH) ? %{+shift} : () };
45             $global_opts->{into} = caller unless exists $global_opts->{into};
46 1         2
47 1         2 my @want;
48 6 50   6   55 my %not_want; $global_opts->{not} = \%not_want;
  6         21  
  6         4957  
  1         2  
  1         3  
  0         0  
49 1         3 my @args = do { no strict qw(refs); @_ ? @_ : @{"$class\::EXPORT"} };
50 1         4 my $opts = mkopt(\@args);
51             $class->$_process_optlist($global_opts, $opts, \@want, \%not_want);
52 1         4
53 1         9 my $permitted = $class->_exporter_permitted_regexp($global_opts);
54             $class->_exporter_validate_opts($global_opts);
55 1         3
56             for my $wanted (@want)
57 1 50       4 {
58             next if $not_want{$wanted->[0]};
59 1         4
60             my %symbols = $class->_exporter_expand_sub(@$wanted, $global_opts, $permitted);
61 1         11 $class->_exporter_install_sub($_, $wanted->[1], $global_opts, $symbols{$_})
62             for keys %symbols;
63             }
64             }
65              
66             sub unimport
67 0     0   0 {
68 0 0 0     0 my $class = shift;
  0         0  
69 0 0       0 my $global_opts = +{ @_ && ref($_[0]) eq q(HASH) ? %{+shift} : () };
70 0         0 $global_opts->{into} = caller unless exists $global_opts->{into};
71             $global_opts->{is_unimport} = 1;
72 0         0
73 0         0 my @want;
74 0 0       0 my %not_want; $global_opts->{not} = \%not_want;
  0         0  
  0         0  
  0         0  
75 0         0 my @args = do { our %TRACKED; @_ ? @_ : keys(%{$TRACKED{$class}{$global_opts->{into}}}) };
76 0         0 my $opts = mkopt(\@args);
77             $class->$_process_optlist($global_opts, $opts, \@want, \%not_want);
78 0         0
79 0         0 my $permitted = $class->_exporter_permitted_regexp($global_opts);
80             $class->_exporter_validate_unimport_opts($global_opts);
81 0         0
82 0 0       0 my $expando = $class->can('_exporter_expand_sub');
83             $expando = undef if $expando == \&_exporter_expand_sub;
84 0         0
85             for my $wanted (@want)
86 0 0       0 {
87             next if $not_want{$wanted->[0]};
88 0 0       0
89             if ($wanted->[1])
90             {
91 0 0 0     0 _carp("Passing options to unimport '%s' makes no sense", $wanted->[0])
  0         0  
92             unless (ref($wanted->[1]) eq 'HASH' and not keys %{$wanted->[1]});
93             }
94            
95             my %symbols = defined($expando)
96 0 0   0   0 ? $class->$expando(@$wanted, $global_opts, $permitted)
  0         0  
97             : ($wanted->[0] => sub { "dummy" });
98 0         0 $class->_exporter_uninstall_sub($_, $wanted->[1], $global_opts)
99             for keys %symbols;
100             }
101             }
102              
103             # Called once per import/unimport, passed the "global" import options.
104             # Expected to validate the options and carp or croak if there are problems.
105             # Can also take the opportunity to do other stuff if needed.
106 1     1   1 #
107 0     0   0 sub _exporter_validate_opts { 1 }
108             sub _exporter_validate_unimport_opts { 1 }
109              
110             # Called after expanding a tag or regexp to merge the tag's options with
111             # any sub-specific options.
112             #
113             sub _exporter_merge_opts
114 0     0   0 {
115 0         0 my $class = shift;
116             my ($tag_opts, $global_opts, @stuff) = @_;
117 0 0       0
118             $tag_opts = {} unless ref($tag_opts) eq q(HASH);
119 0 0 0     0 _croak('Cannot provide an -as option for tags')
120             if exists $tag_opts->{-as} && ref $tag_opts->{-as} ne 'CODE';
121 0         0
122 0         0 my $optlist = mkopt(\@stuff);
123             for my $export (@$optlist)
124 0 0 0     0 {
125             next if defined($export->[1]) && ref($export->[1]) ne q(HASH);
126 0 0       0
  0         0  
127             my %sub_opts = ( %{ $export->[1] or {} }, %$tag_opts );
128 0 0 0     0 $sub_opts{-prefix} = sprintf('%s%s', $tag_opts->{-prefix}, $export->[1]{-prefix})
129             if exists($export->[1]{-prefix}) && exists($tag_opts->{-prefix});
130 0 0 0     0 $sub_opts{-suffix} = sprintf('%s%s', $export->[1]{-suffix}, $tag_opts->{-suffix})
131 0         0 if exists($export->[1]{-suffix}) && exists($tag_opts->{-suffix});
132             $export->[1] = \%sub_opts;
133 0         0 }
134             return @$optlist;
135             }
136              
137             # Given a tag name, looks it up in %EXPORT_TAGS and returns the list of
138             # associated functions. The default implementation magically handles tags
139             # "all" and "default". The default implementation interprets any undefined
140             # tags as being global options.
141             #
142             sub _exporter_expand_tag
143 6     6   58 {
  6         11  
  6         1401  
144             no strict qw(refs);
145 0     0   0
146 0         0 my $class = shift;
147 0         0 my ($name, $value, $globals) = @_;
  0         0  
148             my $tags = \%{"$class\::EXPORT_TAGS"};
149            
150 0 0       0 return $class->_exporter_merge_opts($value, $globals, $tags->{$name}->($class, @_))
151             if ref($tags->{$name}) eq q(CODE);
152 0         0
153 0 0       0 return $class->_exporter_merge_opts($value, $globals, @{$tags->{$name}})
154             if exists $tags->{$name};
155 0 0       0
  0         0  
  0         0  
156             return $class->_exporter_merge_opts($value, $globals, @{"$class\::EXPORT"}, @{"$class\::EXPORT_OK"})
157             if $name eq 'all';
158 0 0       0
  0         0  
159             return $class->_exporter_merge_opts($value, $globals, @{"$class\::EXPORT"})
160             if $name eq 'default';
161 0   0     0
162 0         0 $globals->{$name} = $value || 1;
163             return;
164             }
165              
166             # Given a regexp-like string, looks it up in @EXPORT_OK and returns the
167             # list of matching functions.
168             #
169             sub _exporter_expand_regexp
170 6     6   45 {
  6         26  
  6         963  
171 0     0   0 no strict qw(refs);
172             our %TRACKED;
173 0         0
174 0         0 my $class = shift;
175 0         0 my ($name, $value, $globals) = @_;
176             my $compiled = eval("qr$name");
177            
178 0         0 my @possible = $globals->{is_unimport}
179 0 0       0 ? keys( %{$TRACKED{$class}{$globals->{into}}} )
  0         0  
180             : @{"$class\::EXPORT_OK"};
181 0         0
182             $class->_exporter_merge_opts($value, $globals, grep /$compiled/, @possible);
183             }
184              
185             # Helper for _exporter_expand_sub. Returns a regexp matching all subs in
186             # the exporter package which are available for export.
187             #
188             sub _exporter_permitted_regexp
189 6     6   47 {
  6         12  
  6         1056  
190 1     1   2 no strict qw(refs);
191             my $class = shift;
192 373 50       650 my $re = join "|", map quotemeta, sort {
193 1         2 length($b) <=> length($a) or $a cmp $b
  1         4  
  1         7  
194 1         393 } @{"$class\::EXPORT"}, @{"$class\::EXPORT_OK"};
195             qr{^(?:$re)$}ms;
196             }
197              
198             # Given a sub name, returns a hash of subs to install (usually just one sub).
199             # Keys are sub names, values are coderefs.
200             #
201             sub _exporter_expand_sub
202 1     1   2 {
203 1         3 my $class = shift;
204 1   33     3 my ($name, $value, $globals, $permitted) = @_;
205             $permitted ||= $class->_exporter_permitted_regexp($globals);
206 6     6   44
  6         12  
  6         3494  
207             no strict qw(refs);
208 1         1
209 1 50       4 my $sigil = "&";
210 0         0 if ($name =~ /\A([&\$\%\@\*])(.+)\z/) {
211 0         0 $sigil = $1;
212 0 0       0 $name = $2;
213 0         0 if ($sigil eq '*') {
214             _croak("Cannot export symbols with a * sigil");
215             }
216 1 50       3 }
217             my $sigilname = $sigil eq '&' ? $name : "$sigil$name";
218 1 50       9
219             if ($sigilname =~ $permitted)
220             {
221             my $generatorprefix = {
222             '&' => "_generate_",
223             '$' => "_generateScalar_",
224             '@' => "_generateArray_",
225 1         5 '%' => "_generateHash_",
226             }->{$sigil};
227 1         8
228 1 50       3 my $generator = $class->can("$generatorprefix$name");
229             return $sigilname => $class->$generator($sigilname, $value, $globals) if $generator;
230 1         4
231 1 50       16 my $sub = $class->can($name);
232             return $sigilname => $sub if $sub;
233            
234 0 0       0 # Could do this more cleverly, but this works.
235 0         0 if ($sigil ne '&') {
236 0 0       0 my $evalled = eval "\\${sigil}${class}::${name}";
237             return $sigilname => $evalled if $evalled;
238             }
239             }
240 0         0
241             $class->_exporter_fail(@_);
242             }
243              
244             # Called by _exporter_expand_sub if it is unable to generate a key-value
245             # pair for a sub.
246             #
247             sub _exporter_fail
248 0     0   0 {
249 0         0 my $class = shift;
250 0 0       0 my ($name, $value, $globals) = @_;
251 0         0 return if $globals->{is_unimport};
252             _croak("Could not find sub '%s' exported by %s", $name, $class);
253             }
254              
255             # Actually performs the installation of the sub into the target package. This
256             # also handles renaming the sub.
257             #
258             sub _exporter_install_sub
259 1     1   2 {
260 1         3 my $class = shift;
261             my ($name, $value, $globals, $sym) = @_;
262 1         1
263 1   33     5 my $into = $globals->{into};
264             my $installer = $globals->{installer} || $globals->{exporter};
265            
266             $name =
267             ref $globals->{as} ? $globals->{as}->($name) :
268             ref $value->{-as} ? $value->{-as}->($name) :
269 1 50       5 exists $value->{-as} ? $value->{-as} :
    50          
    50          
270             $name;
271 1 50       2
272             return unless defined $name;
273 1         2
274 1 50       3 my $sigil = "&";
275 1 50       3 unless (ref($name)) {
276 0         0 if ($name =~ /\A([&\$\%\@\*])(.+)\z/) {
277 0         0 $sigil = $1;
278 0 0       0 $name = $2;
279 0         0 if ($sigil eq '*') {
280             _croak("Cannot export symbols with a * sigil");
281             }
282 1         4 }
283 1         2 my ($prefix) = grep defined, $value->{-prefix}, $globals->{prefix}, q();
284 1         3 my ($suffix) = grep defined, $value->{-suffix}, $globals->{suffix}, q();
285             $name = "$prefix$name$suffix";
286             }
287 1 50       10
288             my $sigilname = $sigil eq '&' ? $name : "$sigil$name";
289            
290             # if ({qw/$ SCALAR @ ARRAY % HASH & CODE/}->{$sigil} ne ref($sym)) {
291             # warn $sym;
292             # warn $sigilname;
293             # _croak("Reference type %s does not match sigil %s", ref($sym), $sigil);
294             # }
295 1 50       3
296 1 50       3 return ($$name = $sym) if ref($name) eq q(SCALAR);
297             return ($into->{$sigilname} = $sym) if ref($into) eq q(HASH);
298 6     6   55
  6         10  
  6         1479  
299 1         1 no strict qw(refs);
300             our %TRACKED;
301 1 50 33     4
  1   33     7  
  0         0  
302             if (ref($sym) eq 'CODE' and exists &{"$into\::$name"} and \&{"$into\::$name"} != $sym)
303 0         0 {
304             my ($level) = grep defined, $value->{-replace}, $globals->{replace}, q(0);
305             my $action = {
306             carp => \&_carp,
307             0 => \&_carp,
308             '' => \&_carp,
309             warn => \&_carp,
310             nonfatal => \&_carp,
311             croak => \&_croak,
312             fatal => \&_croak,
313 0   0 0   0 die => \&_croak,
314             }->{$level} || sub {};
315            
316             # Don't complain about double-installing the same sub. This isn't ideal
317 0 0   0   0 # because the same named sub might be generated in two different ways.
318             $action = sub {} if $TRACKED{$class}{$into}{$sigilname};
319 0 0       0
320             $action->(
321             $action == \&_croak
322             ? "Refusing to overwrite existing sub '%s::%s' with sub '%s' exported by %s"
323             : "Overwriting existing sub '%s::%s' with sub '%s' exported by %s",
324             $into,
325             $name,
326             $_[0],
327             $class,
328             );
329             }
330 1         8
331             $TRACKED{$class}{$into}{$sigilname} = $sym;
332 6     6   45
  6         36  
  6         1123  
333             no warnings qw(prototype);
334             $installer
335 1 50       5 ? $installer->($globals, [$sigilname, $sym])
  1         37  
336             : (*{"$into\::$name"} = $sym);
337             }
338              
339             sub _exporter_uninstall_sub
340 0     0   0 {
341 0         0 our %TRACKED;
342 0         0 my $class = shift;
343 0         0 my ($name, $value, $globals, $sym) = @_;
344 0 0       0 my $into = $globals->{into};
345             ref $into and return;
346 6     6   63
  6         15  
  6         3347  
347             no strict qw(refs);
348 0         0  
349 0 0       0 my $sigil = "&";
350 0         0 if ($name =~ /\A([&\$\%\@\*])(.+)\z/) {
351 0         0 $sigil = $1;
352 0 0       0 $name = $2;
353 0         0 if ($sigil eq '*') {
354             _croak("Cannot export symbols with a * sigil");
355             }
356 0 0       0 }
357             my $sigilname = $sigil eq '&' ? $name : "$sigil$name";
358 0 0       0
359 0         0 if ($sigil ne '&') {
360             _croak("Unimporting non-code symbols not supported yet");
361             }
362              
363             # Cowardly refuse to uninstall a sub that differs from the one
364 0         0 # we installed!
365 0 0       0 my $our_coderef = $TRACKED{$class}{$into}{$name};
  0         0  
  0         0  
366 0 0       0 my $cur_coderef = exists(&{"$into\::$name"}) ? \&{"$into\::$name"} : -1;
367             return unless $our_coderef == $cur_coderef;
368 0         0
  0         0  
369 0         0 my $stash = \%{"$into\::"};
370 0         0 my $old = delete $stash->{$name};
371 0         0 my $full_name = join('::', $into, $name);
372             foreach my $type (qw(SCALAR HASH ARRAY IO)) # everything but the CODE
373 0 0       0 {
  0         0  
374 0         0 next unless defined(*{$old}{$type});
  0         0  
375             *$full_name = *{$old}{$type};
376             }
377 0         0
378             delete $TRACKED{$class}{$into}{$name};
379             }
380              
381             sub mkopt
382 1 50   1 0 3 {
383 1         1 my $in = shift or return [];
384             my @out;
385 1 0       3
    50          
386             $in = [map(($_ => ref($in->{$_}) ? $in->{$_} : ()), sort keys %$in)]
387             if ref($in) eq q(HASH);
388 1         3
389             for (my $i = 0; $i < @$in; $i++)
390 1         2 {
391 1         2 my $k = $in->[$i];
392             my $v;
393 1 0       2
    0          
    50          
394             ($i == $#$in) ? ($v = undef) :
395             !defined($in->[$i+1]) ? (++$i, ($v = undef)) :
396             !ref($in->[$i+1]) ? ($v = undef) :
397             ($v = $in->[++$i]);
398 1         4
399             push @out, [ $k => $v ];
400             }
401 1         2
402             \@out;
403             }
404              
405             sub mkopt_hash
406 0 0   0 0   {
407 0           my $in = shift or return;
  0            
408 0           my %out = map +($_->[0] => $_->[1]), @{ mkopt($in) };
409             \%out;
410             }
411              
412             1;
413              
414             __END__