File Coverage

blib/lib/Export/Attrs.pm
Criterion Covered Total %
statement 105 144 72.9
branch 13 44 29.5
condition 6 20 30.0
subroutine 16 17 94.1
pod n/a
total 140 225 62.2


line stmt bran cond sub pod time code
1             package Export::Attrs;
2              
3             our $VERSION = 'v0.1.0';
4              
5 2     2   81899 use warnings;
  2         2  
  2         49  
6 2     2   6 use strict;
  2         2  
  2         26  
7 2     2   5 use Carp;
  2         4  
  2         85  
8 2     2   902 use Attribute::Handlers;
  2         6598  
  2         7  
9 2     2   729 use PadWalker qw( var_name peek_my );
  2         830  
  2         124  
10              
11             my %IMPORT_for;
12              
13             sub import {
14 2     2   13 my $caller = caller;
15 2     2   9 no strict 'refs';
  2         2  
  2         1263  
16 2         3 *{$caller.'::import'} = \&_generic_import;
  2         8  
17 2     0   6 *{$caller.'::IMPORT'} = sub (&) { $IMPORT_for{$caller} = shift };
  2         6  
  0         0  
18 2         3 for my $var_type (qw( SCALAR ARRAY HASH CODE )) {
19 8         5 *{$caller.'::MODIFY_'.$var_type.'_ATTRIBUTES'} = \&_generic_handler;
  8         21  
20             }
21 2         32 return;
22             }
23              
24             my %tagsets_for;
25             my %is_exported_from;
26             my %named_tagsets_for;
27             my %decl_loc_for;
28             my %name_of;
29              
30             my $IDENT = '[^\W\d]\w*';
31              
32             sub _generic_handler {
33 1     1   26 my ($package, $referent, @attrs) = @_;
34              
35             ATTR:
36 1         2 for my $attr (@attrs) {
37              
38 1 50 50     11 ($attr||=q{}) =~ s/\A Export (?: \( (.*) \) )? \z/$1||q{}/exms
  1 50       8  
39             or next ATTR;
40              
41 1         2 my @tagsets = grep {length $_} split m/ \s+,?\s* | ,\s* /xms, $attr;
  0         0  
42              
43 1         3 my (undef, $file, $line) = caller(1);
44 1         4 $file =~ s{.*/}{}xms;
45              
46 1 50       2 if (my @bad_tags = grep {!m/\A :$IDENT \z/xms} @tagsets) {
  0         0  
47 0 0       0 die 'Bad tagset',
48             (@bad_tags==1?' ':'s '),
49             "in :Export attribute at '$file' line $line: [@bad_tags]\n";
50             }
51              
52 1   50     4 my $tagsets = $tagsets_for{$package} ||= {};
53              
54 1         2 for my $tagset (@tagsets) {
55 0         0 push @{ $tagsets->{$tagset} }, $referent;
  0         0  
56             }
57 1         1 push @{ $tagsets->{':ALL'} }, $referent;
  1         1  
58              
59 1         2 $is_exported_from{$package}{$referent} = 1;
60 1         2 $decl_loc_for{$referent} = "$file line $line";
61 1         2 $name_of{$referent} = _get_lexical_name($referent);
62              
63 1         2 undef $attr;
64              
65             }
66              
67 1         1 return grep {defined $_} @attrs;
  1         3  
68             }
69              
70             my %desc_for = (
71             SCALAR => 'lexical scalar variable',
72             ARRAY => 'lexical array variable',
73             HASH => 'lexical hash variable',
74             CODE => 'anonymous subroutine',
75             );
76              
77             my %hint_for = (
78             SCALAR => "(declare the variable with 'our' instead of 'my')",
79             ARRAY => "(declare the variable with 'our' instead of 'my')",
80             HASH => "(declare the variable with 'our' instead of 'my')",
81             CODE => "(specify a name after the 'sub' keyword)",
82             );
83              
84             sub _get_lexical_name {
85 1     1   1 my ($var_ref) = @_;
86 1 50       3 return if ref $var_ref eq 'CODE';
87              
88             SEARCH:
89 0         0 for my $up_level (1..(~0>>1)-1) {
90 0 0       0 my $sym_tab_ref = eval { peek_my($up_level) }
  0         0  
91             or last SEARCH;
92              
93 0         0 for my $var_name (keys %{$sym_tab_ref}) {
  0         0  
94 0 0       0 return $var_name if $var_ref == $sym_tab_ref->{$var_name};
95             }
96             }
97 0         0 return;
98             }
99              
100             sub _invert_tagset {
101 1     1   2 my ($package, $tagset) = @_;
102 1         1 my %inverted_tagset;
103              
104 1         1 for my $tag (keys %{$tagset}) {
  1         3  
105 1         1 for my $sub_ref (@{$tagset->{$tag}}) {
  1         2  
106 1         2 my $type = ref $sub_ref;
107             my $sym = Attribute::Handlers::findsym($package, $sub_ref, $type)
108 1 50 33     2 || $name_of{$sub_ref}
109             or die "Can't export $desc_for{$type} ",
110             "at $decl_loc_for{$sub_ref}\n$hint_for{$type}\n";
111 1 50       63 if (ref $sym) {
112 1         1 $sym = *{$sym}{NAME};
  1         1  
113             }
114 1         3 $inverted_tagset{$tag}{$sym} = $sub_ref;
115             }
116             }
117              
118 1         3 return \%inverted_tagset;
119             }
120              
121             my %type_for = qw( $ SCALAR @ ARRAY % HASH );
122              
123             # Reusable import() subroutine for all packages...
124             sub _generic_import {
125 1     1   69 my $package = shift;
126              
127             my $tagset
128             = $named_tagsets_for{$package}
129 1   33     5 ||= _invert_tagset($package, $tagsets_for{$package});
130              
131 1         1 my $is_exported = $is_exported_from{$package};
132              
133 1         1 my $errors;
134              
135             my %request;
136 0         0 my $subs_ref;
137              
138 1         2 my $args_supplied = @_;
139              
140 1         0 my $argno = 0;
141             REQUEST:
142 1         3 while ($argno < @_) {
143 1         1 my $request = $_[$argno];
144 1 50 0     458 if (my ($sub_name) = $request =~ m/\A &? ($IDENT) (?:\(\))? \z/xms) {
    0          
    0          
145 1 50       3 if (exists $request{$sub_name}) {
146 0         0 splice @_, $argno, 1;
147 0         0 next REQUEST;
148             }
149 2     2   9 no strict 'refs';
  2         1  
  2         44  
150 2     2   6 no warnings 'once';
  2         2  
  2         160  
151 1 50       1 if (my $sub_ref = *{$package.'::'.$sub_name}{CODE}) {
  1         5  
152 1 50       4 if ($is_exported->{$sub_ref}) {
153 1         1 $request{$sub_name} = $sub_ref;
154 1         3 splice @_, $argno, 1;
155 1         9 next REQUEST;
156             }
157             }
158             }
159             elsif (my ($sigil, $name) = $request =~ m/\A ([\$\@%])($IDENT) \z/xms) {
160 0 0       0 next REQUEST if exists $request{$sigil.$name};
161 2     2   6 no strict 'refs';
  2         2  
  2         37  
162 2     2   12 no warnings 'once';
  2         2  
  2         319  
163 0 0       0 if (my $var_ref = *{$package.'::'.$name}{$type_for{$sigil}}) {
  0         0  
164 0 0       0 if ($is_exported->{$var_ref}) {
165 0         0 $request{$sigil.$name} = $var_ref;
166 0         0 splice @_, $argno, 1;
167 0         0 next REQUEST;
168             }
169             }
170             }
171             elsif ($request =~ m/\A :$IDENT \z/xms
172             and $subs_ref = $tagset->{$request}) {
173 0         0 @request{keys %{$subs_ref}} = values %{$subs_ref};
  0         0  
  0         0  
174 0         0 splice @_, $argno, 1;
175 0         0 next REQUEST;
176             }
177 0         0 $errors .= " $request";
178 0         0 $argno++;
179             }
180              
181             # Report unexportable requests...
182 1         2 my $real_import = $IMPORT_for{$package};
183              
184 1 50 33     3 croak "$package does not export:$errors\nuse $package failed"
185             if $errors && !$real_import;
186              
187 1 50       2 if (!$args_supplied) {
188 0   0     0 %request = %{$tagset->{':DEFAULT'}||={}}
  0         0  
189             }
190              
191 1   50     4 my $mandatory = $tagset->{':MANDATORY'} ||= {};
192 1         1 @request{ keys %{$mandatory} } = values %{$mandatory};
  1         1  
  1         3  
193              
194 1         43 my $caller = caller;
195              
196 1         3 for my $sub_name (keys %request) {
197 2     2   7 no strict 'refs';
  2         2  
  2         357  
198 1         4 my ($sym_name) = $sub_name =~ m{\A [\$\@&%]? (.*)}xms;
199 1         2 *{$caller.'::'.$sym_name} = $request{$sub_name};
  1         4  
200             }
201              
202 1 50       5 if ($real_import) {
203 0         0 my $idx=0;
204 0         0 while ($idx < @_) {
205 0 0       0 if (defined $_[$idx]) { $idx++ }
  0         0  
206 0         0 else { splice @_, $idx, 1 }
207             }
208 0         0 goto &{$real_import};
  0         0  
209             }
210 1         4 return;
211             }
212              
213             1; # Magic true value required at end of module
214             __END__