Commit | Line | Data |
94b19069 |
1 | |
2 | package Class::MOP; |
3 | |
4 | use strict; |
5 | use warnings; |
6 | |
727919c5 |
7 | use Carp 'confess'; |
aa448b16 |
8 | use Scalar::Util (); |
8b978dd5 |
9 | |
2eb717d5 |
10 | use Class::MOP::Class; |
11 | use Class::MOP::Attribute; |
12 | use Class::MOP::Method; |
13 | |
857f87a7 |
14 | use Class::MOP::Class::Immutable; |
15 | |
148b4697 |
16 | our $VERSION = '0.33'; |
f0480c45 |
17 | our $AUTHORITY = 'cpan:STEVAN'; |
94b19069 |
18 | |
aa448b16 |
19 | ## ---------------------------------------------------------------------------- |
20 | ## Setting up our environment ... |
21 | ## ---------------------------------------------------------------------------- |
22 | ## Class::MOP needs to have a few things in the global perl environment so |
23 | ## that it can operate effectively. Those things are done here. |
24 | ## ---------------------------------------------------------------------------- |
25 | |
3bf7644b |
26 | # ... nothing yet actually ;) |
8b978dd5 |
27 | |
b51af7f9 |
28 | ## ---------------------------------------------------------------------------- |
29 | ## Bootstrapping |
30 | ## ---------------------------------------------------------------------------- |
31 | ## The code below here is to bootstrap our MOP with itself. This is also |
32 | ## sometimes called "tying the knot". By doing this, we make it much easier |
33 | ## to extend the MOP through subclassing and such since now you can use the |
34 | ## MOP itself to extend itself. |
35 | ## |
36 | ## Yes, I know, thats weird and insane, but it's a good thing, trust me :) |
37 | ## ---------------------------------------------------------------------------- |
727919c5 |
38 | |
39 | # We need to add in the meta-attributes here so that |
40 | # any subclass of Class::MOP::* will be able to |
41 | # inherit them using &construct_instance |
42 | |
f0480c45 |
43 | ## -------------------------------------------------------- |
6d5355c3 |
44 | ## Class::MOP::Package |
727919c5 |
45 | |
6d5355c3 |
46 | Class::MOP::Package->meta->add_attribute( |
351bd7d4 |
47 | Class::MOP::Attribute->new('$:package' => ( |
b880e0de |
48 | reader => { |
49 | # NOTE: we need to do this in order |
50 | # for the instance meta-object to |
51 | # not fall into meta-circular death |
52 | 'name' => sub { (shift)->{'$:package'} } |
53 | }, |
7b31baf4 |
54 | init_arg => ':package', |
727919c5 |
55 | )) |
56 | ); |
57 | |
a5e51f0b |
58 | Class::MOP::Package->meta->add_attribute( |
59 | Class::MOP::Attribute->new('%:namespace' => ( |
60 | reader => { |
61 | 'namespace' => sub { (shift)->{'%:namespace'} } |
62 | }, |
63 | default => sub { |
64 | my ($class) = @_; |
65 | no strict 'refs'; |
66 | return \%{$class->name . '::'}; |
67 | }, |
68 | # NOTE: |
69 | # protect this from silliness |
a2ee6c61 |
70 | init_arg => '!............( DO NOT DO THIS )............!', |
a5e51f0b |
71 | )) |
72 | ); |
73 | |
9d6dce77 |
74 | # NOTE: |
75 | # use the metaclass to construct the meta-package |
76 | # which is a superclass of the metaclass itself :P |
77 | Class::MOP::Package->meta->add_method('initialize' => sub { |
78 | my $class = shift; |
79 | my $package_name = shift; |
80 | $class->meta->new_object(':package' => $package_name, @_); |
81 | }); |
82 | |
f0480c45 |
83 | ## -------------------------------------------------------- |
84 | ## Class::MOP::Module |
85 | |
86 | # NOTE: |
87 | # yeah this is kind of stretching things a bit, |
88 | # but truthfully the version should be an attribute |
89 | # of the Module, the weirdness comes from having to |
90 | # stick to Perl 5 convention and store it in the |
91 | # $VERSION package variable. Basically if you just |
92 | # squint at it, it will look how you want it to look. |
93 | # Either as a package variable, or as a attribute of |
94 | # the metaclass, isn't abstraction great :) |
95 | |
96 | Class::MOP::Module->meta->add_attribute( |
97 | Class::MOP::Attribute->new('$:version' => ( |
98 | reader => { |
99 | 'version' => sub { |
100 | my $self = shift; |
101 | ${$self->get_package_symbol('$VERSION')}; |
102 | } |
103 | }, |
104 | # NOTE: |
105 | # protect this from silliness |
106 | init_arg => '!............( DO NOT DO THIS )............!', |
107 | )) |
108 | ); |
109 | |
110 | # NOTE: |
111 | # By following the same conventions as version here, |
112 | # we are opening up the possibility that people can |
113 | # use the $AUTHORITY in non-Class::MOP modules as |
114 | # well. |
115 | |
116 | Class::MOP::Module->meta->add_attribute( |
117 | Class::MOP::Attribute->new('$:authority' => ( |
118 | reader => { |
119 | 'authority' => sub { |
120 | my $self = shift; |
121 | ${$self->get_package_symbol('$AUTHORITY')}; |
122 | } |
123 | }, |
124 | # NOTE: |
125 | # protect this from silliness |
126 | init_arg => '!............( DO NOT DO THIS )............!', |
127 | )) |
128 | ); |
129 | |
130 | ## -------------------------------------------------------- |
6d5355c3 |
131 | ## Class::MOP::Class |
132 | |
727919c5 |
133 | Class::MOP::Class->meta->add_attribute( |
351bd7d4 |
134 | Class::MOP::Attribute->new('%:attributes' => ( |
f7259199 |
135 | reader => { |
136 | # NOTE: we need to do this in order |
137 | # for the instance meta-object to |
138 | # not fall into meta-circular death |
139 | 'get_attribute_map' => sub { (shift)->{'%:attributes'} } |
140 | }, |
351bd7d4 |
141 | init_arg => ':attributes', |
727919c5 |
142 | default => sub { {} } |
143 | )) |
144 | ); |
145 | |
351bd7d4 |
146 | Class::MOP::Class->meta->add_attribute( |
147 | Class::MOP::Attribute->new('$:attribute_metaclass' => ( |
7b31baf4 |
148 | reader => 'attribute_metaclass', |
351bd7d4 |
149 | init_arg => ':attribute_metaclass', |
150 | default => 'Class::MOP::Attribute', |
151 | )) |
152 | ); |
153 | |
154 | Class::MOP::Class->meta->add_attribute( |
155 | Class::MOP::Attribute->new('$:method_metaclass' => ( |
7b31baf4 |
156 | reader => 'method_metaclass', |
351bd7d4 |
157 | init_arg => ':method_metaclass', |
158 | default => 'Class::MOP::Method', |
159 | )) |
160 | ); |
161 | |
2bab2be6 |
162 | Class::MOP::Class->meta->add_attribute( |
163 | Class::MOP::Attribute->new('$:instance_metaclass' => ( |
b880e0de |
164 | reader => { |
165 | # NOTE: we need to do this in order |
166 | # for the instance meta-object to |
167 | # not fall into meta-circular death |
168 | 'instance_metaclass' => sub { (shift)->{'$:instance_metaclass'} } |
169 | }, |
2bab2be6 |
170 | init_arg => ':instance_metaclass', |
171 | default => 'Class::MOP::Instance', |
172 | )) |
173 | ); |
174 | |
9d6dce77 |
175 | # NOTE: |
176 | # we don't actually need to tie the knot with |
177 | # Class::MOP::Class here, it is actually handled |
178 | # within Class::MOP::Class itself in the |
179 | # construct_class_instance method. |
180 | |
f0480c45 |
181 | ## -------------------------------------------------------- |
727919c5 |
182 | ## Class::MOP::Attribute |
183 | |
7b31baf4 |
184 | Class::MOP::Attribute->meta->add_attribute( |
185 | Class::MOP::Attribute->new('name' => ( |
b880e0de |
186 | reader => { |
187 | # NOTE: we need to do this in order |
188 | # for the instance meta-object to |
189 | # not fall into meta-circular death |
190 | 'name' => sub { (shift)->{name} } |
191 | } |
7b31baf4 |
192 | )) |
193 | ); |
194 | |
195 | Class::MOP::Attribute->meta->add_attribute( |
196 | Class::MOP::Attribute->new('associated_class' => ( |
b880e0de |
197 | reader => { |
198 | # NOTE: we need to do this in order |
199 | # for the instance meta-object to |
200 | # not fall into meta-circular death |
201 | 'associated_class' => sub { (shift)->{associated_class} } |
202 | } |
7b31baf4 |
203 | )) |
204 | ); |
205 | |
206 | Class::MOP::Attribute->meta->add_attribute( |
207 | Class::MOP::Attribute->new('accessor' => ( |
208 | reader => 'accessor', |
209 | predicate => 'has_accessor', |
210 | )) |
211 | ); |
212 | |
213 | Class::MOP::Attribute->meta->add_attribute( |
214 | Class::MOP::Attribute->new('reader' => ( |
215 | reader => 'reader', |
216 | predicate => 'has_reader', |
217 | )) |
218 | ); |
219 | |
220 | Class::MOP::Attribute->meta->add_attribute( |
221 | Class::MOP::Attribute->new('writer' => ( |
222 | reader => 'writer', |
223 | predicate => 'has_writer', |
224 | )) |
225 | ); |
226 | |
227 | Class::MOP::Attribute->meta->add_attribute( |
228 | Class::MOP::Attribute->new('predicate' => ( |
229 | reader => 'predicate', |
230 | predicate => 'has_predicate', |
231 | )) |
232 | ); |
233 | |
234 | Class::MOP::Attribute->meta->add_attribute( |
7d28758b |
235 | Class::MOP::Attribute->new('clearer' => ( |
236 | reader => 'clearer', |
237 | predicate => 'has_clearer', |
238 | )) |
239 | ); |
240 | |
241 | Class::MOP::Attribute->meta->add_attribute( |
7b31baf4 |
242 | Class::MOP::Attribute->new('init_arg' => ( |
243 | reader => 'init_arg', |
244 | predicate => 'has_init_arg', |
245 | )) |
246 | ); |
247 | |
248 | Class::MOP::Attribute->meta->add_attribute( |
249 | Class::MOP::Attribute->new('default' => ( |
250 | # default has a custom 'reader' method ... |
251 | predicate => 'has_default', |
252 | )) |
253 | ); |
254 | |
727919c5 |
255 | |
256 | # NOTE: (meta-circularity) |
257 | # This should be one of the last things done |
258 | # it will "tie the knot" with Class::MOP::Attribute |
259 | # so that it uses the attributes meta-objects |
260 | # to construct itself. |
261 | Class::MOP::Attribute->meta->add_method('new' => sub { |
262 | my $class = shift; |
263 | my $name = shift; |
264 | my %options = @_; |
265 | |
266 | (defined $name && $name) |
267 | || confess "You must provide a name for the attribute"; |
5659d76e |
268 | $options{init_arg} = $name |
269 | if not exists $options{init_arg}; |
148b4697 |
270 | |
271 | (Class::MOP::Attribute::is_default_a_coderef(\%options)) |
272 | || confess("References are not allowed as default values, you must ". |
273 | "wrap then in a CODE reference (ex: sub { [] } and not [])") |
274 | if exists $options{default} && ref $options{default}; |
651955fb |
275 | |
5659d76e |
276 | # return the new object |
277 | $class->meta->new_object(name => $name, %options); |
278 | }); |
279 | |
280 | Class::MOP::Attribute->meta->add_method('clone' => sub { |
a740253a |
281 | my $self = shift; |
a27ae83f |
282 | $self->meta->clone_object($self, @_); |
727919c5 |
283 | }); |
284 | |
f0480c45 |
285 | ## -------------------------------------------------------- |
286 | ## Now close all the Class::MOP::* classes |
4d47b77f |
287 | |
288 | Class::MOP::Package ->meta->make_immutable(inline_constructor => 0); |
289 | Class::MOP::Module ->meta->make_immutable(inline_constructor => 0); |
290 | Class::MOP::Class ->meta->make_immutable(inline_constructor => 0); |
291 | Class::MOP::Attribute->meta->make_immutable(inline_constructor => 0); |
292 | Class::MOP::Method ->meta->make_immutable(inline_constructor => 0); |
293 | Class::MOP::Instance ->meta->make_immutable(inline_constructor => 0); |
6e57504d |
294 | Class::MOP::Object ->meta->make_immutable(inline_constructor => 0); |
4d47b77f |
295 | |
94b19069 |
296 | 1; |
297 | |
298 | __END__ |
299 | |
300 | =pod |
301 | |
302 | =head1 NAME |
303 | |
304 | Class::MOP - A Meta Object Protocol for Perl 5 |
305 | |
306 | =head1 SYNOPSIS |
307 | |
a2e85e6c |
308 | # ... This will come later, for now see |
309 | # the other SYNOPSIS for more information |
94b19069 |
310 | |
311 | =head1 DESCRIPTON |
312 | |
313 | This module is an attempt to create a meta object protocol for the |
314 | Perl 5 object system. It makes no attempt to change the behavior or |
315 | characteristics of the Perl 5 object system, only to create a |
27e31eaf |
316 | protocol for its manipulation and introspection. |
94b19069 |
317 | |
318 | That said, it does attempt to create the tools for building a rich |
319 | set of extensions to the Perl 5 object system. Every attempt has been |
320 | made for these tools to keep to the spirit of the Perl 5 object |
321 | system that we all know and love. |
322 | |
40483095 |
323 | This documentation is admittedly sparse on details, as time permits |
324 | I will try to improve them. For now, I suggest looking at the items |
325 | listed in the L<SEE ALSO> section for more information. In particular |
326 | the book "The Art of the Meta Object Protocol" was very influential |
327 | in the development of this system. |
328 | |
bfe4d0fc |
329 | =head2 What is a Meta Object Protocol? |
330 | |
331 | A meta object protocol is an API to an object system. |
332 | |
333 | To be more specific, it is a set of abstractions of the components of |
334 | an object system (typically things like; classes, object, methods, |
335 | object attributes, etc.). These abstractions can then be used to both |
336 | inspect and manipulate the object system which they describe. |
337 | |
338 | It can be said that there are two MOPs for any object system; the |
339 | implicit MOP, and the explicit MOP. The implicit MOP handles things |
340 | like method dispatch or inheritance, which happen automatically as |
341 | part of how the object system works. The explicit MOP typically |
342 | handles the introspection/reflection features of the object system. |
343 | All object systems have implicit MOPs, without one, they would not |
344 | work. Explict MOPs however as less common, and depending on the |
345 | language can vary from restrictive (Reflection in Java or C#) to |
346 | wide open (CLOS is a perfect example). |
347 | |
e16da3e6 |
348 | =head2 Yet Another Class Builder!! Why? |
349 | |
350 | This is B<not> a class builder so much as it is a I<class builder |
351 | B<builder>>. My intent is that an end user does not use this module |
352 | directly, but instead this module is used by module authors to |
353 | build extensions and features onto the Perl 5 object system. |
354 | |
94b19069 |
355 | =head2 Who is this module for? |
356 | |
357 | This module is specifically for anyone who has ever created or |
358 | wanted to create a module for the Class:: namespace. The tools which |
359 | this module will provide will hopefully make it easier to do more |
360 | complex things with Perl 5 classes by removing such barriers as |
361 | the need to hack the symbol tables, or understand the fine details |
362 | of method dispatch. |
363 | |
bfe4d0fc |
364 | =head2 What changes do I have to make to use this module? |
365 | |
2eb717d5 |
366 | This module was designed to be as unintrusive as possible. Many of |
343203ee |
367 | its features are accessible without B<any> change to your existsing |
bfe4d0fc |
368 | code at all. It is meant to be a compliment to your existing code and |
2eb717d5 |
369 | not an intrusion on your code base. Unlike many other B<Class::> |
a2e85e6c |
370 | modules, this module B<does not> require you subclass it, or even that |
371 | you C<use> it in within your module's package. |
bfe4d0fc |
372 | |
2eb717d5 |
373 | The only features which requires additions to your code are the |
374 | attribute handling and instance construction features, and these are |
a2e85e6c |
375 | both completely optional features. The only reason for this is because |
2eb717d5 |
376 | Perl 5's object system does not actually have these features built |
377 | in. More information about this feature can be found below. |
bfe4d0fc |
378 | |
379 | =head2 A Note about Performance? |
380 | |
381 | It is a common misconception that explict MOPs are performance drains. |
382 | But this is not a universal truth at all, it is an side-effect of |
383 | specific implementations. For instance, using Java reflection is much |
384 | slower because the JVM cannot take advantage of any compiler |
385 | optimizations, and the JVM has to deal with much more runtime type |
386 | information as well. Reflection in C# is marginally better as it was |
387 | designed into the language and runtime (the CLR). In contrast, CLOS |
388 | (the Common Lisp Object System) was built to support an explicit MOP, |
389 | and so performance is tuned for it. |
390 | |
391 | This library in particular does it's absolute best to avoid putting |
2eb717d5 |
392 | B<any> drain at all upon your code's performance. In fact, by itself |
393 | it does nothing to affect your existing code. So you only pay for |
394 | what you actually use. |
bfe4d0fc |
395 | |
550d56db |
396 | =head2 About Metaclass compatibility |
397 | |
398 | This module makes sure that all metaclasses created are both upwards |
399 | and downwards compatible. The topic of metaclass compatibility is |
400 | highly esoteric and is something only encountered when doing deep and |
401 | involved metaclass hacking. There are two basic kinds of metaclass |
402 | incompatibility; upwards and downwards. |
403 | |
404 | Upwards metaclass compatibility means that the metaclass of a |
405 | given class is either the same as (or a subclass of) all of the |
406 | class's ancestors. |
407 | |
408 | Downward metaclass compatibility means that the metaclasses of a |
409 | given class's anscestors are all either the same as (or a subclass |
410 | of) that metaclass. |
411 | |
412 | Here is a diagram showing a set of two classes (C<A> and C<B>) and |
413 | two metaclasses (C<Meta::A> and C<Meta::B>) which have correct |
414 | metaclass compatibility both upwards and downwards. |
415 | |
416 | +---------+ +---------+ |
417 | | Meta::A |<----| Meta::B | <....... (instance of ) |
418 | +---------+ +---------+ <------- (inherits from) |
419 | ^ ^ |
420 | : : |
421 | +---------+ +---------+ |
422 | | A |<----| B | |
423 | +---------+ +---------+ |
424 | |
425 | As I said this is a highly esoteric topic and one you will only run |
426 | into if you do a lot of subclassing of B<Class::MOP::Class>. If you |
427 | are interested in why this is an issue see the paper |
428 | I<Uniform and safe metaclass composition> linked to in the |
429 | L<SEE ALSO> section of this document. |
430 | |
aa448b16 |
431 | =head2 Using custom metaclasses |
432 | |
433 | Always use the metaclass pragma when using a custom metaclass, this |
434 | will ensure the proper initialization order and not accidentely |
435 | create an incorrect type of metaclass for you. This is a very rare |
436 | problem, and one which can only occur if you are doing deep metaclass |
437 | programming. So in other words, don't worry about it. |
438 | |
94b19069 |
439 | =head1 PROTOCOLS |
440 | |
441 | The protocol is divided into 3 main sub-protocols: |
442 | |
443 | =over 4 |
444 | |
445 | =item The Class protocol |
446 | |
447 | This provides a means of manipulating and introspecting a Perl 5 |
448 | class. It handles all of symbol table hacking for you, and provides |
449 | a rich set of methods that go beyond simple package introspection. |
450 | |
552e3d24 |
451 | See L<Class::MOP::Class> for more details. |
452 | |
94b19069 |
453 | =item The Attribute protocol |
454 | |
455 | This provides a consistent represenation for an attribute of a |
456 | Perl 5 class. Since there are so many ways to create and handle |
457 | atttributes in Perl 5 OO, this attempts to provide as much of a |
458 | unified approach as possible, while giving the freedom and |
459 | flexibility to subclass for specialization. |
460 | |
552e3d24 |
461 | See L<Class::MOP::Attribute> for more details. |
462 | |
94b19069 |
463 | =item The Method protocol |
464 | |
465 | This provides a means of manipulating and introspecting methods in |
466 | the Perl 5 object system. As with attributes, there are many ways to |
467 | approach this topic, so we try to keep it pretty basic, while still |
468 | making it possible to extend the system in many ways. |
469 | |
552e3d24 |
470 | See L<Class::MOP::Method> for more details. |
94b19069 |
471 | |
472 | =back |
473 | |
552e3d24 |
474 | =head1 SEE ALSO |
8b978dd5 |
475 | |
552e3d24 |
476 | =head2 Books |
8b978dd5 |
477 | |
a2e85e6c |
478 | There are very few books out on Meta Object Protocols and Metaclasses |
479 | because it is such an esoteric topic. The following books are really |
480 | the only ones I have found. If you know of any more, B<I<please>> |
481 | email me and let me know, I would love to hear about them. |
482 | |
8b978dd5 |
483 | =over 4 |
484 | |
552e3d24 |
485 | =item "The Art of the Meta Object Protocol" |
8b978dd5 |
486 | |
552e3d24 |
487 | =item "Advances in Object-Oriented Metalevel Architecture and Reflection" |
8b978dd5 |
488 | |
b51af7f9 |
489 | =item "Putting MetaClasses to Work" |
490 | |
a2e85e6c |
491 | =item "Smalltalk: The Language" |
492 | |
94b19069 |
493 | =back |
494 | |
550d56db |
495 | =head2 Papers |
496 | |
497 | =over 4 |
498 | |
499 | =item Uniform and safe metaclass composition |
500 | |
501 | An excellent paper by the people who brought us the original Traits paper. |
502 | This paper is on how Traits can be used to do safe metaclass composition, |
503 | and offers an excellent introduction section which delves into the topic of |
504 | metaclass compatibility. |
505 | |
506 | L<http://www.iam.unibe.ch/~scg/Archive/Papers/Duca05ySafeMetaclassTrait.pdf> |
507 | |
508 | =item Safe Metaclass Programming |
509 | |
510 | This paper seems to precede the above paper, and propose a mix-in based |
511 | approach as opposed to the Traits based approach. Both papers have similar |
512 | information on the metaclass compatibility problem space. |
513 | |
514 | L<http://citeseer.ist.psu.edu/37617.html> |
515 | |
516 | =back |
517 | |
552e3d24 |
518 | =head2 Prior Art |
8b978dd5 |
519 | |
520 | =over 4 |
521 | |
7184ca14 |
522 | =item The Perl 6 MetaModel work in the Pugs project |
8b978dd5 |
523 | |
524 | =over 4 |
525 | |
552e3d24 |
526 | =item L<http://svn.openfoundry.org/pugs/perl5/Perl6-MetaModel> |
8b978dd5 |
527 | |
552e3d24 |
528 | =item L<http://svn.openfoundry.org/pugs/perl5/Perl6-ObjectSpace> |
8b978dd5 |
529 | |
530 | =back |
531 | |
94b19069 |
532 | =back |
533 | |
a2e85e6c |
534 | =head1 SIMILAR MODULES |
535 | |
536 | As I have said above, this module is a class-builder-builder, so it is |
537 | not the same thing as modules like L<Class::Accessor> and |
538 | L<Class::MethodMaker>. That being said there are very few modules on CPAN |
539 | with similar goals to this module. The one I have found which is most |
550d56db |
540 | like this module is L<Class::Meta>, although it's philosophy and the MOP it |
541 | creates are very different from this modules. |
94b19069 |
542 | |
a2e85e6c |
543 | =head1 BUGS |
544 | |
545 | All complex software has bugs lurking in it, and this module is no |
546 | exception. If you find a bug please either email me, or add the bug |
547 | to cpan-RT. |
548 | |
22286063 |
549 | =head1 CODE COVERAGE |
550 | |
551 | I use L<Devel::Cover> to test the code coverage of my tests, below is the |
552 | L<Devel::Cover> report on this module's test suite. |
553 | |
554 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
555 | File stmt bran cond sub pod time total |
556 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
cdfaa4cc |
557 | Class/MOP.pm 100.0 100.0 100.0 100.0 n/a 19.8 100.0 |
558 | Class/MOP/Attribute.pm 100.0 100.0 91.7 61.2 100.0 14.3 87.9 |
559 | Class/MOP/Class.pm 97.6 91.3 77.3 98.4 100.0 56.4 93.2 |
560 | Class/MOP/Instance.pm 91.1 75.0 33.3 91.7 100.0 6.8 90.7 |
561 | Class/MOP/Method.pm 97.6 60.0 52.9 76.9 100.0 1.6 82.6 |
562 | metaclass.pm 100.0 100.0 83.3 100.0 n/a 1.0 97.7 |
22286063 |
563 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
cdfaa4cc |
564 | Total 97.5 88.5 75.5 82.8 100.0 100.0 91.2 |
22286063 |
565 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
566 | |
a2e85e6c |
567 | =head1 ACKNOWLEDGEMENTS |
568 | |
569 | =over 4 |
570 | |
571 | =item Rob Kinyon E<lt>rob@iinteractive.comE<gt> |
572 | |
573 | Thanks to Rob for actually getting the development of this module kick-started. |
574 | |
575 | =back |
576 | |
1a09d9cc |
577 | =head1 AUTHORS |
94b19069 |
578 | |
a2e85e6c |
579 | Stevan Little E<lt>stevan@iinteractive.comE<gt> |
552e3d24 |
580 | |
1a09d9cc |
581 | Yuval Kogman E<lt>nothingmuch@woobling.comE<gt> |
582 | |
94b19069 |
583 | =head1 COPYRIGHT AND LICENSE |
584 | |
585 | Copyright 2006 by Infinity Interactive, Inc. |
586 | |
587 | L<http://www.iinteractive.com> |
588 | |
589 | This library is free software; you can redistribute it and/or modify |
590 | it under the same terms as Perl itself. |
591 | |
592 | =cut |