File Coverage

blib/lib/Sub/Talisman.pm
Criterion Covered Total %
statement 95 98 96.9
branch 7 12 58.3
condition n/a
subroutine 27 28 96.4
pod 4 4 100.0
total 133 142 93.6


line stmt bran cond sub pod time code
1             package Sub::Talisman;
2              
3 1     1   44849 use 5.008;
  1         5  
  1         36  
4 1     1   5 use strict;
  1         1  
  1         26  
5 1     1   5 use warnings;
  1         5  
  1         55  
6              
7             BEGIN {
8 1     1   2 $Sub::Talisman::AUTHORITY = 'cpan:TOBYINK';
9 1         14 $Sub::Talisman::VERSION = '0.005';
10             }
11              
12 1     1   7566 use Attribute::Handlers;
  1         4051  
  1         6  
13 1     1   1066 use Sub::Identify qw( get_code_info );
  1         1713  
  1         67  
14 1     1   992 use Sub::Name qw( subname );
  1         1216  
  1         71  
15 1     1   1233 use Scalar::Does qw( does -constants );
  1         219687  
  1         13  
16 1     1   1997 use Scalar::Util qw( refaddr );
  1         2  
  1         225  
17              
18             sub _identify
19             {
20             my $sub = shift;
21             if (does $sub, CODE)
22             {
23             my ($p, $n) = get_code_info($sub);
24             $n .= sprintf('(%d)', refaddr($sub)) if $n eq '__ANON__';
25             return ($p, $n);
26             }
27             elsif ($sub =~ /::/)
28             {
29             my ($p, $n) = ($sub =~ /^(.*)::(\w+)$/);
30             $p = 'main' if $p eq q();
31             return ($p, $n);
32             }
33             else
34             {
35             return ($_[0], $sub);
36             }
37             }
38              
39 1     1   6 use namespace::clean;
  1         1  
  1         4  
