File Coverage

blib/lib/Class/Tag.pm
Criterion Covered Total %
statement 69 75 92.0
branch 44 54 81.4
condition 17 29 58.6
subroutine 10 11 90.9
pod 0 1 0.0
total 140 170 82.3


line stmt bran cond sub pod time code
1             package Class::Tag;
2            
3             #use 5.006;
4            
5 1     1   24205 use strict qw[vars subs];
  1         2  
  1         197  
6             $Class::Tag::VERSION = '0.02_02';
7            
8             =head1 NAME
9            
10             Class::Tag - programmatically label (mark) classes and modules with tags (key/value pairs) and query those tags
11            
12             =head1 SYNOPSIS
13            
14             Directly using Class::Tag as tagger class:
15            
16             package Foo;
17             use Class::Tag 'tagged';
18             tag Class::Tag 'tagged'; # same, but at run-time
19            
20             Class::Tag->tagged('Foo'); # true
21             Class::Tag->tagged('Bar'); # false
22            
23             #no Class::Tag 'tagged'; # at compile-time, so will not work - instead...
24             untag Class::Tag 'tagged'; # at run-time
25             Class::Tag->tagged('Foo'); # false
26            
27             If no tags are given, the 'is' tag is assumed:
28            
29             package Foo;
30             use Class::Tag; # equivalent to...
31             use Class::Tag 'is'; # same
32             use Class::Tag (); # no tagging
33            
34             New tagger class can be created by simply tagging package with special 'tagger_class' tag using Class::Tag or any other tagger class, and then declaring specific tags or any tag to be used with that new tagger class. Tags declaration is done by tagger class tagging itself with those specific tags or special 'AUTOLOAD' tag:
35            
36             package Awesome; # new tagger class
37             use Class::Tag 'tagger_class'; # must be before following declarations
38             use Awesome 'specific_tag'; # declare 'specific_tag' for use
39             use Awesome 'AUTOLOAD'; # declares that any tag can be used
40            
41             Class::Tag->tagger_class('Awesome'); # true
42            
43             The Class::Tag itself is somewhat similar to the following implicit declaration:
44            
45             package Class::Tag;
46             use Class::Tag 'tagger_class';
47             use Class::Tag 'AUTOLOAD';
48            
49             Attempt to use tag that has not been declared (assuming 'AUTOLOAD' declares any tag) raises exception. Values of declaration tags can be used to modify behavior of tags - see L section for details.
50            
51             Any tagger class can be used as follows (in all following examples the original Class::Tag and Awesome tagger classes are interchangeable), assuming tags have been declared:
52            
53             Using default 'is' tag:
54            
55             package Foo;
56             use Awesome;
57             use Awesome 'is'; # same
58             use Awesome { is => 1 }; # same
59            
60             is Awesome 'Foo'; # true
61             is Awesome 'Bar'; # false
62            
63             Awesome->is('Foo'); # true
64             Awesome->is('Bar'); # false
65            
66             $obj = bless {}, 'Foo';
67            
68             is Awesome $obj; # true
69             Awesome->is($obj); # true
70            
71             $obj = bless {}, 'Bar';
72            
73             is Awesome $obj; # false
74             Awesome->is($obj); # false
75            
76             Using tags 'class' and 'pureperl':
77            
78             package Foo;
79             # tagging class Foo with tags 'class' and 'pureperl' of Awesome tagger class...
80             use Awesome 'class';
81             use Awesome 'pureperl';
82             use Awesome 'class', 'pureperl'; # same
83             use Awesome { class => 1, pureperl => 1 }; # same
84            
85             Awesome->class( 'Foo'); # true
86             Awesome->pureperl('Foo'); # true
87             Awesome->class( 'Bar'); # false
88             Awesome->pureperl('Bar'); # false
89            
90             Using key/value pairs as tags (tag values):
91            
92             package Foo;
93             use Awesome { class => 'is cool', author => 'metadoo' };
94            
95             Awesome->class( 'Foo') eq 'is cool'; # true
96             Awesome->author('Foo') eq 'metadoo'; # true
97            
98             Tag values can be modified with with samename accessors. Object instances from the class inherit tags from the class, so that modifying tag value on instance modifies that of a class, except blessed-hash objects get their own, instance-specific values when modifying tag value on instance - copy-on-write approach:
99            
100             $foo = bless {}, 'Foo';
101            
102             Awesome->class( $foo) eq 'is cool'; # true
103             Awesome->author($foo) eq 'metadoo'; # true (inheriting)
104            
105             Awesome->class( 'Foo', 'pupe-perl') eq 'pupe-perl'; # true
106             Awesome->class( 'Foo') eq 'pupe-perl'; # true
107             Awesome->class( $foo) eq 'pupe-perl'; # true (inheriting)
108             Awesome->class( $foo, 'pupe-perl too') eq 'pupe-perl too'; # true (copy-on-write)
109             Awesome->class( $foo) eq 'pupe-perl too'; # true (copy-on-write)
110             Awesome->class( 'Foo') eq 'pupe-perl'; # true (unmodified)
111            
112             Inheriting tags, using for example the default 'is' tag:
113            
114             package Foo;
115             use Awesome;
116             use Awesome 'is'; # same
117            
118             @Bar::ISA = 'Foo';
119            
120             Awesome->is('Foo'); # true
121             Awesome->is('Bar'); # true ('is' tag inherited)
122             Awesome::is('Foo'); # true
123             Awesome::is('Bar'); # false (no tag inheritance)
124            
125             =head1 DESCRIPTION
126            
127             Sometimes it is necessary to programmatically tag modules and classes with some tags (arbitrary labels or key/value pairs) to be able to assert that you deal with proper class or module. Such need typically arises for plug-in modules, application component modules, complex class inheritance hierarchies, etc.
128            
129             Class::Tag allows programmatically label (mark) classes and modules with arbitrary inheritable tags (key/value pairs) without avoiding collision with methods/attributes/functions of the class/module and query those tags on arbitrary classes and modules.
130            
131             Essentially, Class::Tag is the special variety of class/object attributes that are orthogonal to conventional attributes/methods of the class. And use of tagger classes is a way to extend and partition class's namespace into meaningful orthogonal domains, as well as to extend the notion of the tag.
132            
133             Often tags need to be inheritable (but it is not always the case), and consequently there are two natural solutions: classes-as-tags and methods-as-tags. The classes-as-tags solution is using universal isa() method to see if class has specific parent and effectively using specific parent classes as tags. However, using parent classes just as tags is a limited solution since @ISA is used for different things and better be used for those things exclusively to avoid interferences.
134            
135             Using methods-as-tags approach is about defining and using specific methods as tags. It is way better then classes-as-tags, but but if specific method-tag need to be queried on unknown class/module, the following problems may arise:
136            
137             =over
138            
139             =item Name collision
140            
141             It may be that class/module have defined samename method/attribute by coincidence. Possibility of collision is considerable for short readable names (like 'is'), especially for undocumented tags that are used internally and in case of subclassing. To avoid collision method-tags usually have some unique prefix and may be in upper-case and/or starting with '_', etc. The typical solution is prefixing name of some module as unique identifier, and this is exactly what Class::Tag does in its own way.
142            
143             Foo->Awesome_is;
144            
145             Awesome->is('Foo');
146            
147             Class::Tag allows to either dedicate specific tagger class, either loadable or inlined, just to serve as effective "prefix" with arbitrary risk-free tag names, or use some existing class/module as tagger.
148            
149             =item AUTOLOAD()ing of methods and not-loaded classes/modules
150            
151             If one tries to check tag before either class module has loaded or tagging been done, there will be no tag method yet, so call of tag method will raise an exception. This suggests can() or eval{} wrap to be always used as a precaution.
152            
153             Moreover, potential use of AUTOLOAD defeats unique prefixes in tag method names and requires always calling tag method conditional on result of prior can() (eval{} will not help in this case) checking if tag is defined:
154            
155             $tag_value = $class->is
156             if $class->can('is');
157            
158             Awesome->is($class);
159            
160             =item Tagging
161            
162             Tagging using tag methods is essentially defining an attribute. For tagging classes only it is simple enough, but for tagging blesses-hash objects ends up in writing accessor, so it requires use of some attributes construction module, of which Class::Tag is essentially the one:
163            
164             package Foo;
165             bless $obj = {}, 'Foo';
166            
167             sub Foo::Awesome_is { 'old_value' }; # compile-time tagging
168             *Foo::Awesome_is = sub { 'old_value' };
169             *Foo::Awesome_is = sub { 'new_value' };
170             # tagging object instance of the class...
171             sub Foo::Awesome_is { @_ > 1 ? $_[0]->{Awesome_is} = $_[1] : $_[0]->{Awesome_is} }
172             $obj->Awesome_is('new_value');
173            
174             use Awesome is => 'old_value'; # compile-time tagging
175             is Awesome 'Foo' => 'old_value';
176             is Awesome 'Foo' => 'new_value';
177             is Awesome $obj => 'new_value';
178            
179             except Class::Tag's default accessor implement copy-on-write tags on blessed-hash object instances (and simple tag inheritance by instances otherwise), rather than simplistic accessor in above alternative.
180            
181             =back
182            
183             Class::Tag solves these problems by moving tag creation and tag accessors to "tagger classes".
184            
185             Class::Tag itself serves as tagger class, and each tagger class is a "constructor" for other tagger classes, either loadable or inlined. The use() of tagger class looks as if it exports chosen named tags into packages, but in fact it doesn't - tagger class itself provides samename accessor methods for those tags. As a result, tag names can be arbitrary without risk of collision, so that together with name of tagger class they can be selected to read somewhat meaningful (see examples in L).
186            
187             =head1 Tagger class construction
188            
189             See L for description of new tagger class creation. Tagger class can be created "inline", without using separate .pm file for it.
190            
191             The value of 'tagger_class' tag is reserved for special use in the future, so it should not be used for anything to avoid collision with future versions.
192            
193             There are a few reasons to use multiple tagger classes in addition to Class::Tag itself:
194            
195             =over
196            
197             =item Name
198            
199             Name of the tagger class can be chosen to read naturally and meaningful, in either direct or indirect method call notations i.e. reversing order of tagger and tag names (doubling readability options), with semantically meaningful tags used in the context of given application or problem area domain.
200            
201             =item Collision with Class::Tag guts
202            
203             The original Class::Tag tagger class is not empty, so that not every tag can be used. In contrast, any empty package can be used as tagger classes (but tag(), untag() and Perl's specials, like import(), can(), etc. are still reserved).
204            
205             =item Orthogonality of tags
206            
207             Each tagger class has its own orthogonal tags namespace, so that same tags of different tagger classes do not collide:
208            
209             package Awesome;
210             use Class::Tag 'tagger_class';
211             use Awesome 'AUTOLOAD';
212            
213             package Bad;
214             use Class::Tag 'tagger_class';
215             use Bad 'AUTOLOAD';
216            
217             package Foo;
218             use Awesome 'really';
219             use Awesome { orthogonal => 'awesome' };
220             use Bad { orthogonal => 'bad' };
221            
222             really Awesome 'Foo'; # true
223             really Bad 'Foo'; # false
224             Bad->orthogonal('Foo') eq 'bad'; # true
225             Awesome->orthogonal('Foo') eq 'awesome'; # true
226            
227             Without other tagger classes the tags namespace of Class::Tag would be exposed to higher risk of tags collision, since due to global nature of Perl classes there is always a possibility of collision when same tag is used for unrelated purposes (e.g. in the same inheritance chain, etc.).
228            
229             Since tagger class tags upon use() and classes usually do not export anything, it is often useful and possible to make some existing class a tagger to tag classes that use() it. Moreover, it can be done from a distance, without cognizance of the existing class. The same also applies to modules that are not classes.
230            
231             However, making existing class/module a tagger class requires care to not collide with methods of that class - Class::Tag will raise an exception when such collision happens. It is better not to declare 'AUTOLOAD' for such tagger class.
232            
233             =item Encapsulated tags namespace
234            
235             Tagger class is a class dedicated to defining, managing and documenting specific tags and domain-specific tags namespace.
236            
237             =back
238            
239             =head2 Declaration of tags
240            
241             Attempt to use tag that has not been declared (assuming 'AUTOLOAD' declares any tag) raises exception.
242            
243             In addition, values of declaration tags can be used to modify behavior of tags and, thus, redefine/evolve the whole notion of the tag. If tag is declared with subroutine reference value, that subroutine is called when tag is accessed:
244            
245             package Awesome; # new tagger class
246             use Class::Tag 'tagger_class'; # must be before following declarations
247             use Awesome specific_tag => \&accessor; # declare 'specific_tag' for use with \&accessor
248             use Awesome AUTOLOAD => \&ACCESSOR; # declares that any tag can be used and uses \&accessor for all of them
249            
250             Awesome->specific_tag( $class_or_obj, @args); # is equivalent to...
251             &accessor('Awesome', $class_or_obj, @args);
252            
253             Awesome::specific_tag( $class_or_obj, @args); # is equivalent to...
254             &accessor( undef, $class_or_obj, @args);
255            
256             Awesome->any_other_tag($class_or_obj, @args); # is equivalent to...
257             &ACCESSOR('Awesome', $class_or_obj, @args);
258            
259             Awesome::any_other_tag($class_or_obj, @args); # is equivalent to...
260             &ACCESSOR( undef, $class_or_obj, @args);
261            
262             The Awesome class in above code may also be replaced with object of Awesome class. With custom accessors as above the entire tag syntax can be used for something different.
263            
264             =head1 SUPPORT
265            
266             Send bug reports, patches, ideas, suggestions, feature requests or any module-related information to L. They are welcome and each carefully considered.
267            
268             In particular, if you find certain portions of this documentation either unclear, complicated or incomplete, please let me know, so that I can try to make it better.
269            
270             If you have examples of a neat usage of Class::Tag, drop a line too.
271            
272             =head1 AUTHOR
273            
274             Alexandr Kononoff (L)
275            
276             =head1 COPYRIGHT AND LICENSE
277            
278             Copyright (c) 2010 Alexandr Kononoff (L). All rights reserved.
279            
280             This program is free software; you can use, redistribute and/or modify it either under the same terms as Perl itself or, at your discretion, under following Simplified (2-clause) BSD License terms:
281            
282             Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
283            
284             * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
285             * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
286            
287             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
288            
289             =cut
290            
291 1     1   7 no warnings;
  1         2  
  1         57  
292            
293 1     1   6 use Carp;
  1         8  
  1         1465  
294            
295             sub SIGNATURE () { 'aixfHgvpm7hgVziaO' }
296            
297 237     237   775 sub _tagged_accessor { _subnames( join '_', $_[0], SIGNATURE, $_[1] ) }
298            
299 237     237   197 sub _subnames { my $a; ($a = $_[0]) =~ s/:/_/g; return $a }
  237         540  
  237         450  
300            
301             *unimport = *untag = __PACKAGE__->new_import('unimport');
302             *import = *tag = __PACKAGE__->new_import();
303             import { __PACKAGE__ } 'AUTOLOAD';
304            
305             sub new_import {
306 14     14 0 26 my (undef, $unimport) = @_;
307            
308             return sub{
309 37     37   3534 my $self = shift;
310 37   33     161 my $tagger_class = ref($self)||$self;
311 37   33     133 my $tagged_class =
312             $Class::Tag::caller||caller;
313 37         39 $Class::Tag::caller = undef;
314            
315 37         38 my $tags;
316             ref $_[0] eq 'HASH'
317             ? ( $tags = $_[0] )
318 37 100       177 : ( @$tags{ @_ } = (1) x @_ );
319            
320 37 100       88 %$tags or $tags->{is} = 1;
321            
322 37         119 foreach my $tag (keys %$tags) {
323 49         79 my $tagged_accessor
324             = _tagged_accessor($tagger_class, $tag);
325 49         74 my $tag_value = $tags->{$tag};
326            
327             # bless()ings below are just for labeling (safe enough as nobody would check ref *GLOB{CODE} eq 'CODE', which becomes false unexpectedly)...
328            
329 49         153 my $tagger_accessor = join '::', $tagger_class, $tag;
330 49         81 my $tagged_accessor2 = join '::', $tagged_class, $tagged_accessor;
331 49 100       67 if ($unimport) {
332             croak("Error: tag accessor collision - alien $tag() in tagger class $tagger_class")
333             if *$tagger_accessor{CODE}
334 8 50 33     38 and ref *$tagger_accessor{CODE} ne $tagger_class; # means we may have been using alien thing as accessor
335            
336 8 50       29 undef *$tagger_accessor
337             and $tagged_class
338             eq $tagger_class;
339            
340 8         29 undef *$tagged_accessor2; # has rare name, so safe to unconditionally undef entire glob
341             }
342             else {
343             *$tagged_accessor2 = sub{
344             @_ > 1
345             ? ( _ref_type($_[0]) eq 'HASH'
346             ? $_[0]->{$tagger_accessor} : $tag_value ) = $_[1]
347             : ( _ref_type($_[0]) eq 'HASH'
348             ? exists $_[0]->{$tagger_accessor}
349 173 100   173   433 ? $_[0]->{$tagger_accessor} : $tag_value
    100          
    100          
    100          
350             : $tag_value )
351 41         346 };
352            
353 41 100       77 if ( $tagged_class
354             eq $tagger_class) {
355             *$tagger_accessor{CODE} and ref
356 7 50 33     32 *$tagger_accessor{CODE} ne $tagger_class and croak("Error: tag accessor collision - tagger class $tagger_class already defines or stubs $tag()");
357             *$tagger_accessor{CODE} or
358             *$tagger_accessor = bless sub{
359            
360 214     214   5559 my $sub_accessor;
361 214 100 100     999 unless (@_ == 2 and $_[0] eq $_[1]) {
362 107 100       268 local $Class::Tag::AUTOLOAD
363             = 'AUTOLOAD'
364             if $tag eq 'AUTOLOAD';
365 107         261 $sub_accessor = $tagger_class->$tag($tagger_class);
366             }
367            
368 214 100 66     1061 unshift @_, undef # if called as function
      66        
369             unless @_ > 1
370             and ref($_[0])||$_[0] eq $tagger_class;
371            
372 214 100       411 goto &$sub_accessor
373             if ref $sub_accessor eq 'CODE';
374            
375 202         228 my $tagged_accessor
376             = $tagged_accessor;
377 202 100       372 if ($tag eq 'AUTOLOAD') {
378 188         769 (my $AUTOLOAD = $Class::Tag::AUTOLOAD) =~ s/^.*:://;
379 188         303 $tagged_accessor =
380             _tagged_accessor($tagger_class, $AUTOLOAD);
381             }
382            
383             return defined $_[0] # called as method
384 198 100       174 ? &{ shift; $_[0]->can($tagged_accessor) or return undef }
  198         1302  
385 202 100 33     370 : &{*{join '::', ref($_[1])||$_[1], $tagged_accessor}{CODE} or return undef };
  4 100       7  
  4         43  
386             }
387 7 50       54 , $tagger_class;
388             }
389             else {
390 34 100 100     787 $tagger_class->isa( ref
391             $tagger_class->can($tag) ) or
392             $tagger_class->isa( ref
393             $tagger_class->can('AUTOLOAD') )
394             or confess("Error: tagger class $tagger_class declares no '$tag' tag: ", $tagged_class);
395             }
396             }
397            
398 48 100       5101 if ($tag eq 'tagger_class') {
399            
400 6         7 my $new_tagger_class = $tagged_class;
401 6   100     37 $INC{ join '/', split '::', "$new_tagger_class.pm" } ||= 1; # support inlined tag classes
402 6         12 my $new_import = join '::', $new_tagger_class, 'import';
403 6         11 my $new_import2 = join '::', $new_tagger_class, 'tag';
404 6         16 my $sub_import = *$new_import{CODE};
405 6         14 my $sub_import2 = *$new_import2{CODE};
406 6         10 my $new_unimport = join '::', $new_tagger_class, 'unimport';
407 6         11 my $new_unimport2 = join '::', $new_tagger_class, 'untag';
408 6         19 my $sub_unimport = *$new_unimport{CODE};
409 6         14 my $sub_unimport2 = *$new_unimport2{CODE};
410            
411 6 50       10 if ($unimport) {
412             }
413             else {
414             my $sub_new_import = sub{
415 0     0   0 my ($sub_import, $sub_wasimport) = @_;
416            
417             return #bless
418             ! $sub_wasimport
419             ? $sub_import
420             : sub{
421            
422             #goto &$sub_import;
423            
424 0         0 local $Class::Tag::caller = caller; # let &$sub_import know original caller...
425             # &$sub_import;
426 0         0 &$sub_import(@_);
427 0 0       0 goto &$sub_wasimport
428             if $sub_wasimport;
429 0 0       0 };
430             #, $tagger_class;
431 6         27 };
432            
433 6         15 *$new_import =
434             *$new_import2
435             = __PACKAGE__->new_import();
436            
437 6         15 *$new_unimport =
438             *$new_unimport2
439             = __PACKAGE__->new_import('unimport');
440             }
441             }
442             }
443             }
444 14         340 }
445            
446             sub _ref_type {
447 173 100   173   821 return undef if !ref $_[0];
448 26 50       356 return $1 if $_[0] =~ /=(\w+)/;
449 0           return ref $_[0]
450             }
451            
452             1;
453