40             my (%TALI, %FETCH);
41              
42             sub setup_for
43             {
44 4     4 1 5 my ($class, $caller, $opts) = @_;
45 4         19 my $atr = $opts->{attribute};
46 1     1   5 eval qq{
  1     1   1  
  1     1   8  
  0     1   0  
  0     0   0  
  0     3   0  
  1     2   4  
  1     2   2  
  1         3  
  3         2429  
  3         13  
  3         8  
  1         5  
  1         1  
  1         3  
  2         75  
  2         7  
  2         6  
  1         4  
  1         2  
  1         3  
  2         81  
  2         8  
  2         6  
  4         452  
47             package $caller;
48             sub $atr :ATTR(CODE)
49             {
50             unshift \@_, q[$class], q[$caller];
51             my \$callback = "$class"->can("_callback");
52             goto \$callback;
53             }
54             };
55 4         708 namespace::clean->import(
56             -cleanee => $caller,
57             $opts->{attribute},
58             );
59 4 100       141 unless ($FETCH{$caller})
60             {
61 1     1   357 no strict 'refs';
  1         2  
  1         623  
62 1         2 my $subname = "$caller\::FETCH_CODE_ATTRIBUTES";
63             *$subname = subname $subname, sub {
64 1     1   419 my ($class, $sub) = @_;
        1      
65 1 50       5 return map { /(\w+)$/ ? $1 : () }
  3         26  
66             __PACKAGE__->get_attributes($sub);
67 1         10 };
68 1         3 $FETCH{$caller} = 1;
69             }
70             }
71              
72             sub import
73             {
74 1     1   6 my $class = shift;
75 1         2 my $caller = caller;
76 1         2 foreach my $atr (@_)
77             {
78 4         15 $class->setup_for($caller, { attribute => $atr });
79             }
80             }
81              
82             sub _process_params
83             {
84 7     7   9 my ($class, $attr, $params) = @_;
85 7         10 return $params;
86             }
87              
88             sub _callback
89             {
90 7     7   17 my ($class, $installation_pkg, $caller_pkg, $glob, $ref, $attr, $params, $step, $file, $line) = @_;
91 7         14 my ($p, $n) = _identify($ref, scalar caller);
92 7         16 my $full_attr = join q[::], $installation_pkg, $attr;
93 7         17 my $obj = $class->_process_params($full_attr, $params);
94 7         32 $TALI{$p}{$n}{$full_attr} = $obj;
95             }
96              
97             sub get_attributes
98             {
99 4     4 1 1060 my ($class, $sub) = @_;
100 4         12 my ($p, $n) = _identify($sub, scalar caller);
101 4 50       6 my %hash = %{ $TALI{$p}{$n} || {} };
  4         26  
102 4         26 return sort keys %hash;
103             }
104              
105             sub get_attribute_parameters
106             {
107 3     3 1 383 my ($class, $sub, $attr) = @_;
108 3 50       10 $attr = scalar(caller).'::'.$attr unless $attr =~ /::/;
109 3         6 my ($p, $n) = _identify($sub, scalar caller);
110 3 50       12 return unless exists $TALI{$p}{$n}{$attr};
111 3         15 return $TALI{$p}{$n}{$attr};
112             }
113              
114             sub get_subs
115             {
116 3     3 1 845 my ($class, $attr) = @_;
117 3 50       13 $attr = scalar(caller).'::'.$attr unless $attr =~ /::/;
118 3         2 my @subs;
119 3         6 foreach my $pkg (keys %TALI)
120             {
121 7         24 push @subs,
122 9         14 map { "$pkg\::$_" }
123 9         11 grep { exists $TALI{$pkg}{$_}{$attr} }
124 3         7 grep { not /^__ANON__\([0-9]+\)$/ }
125 3         4 keys %{ $TALI{$pkg} };
126             }
127 3         15 return @subs;
128             }
129              
130             1;
131              
132             __END__
133              
134             =head1 NAME
135              
136             Sub::Talisman - use attributes to tag or classify subs
137              
138             =head1 SYNOPSIS
139              
140             package Local::Example;
141            
142             use Sub::Talisman qw( Awesome Info );
143            
144             sub mysub :Awesome {
145             ...;
146             }
147            
148             sub othersub :Info("Hello World") {
149             ...;
150             }
151            
152             my @awesome_subs = Sub::Talisman->get_subs("Local::Example::Awesome");
153            
154             print Sub::Talisman # prints "Hello World"
155             -> get_attribute_parameters(\&othersub, "Local::Example::Info")
156             -> [0];
157              
158             =head1 DESCRIPTION
159              
160             Sub::Talisman allows you to define "talisman" attibutes for your subs,
161             and provides a basic introspection API for these talismans.
162              
163             =head2 Class Methods
164              
165             Sub::Talisman's methods are designed to be called as class methods.
166              
167             =over
168              
169             =item C<< setup_for $package, \%options >>
170              
171             This is used by C<import> to setup a single attribute. As an example, to
172             create a "Purpose" talisman in UNIVERSAL, then:
173              
174             Sub::Talisman->setup_for(
175             'UNIVERSAL',
176             { attribute => 'Purpose' },
177             );
178              
179             The only option understood is "attribute" which provides the name of the
180             attribute.
181              
182             =item C<< get_attributes($sub) >>
183              
184             Gets a list of attributes associated with the sub. Each attribute is a
185             package-qualified name, such as "Local::Example::Awesome" from the
186             SYNPOSIS.
187              
188             C<< $sub >> can be a code ref or a sub name. In the case of subs which
189             have been exported and imported between packages, using the sub name
190             may not be very reliable. Using a code reference is recommended.
191              
192             This function only returns attributes defined via Sub::Talisman. For
193             other attributes such as the Perl built-in C<< :lvalue >> attribute,
194             see the C<get> function in the L<attributes> package.
195              
196             =item C<< get_attribute_parameters($sub, $attr) >>
197              
198             Given a sub and an attribute name, retrieves the parenthesized list of
199             parameters. For example:
200              
201             sub foo :Info("Hello World") { ... }
202             my $params = Sub::Talisman->get_attribute_parameters(\&foo, "Info");
203              
204             The attribute name can be package-qualified. If it is not, then the
205             caller package is assumed.
206              
207             The list of parameters retrieved is a simple arrayref (or undef if the
208             attribute was used without parentheses). For a more structured approach
209             including compile-time validation of the parameters, see
210             L<Sub::Talisman::Struct>.
211              
212             =item C<< get_subs($attr) >>
213              
214             Finds all subs which have the attribute, and returns a list of their
215             names. Anonymous subs are not returned.
216              
217             =back
218              
219             =head1 CAVEATS
220              
221             =head2 Anonymous subs
222              
223             Talisman attributes may be added to anonymous subs too, but it is
224             suspected that this may not be thread-safe...
225              
226             my $sub = sub :Awesome { ... };
227              
228             Anonymous subs can of course be assigned into the symbol tables, a la:
229              
230             *foo = sub :Awesome { ... };
231              
232             But as far as Sub::Talisman is concerned, they were anonymous at the time
233             of definition, so remain anonymous. A workaround would be:
234              
235             no warnings 'redefine';
236             sub foo :Awesome;
237             *foo = sub :Awesome { ... };
238              
239             =head2 Talisman naming
240              
241             Perl reserves lower-case attributes for its own future use; lower-cased
242             talisman attributes may work, but will probably spew warnings. Try to name
243             your talisman attributes in UpperCamelCase.
244              
245             =head2 Talisman subs
246              
247             Be aware that creating an attribute Foo will also create a sub called "Foo"
248             in your package. Sub::Talisman uses L<namespace::clean> to later wipe that
249             sub away, but that temporary sub does need to exist during compile-time,
250             so you won't be able to use that name for your own subs.
251              
252             =head1 BUGS
253              
254             Please report any bugs to
255             L<http://rt.cpan.org/Dist/Display.html?Queue=Sub-Talisman>.
256              
257             =head1 SEE ALSO
258              
259             L<attributes>, L<Attribute::Handlers>, L<Sub::Talisman::Struct>.
260              
261             =head1 AUTHOR
262              
263             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
264              
265             =head1 COPYRIGHT AND LICENCE
266              
267             This software is copyright (c) 2012 by Toby Inkster.
268              
269             This is free software; you can redistribute it and/or modify it under
270             the same terms as the Perl 5 programming language system itself.
271              
272             =head1 DISCLAIMER OF WARRANTIES
273              
274             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
275             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
276             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
277