lazy attr bug found by ashley
[gitmo/Moose.git] / lib / Moose / Meta / Attribute.pm
CommitLineData
c0e30cf5 1
2package Moose::Meta::Attribute;
3
4use strict;
5use warnings;
6
78cd1d3b 7use Scalar::Util 'blessed', 'weaken', 'reftype';
a15dff8d 8use Carp 'confess';
9
4fd69d6c 10our $VERSION = '0.08';
78cd1d3b 11
a3c7e2fe 12use Moose::Util::TypeConstraints ();
bc1e29b5 13
c0e30cf5 14use base 'Class::MOP::Attribute';
15
452bac1b 16# options which are not directly used
17# but we store them for metadata purposes
98aae381 18__PACKAGE__->meta->add_attribute('isa' => (reader => '_isa_metadata'));
19__PACKAGE__->meta->add_attribute('does' => (reader => '_does_metadata'));
20__PACKAGE__->meta->add_attribute('is' => (reader => '_is_metadata'));
452bac1b 21
22# these are actual options for the attrs
1a563243 23__PACKAGE__->meta->add_attribute('required' => (reader => 'is_required' ));
24__PACKAGE__->meta->add_attribute('lazy' => (reader => 'is_lazy' ));
25__PACKAGE__->meta->add_attribute('coerce' => (reader => 'should_coerce' ));
26__PACKAGE__->meta->add_attribute('weak_ref' => (reader => 'is_weak_ref' ));
27__PACKAGE__->meta->add_attribute('auto_deref' => (reader => 'should_auto_deref'));
82168dbb 28__PACKAGE__->meta->add_attribute('type_constraint' => (
29 reader => 'type_constraint',
30 predicate => 'has_type_constraint',
31));
8c9d74e7 32__PACKAGE__->meta->add_attribute('trigger' => (
33 reader => 'trigger',
34 predicate => 'has_trigger',
35));
452bac1b 36__PACKAGE__->meta->add_attribute('handles' => (
37 reader => 'handles',
38 predicate => 'has_handles',
39));
82168dbb 40
78cd1d3b 41sub new {
42 my ($class, $name, %options) = @_;
1d768fb1 43 $class->_process_options($name, \%options);
98aae381 44 return $class->SUPER::new($name, %options);
1d768fb1 45}
46
ce0e8d63 47sub clone_and_inherit_options {
48 my ($self, %options) = @_;
49 # you can change default, required and coerce
50 my %actual_options;
51 foreach my $legal_option (qw(default coerce required)) {
52 if (exists $options{$legal_option}) {
53 $actual_options{$legal_option} = $options{$legal_option};
54 delete $options{$legal_option};
55 }
56 }
fcb7afc2 57 # isa can be changed, but only if the
58 # new type is a subtype
ce0e8d63 59 if ($options{isa}) {
60 my $type_constraint;
61 if (blessed($options{isa}) && $options{isa}->isa('Moose::Meta::TypeConstraint')) {
62 $type_constraint = $options{isa};
63 }
64 else {
65 $type_constraint = Moose::Util::TypeConstraints::find_type_constraint($options{isa});
66 (defined $type_constraint)
67 || confess "Could not find the type constraint '" . $options{isa} . "'";
68 }
2a0f3bd3 69 # NOTE:
70 # check here to see if the new type
71 # is a subtype of the old one
ce0e8d63 72 ($type_constraint->is_subtype_of($self->type_constraint->name))
73 || confess "New type constraint setting must be a subtype of inherited one"
2a0f3bd3 74 # iff we have a type constraint that is ...
ce0e8d63 75 if $self->has_type_constraint;
2a0f3bd3 76 # then we use it :)
ce0e8d63 77 $actual_options{type_constraint} = $type_constraint;
78 delete $options{isa};
79 }
80 (scalar keys %options == 0)
81 || confess "Illegal inherited options => (" . (join ', ' => keys %options) . ")";
82 $self->clone(%actual_options);
1d768fb1 83}
84
85sub _process_options {
86 my ($class, $name, $options) = @_;
452bac1b 87
1d768fb1 88 if (exists $options->{is}) {
89 if ($options->{is} eq 'ro') {
90 $options->{reader} = $name;
91 (!exists $options->{trigger})
8c9d74e7 92 || confess "Cannot have a trigger on a read-only attribute";
78cd1d3b 93 }
1d768fb1 94 elsif ($options->{is} eq 'rw') {
452bac1b 95 $options->{accessor} = $name;
98aae381 96 ((reftype($options->{trigger}) || '') eq 'CODE')
97 || confess "Trigger must be a CODE ref"
98 if exists $options->{trigger};
452bac1b 99 }
100 else {
101 confess "I do not understand this option (is => " . $options->{is} . ")"
78cd1d3b 102 }
103 }
104
1d768fb1 105 if (exists $options->{isa}) {
02a0fb52 106
1d768fb1 107 if (exists $options->{does}) {
108 if (eval { $options->{isa}->can('does') }) {
109 ($options->{isa}->does($options->{does}))
02a0fb52 110 || confess "Cannot have an isa option and a does option if the isa does not do the does";
111 }
7eaef7ad 112 else {
113 confess "Cannot have an isa option which cannot ->does()";
114 }
02a0fb52 115 }
116
78cd1d3b 117 # allow for anon-subtypes here ...
1d768fb1 118 if (blessed($options->{isa}) && $options->{isa}->isa('Moose::Meta::TypeConstraint')) {
119 $options->{type_constraint} = $options->{isa};
78cd1d3b 120 }
121 else {
c07af9d2 122
1d768fb1 123 if ($options->{isa} =~ /\|/) {
124 my @type_constraints = split /\s*\|\s*/ => $options->{isa};
125 $options->{type_constraint} = Moose::Util::TypeConstraints::create_type_constraint_union(
c07af9d2 126 @type_constraints
78cd1d3b 127 );
c07af9d2 128 }
129 else {
130 # otherwise assume it is a constraint
1d768fb1 131 my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{isa});
c07af9d2 132 # if the constraing it not found ....
133 unless (defined $constraint) {
134 # assume it is a foreign class, and make
135 # an anon constraint for it
136 $constraint = Moose::Util::TypeConstraints::subtype(
137 'Object',
1d768fb1 138 Moose::Util::TypeConstraints::where { $_->isa($options->{isa}) }
c07af9d2 139 );
140 }
1d768fb1 141 $options->{type_constraint} = $constraint;
c07af9d2 142 }
78cd1d3b 143 }
144 }
1d768fb1 145 elsif (exists $options->{does}) {
02a0fb52 146 # allow for anon-subtypes here ...
1d768fb1 147 if (blessed($options->{does}) && $options->{does}->isa('Moose::Meta::TypeConstraint')) {
148 $options->{type_constraint} = $options->{isa};
02a0fb52 149 }
150 else {
151 # otherwise assume it is a constraint
1d768fb1 152 my $constraint = Moose::Util::TypeConstraints::find_type_constraint($options->{does});
02a0fb52 153 # if the constraing it not found ....
154 unless (defined $constraint) {
155 # assume it is a foreign class, and make
156 # an anon constraint for it
157 $constraint = Moose::Util::TypeConstraints::subtype(
158 'Role',
1d768fb1 159 Moose::Util::TypeConstraints::where { $_->does($options->{does}) }
02a0fb52 160 );
161 }
1d768fb1 162 $options->{type_constraint} = $constraint;
02a0fb52 163 }
164 }
78cd1d3b 165
1d768fb1 166 if (exists $options->{coerce} && $options->{coerce}) {
167 (exists $options->{type_constraint})
4b598ea3 168 || confess "You cannot have coercion without specifying a type constraint";
0a5bd159 169 #(!$options->{type_constraint}->isa('Moose::Meta::TypeConstraint::Union'))
170 # || confess "You cannot have coercion with a type constraint union";
4b598ea3 171 confess "You cannot have a weak reference to a coerced value"
1d768fb1 172 if $options->{weak_ref};
ca01a97b 173 }
78cd1d3b 174
536f0b17 175 if (exists $options->{auto_deref} && $options->{auto_deref}) {
176 (exists $options->{type_constraint})
177 || confess "You cannot auto-dereference without specifying a type constraint";
94b8bbb8 178 ($options->{type_constraint}->is_a_type_of('ArrayRef') ||
179 $options->{type_constraint}->is_a_type_of('HashRef'))
536f0b17 180 || confess "You cannot auto-dereference anything other than a ArrayRef or HashRef";
181 }
182
94b8bbb8 183 if (exists $options->{type_constraint} &&
184 ($options->{type_constraint}->is_a_type_of('ArrayRef') ||
185 $options->{type_constraint}->is_a_type_of('HashRef') )) {
3f7376b0 186 unless (exists $options->{default}) {
187 $options->{default} = sub { [] } if $options->{type_constraint}->name eq 'ArrayRef';
188 $options->{default} = sub { {} } if $options->{type_constraint}->name eq 'HashRef';
189 }
190 }
191
1d768fb1 192 if (exists $options->{lazy} && $options->{lazy}) {
193 (exists $options->{default})
ca01a97b 194 || confess "You cannot have lazy attribute without specifying a default value for it";
1d768fb1 195 }
78cd1d3b 196}
c0e30cf5 197
d500266f 198sub initialize_instance_slot {
ddd0ec20 199 my ($self, $meta_instance, $instance, $params) = @_;
d500266f 200 my $init_arg = $self->init_arg();
201 # try to fetch the init arg from the %params ...
ddd0ec20 202
d500266f 203 my $val;
204 if (exists $params->{$init_arg}) {
205 $val = $params->{$init_arg};
206 }
207 else {
208 # skip it if it's lazy
209 return if $self->is_lazy;
210 # and die if it's required and doesn't have a default value
211 confess "Attribute (" . $self->name . ") is required"
212 if $self->is_required && !$self->has_default;
213 }
ddd0ec20 214
d500266f 215 # if nothing was in the %params, we can use the
216 # attribute's default value (if it has one)
217 if (!defined $val && $self->has_default) {
218 $val = $self->default($instance);
219 }
220 if (defined $val) {
221 if ($self->has_type_constraint) {
c07af9d2 222 my $type_constraint = $self->type_constraint;
223 if ($self->should_coerce && $type_constraint->has_coercion) {
0a5bd159 224 $val = $type_constraint->coerce($val);
d500266f 225 }
c07af9d2 226 (defined($type_constraint->check($val)))
227 || confess "Attribute (" .
228 $self->name .
8449e6e7 229 ") does not pass the type constraint (" .
c07af9d2 230 $type_constraint->name .
231 ") with '$val'";
d500266f 232 }
233 }
ddd0ec20 234
ac1ef2f9 235 $meta_instance->set_slot_value($instance, $self->name, $val);
236 $meta_instance->weaken_slot_value($instance, $self->name)
237 if ref $val && $self->is_weak_ref;
d500266f 238}
239
9e93dd19 240## Accessor inline subroutines
241
67ad26d9 242sub _inline_check_constraint {
ac1ef2f9 243 my ($self, $value) = @_;
67ad26d9 244 return '' unless $self->has_type_constraint;
245
246 # FIXME - remove 'unless defined($value) - constraint Undef
247 return sprintf <<'EOF', $value, $value, $value, $value
248defined($attr->type_constraint->check(%s))
8449e6e7 249 || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("
67ad26d9 250 . $attr->type_constraint->name . ") with " . (defined(%s) ? "'%s'" : "undef")
251 if defined(%s);
252EOF
253}
254
9e93dd19 255sub _inline_check_coercion {
256 my $self = shift;
257 return '' unless $self->should_coerce;
0a5bd159 258 return 'my $val = $attr->type_constraint->coerce($_[1]);'
9e93dd19 259}
260
261sub _inline_check_required {
262 my $self = shift;
263 return '' unless $self->is_required;
264 return 'defined($_[1]) || confess "Attribute ($attr_name) is required, so cannot be set to undef";'
265}
266
267sub _inline_check_lazy {
268 my $self = shift;
269 return '' unless $self->is_lazy;
4fd69d6c 270 if ($self->has_type_constraint) {
271 # NOTE:
272 # this could probably be cleaned
273 # up and streamlined a little more
274 return 'unless (exists $_[0]->{$attr_name}) {' .
275 ' if ($attr->has_default) {' .
276 ' my $default = $attr->default($_[0]);' .
277 ' (defined($attr->type_constraint->check($default)))' .
278 ' || confess "Attribute (" . $attr->name . ") does not pass the type constraint ("' .
279 ' . $attr->type_constraint->name . ") with " . (defined($default) ? "\'$default\'" : "undef")' .
280 ' if defined($default);' .
281 ' $_[0]->{$attr_name} = $default; ' .
282 ' }' .
283 ' else {' .
284 ' $_[0]->{$attr_name} = undef;' .
285 ' }' .
286 '}';
287 }
9e93dd19 288 return '$_[0]->{$attr_name} = ($attr->has_default ? $attr->default($_[0]) : undef)'
289 . 'unless exists $_[0]->{$attr_name};';
290}
291
292
67ad26d9 293sub _inline_store {
ac1ef2f9 294 my ($self, $instance, $value) = @_;
67ad26d9 295
296 my $mi = $self->associated_class->get_meta_instance;
ac1ef2f9 297 my $slot_name = sprintf "'%s'", $self->slots;
67ad26d9 298
ac1ef2f9 299 my $code = $mi->inline_set_slot_value($instance, $slot_name, $value) . ";";
300 $code .= $mi->inline_weaken_slot_value($instance, $slot_name, $value) . ";"
301 if $self->is_weak_ref;
302 return $code;
8a7a9c53 303}
304
67ad26d9 305sub _inline_trigger {
ac1ef2f9 306 my ($self, $instance, $value) = @_;
67ad26d9 307 return '' unless $self->has_trigger;
308 return sprintf('$attr->trigger->(%s, %s, $attr);', $instance, $value);
8a7a9c53 309}
310
ddd0ec20 311sub _inline_get {
ac1ef2f9 312 my ($self, $instance) = @_;
ddd0ec20 313
314 my $mi = $self->associated_class->get_meta_instance;
ac1ef2f9 315 my $slot_name = sprintf "'%s'", $self->slots;
ddd0ec20 316
ac1ef2f9 317 return $mi->inline_get_slot_value($instance, $slot_name);
ddd0ec20 318}
319
1a563243 320sub _inline_auto_deref {
321 my ( $self, $ref_value ) = @_;
322
323 return $ref_value unless $self->should_auto_deref;
324
94b8bbb8 325 my $type_constraint = $self->type_constraint;
1a563243 326
536f0b17 327 my $sigil;
94b8bbb8 328 if ($type_constraint->is_a_type_of('ArrayRef')) {
1a563243 329 $sigil = '@';
536f0b17 330 }
94b8bbb8 331 elsif ($type_constraint->is_a_type_of('HashRef')) {
1a563243 332 $sigil = '%';
536f0b17 333 }
334 else {
94b8bbb8 335 confess "Can not auto de-reference the type constraint '" . $type_constraint->name . "'";
1a563243 336 }
337
338 "(wantarray() ? $sigil\{ ( $ref_value ) || return } : ( $ref_value ) )";
339}
340
a15dff8d 341sub generate_accessor_method {
67ad26d9 342 my ($attr, $attr_name) = @_;
343 my $value_name = $attr->should_coerce ? '$val' : '$_[1]';
344 my $mi = $attr->associated_class->get_meta_instance;
ac1ef2f9 345 my $slot_name = sprintf "'%s'", $attr->slots;
67ad26d9 346 my $inv = '$_[0]';
ca01a97b 347 my $code = 'sub { '
348 . 'if (scalar(@_) == 2) {'
9e93dd19 349 . $attr->_inline_check_required
350 . $attr->_inline_check_coercion
ac1ef2f9 351 . $attr->_inline_check_constraint($value_name)
352 . $attr->_inline_store($inv, $value_name)
353 . $attr->_inline_trigger($inv, $value_name)
ca01a97b 354 . ' }'
9e93dd19 355 . $attr->_inline_check_lazy
536f0b17 356 . 'return ' . $attr->_inline_auto_deref($attr->_inline_get($inv))
ca01a97b 357 . ' }';
358 my $sub = eval $code;
67ad26d9 359 confess "Could not create accessor for '$attr_name' because $@ \n code: $code" if $@;
ca01a97b 360 return $sub;
a15dff8d 361}
362
363sub generate_writer_method {
67ad26d9 364 my ($attr, $attr_name) = @_;
365 my $value_name = $attr->should_coerce ? '$val' : '$_[1]';
366 my $inv = '$_[0]';
ca01a97b 367 my $code = 'sub { '
9e93dd19 368 . $attr->_inline_check_required
369 . $attr->_inline_check_coercion
ac1ef2f9 370 . $attr->_inline_check_constraint($value_name)
371 . $attr->_inline_store($inv, $value_name)
372 . $attr->_inline_trigger($inv, $value_name)
ca01a97b 373 . ' }';
374 my $sub = eval $code;
375 confess "Could not create writer for '$attr_name' because $@ \n code: $code" if $@;
376 return $sub;
a15dff8d 377}
c0e30cf5 378
d7f17ebb 379sub generate_reader_method {
9e93dd19 380 my $attr = shift;
381 my $attr_name = $attr->slots;
ca01a97b 382 my $code = 'sub {'
383 . 'confess "Cannot assign a value to a read-only accessor" if @_ > 1;'
9e93dd19 384 . $attr->_inline_check_lazy
385 . 'return ' . $attr->_inline_auto_deref( '$_[0]->{$attr_name}' ) . ';'
ca01a97b 386 . '}';
387 my $sub = eval $code;
388 confess "Could not create reader for '$attr_name' because $@ \n code: $code" if $@;
389 return $sub;
d7f17ebb 390}
391
452bac1b 392sub install_accessors {
393 my $self = shift;
394 $self->SUPER::install_accessors(@_);
395
396 if ($self->has_handles) {
397
398 # NOTE:
399 # Here we canonicalize the 'handles' option
400 # this will sort out any details and always
401 # return an hash of methods which we want
402 # to delagate to, see that method for details
403 my %handles = $self->_canonicalize_handles();
404
405 # find the name of the accessor for this attribute
406 my $accessor_name = $self->reader || $self->accessor;
407 (defined $accessor_name)
408 || confess "You cannot install delegation without a reader or accessor for the attribute";
409
410 # make sure we handle HASH accessors correctly
411 ($accessor_name) = keys %{$accessor_name}
412 if ref($accessor_name) eq 'HASH';
413
414 # install the delegation ...
415 my $associated_class = $self->associated_class;
416 foreach my $handle (keys %handles) {
417 my $method_to_call = $handles{$handle};
418
419 (!$associated_class->has_method($handle))
420 || confess "You cannot overwrite a locally defined method ($handle) with a delegation";
421
422 if ((reftype($method_to_call) || '') eq 'CODE') {
423 $associated_class->add_method($handle => $method_to_call);
424 }
425 else {
426 $associated_class->add_method($handle => sub {
b805c70c 427 # FIXME
428 # we should check for lack of
429 # a callable return value from
430 # the accessor here
452bac1b 431 ((shift)->$accessor_name())->$method_to_call(@_);
432 });
433 }
434 }
435 }
436
437 return;
438}
439
98aae381 440# private methods to help delegation ...
441
452bac1b 442sub _canonicalize_handles {
443 my $self = shift;
444 my $handles = $self->handles;
445 if (ref($handles) eq 'HASH') {
446 return %{$handles};
447 }
448 elsif (ref($handles) eq 'ARRAY') {
449 return map { $_ => $_ } @{$handles};
450 }
451 elsif (ref($handles) eq 'Regexp') {
452 ($self->has_type_constraint)
453 || confess "Cannot delegate methods based on a RegExpr without a type constraint (isa)";
454 return map { ($_ => $_) }
455 grep { $handles } $self->_get_delegate_method_list;
456 }
457 elsif (ref($handles) eq 'CODE') {
458 return $handles->($self, $self->_find_delegate_metaclass);
459 }
460 else {
461 confess "Unable to canonicalize the 'handles' option with $handles";
462 }
463}
464
465sub _find_delegate_metaclass {
466 my $self = shift;
98aae381 467 if (my $class = $self->_isa_metadata) {
452bac1b 468 # if the class does have
469 # a meta method, use it
470 return $class->meta if $class->can('meta');
471 # otherwise we might be
472 # dealing with a non-Moose
473 # class, and need to make
474 # our own metaclass
475 return Moose::Meta::Class->initialize($class);
476 }
98aae381 477 elsif (my $role = $self->_does_metadata) {
452bac1b 478 # our role will always have
479 # a meta method
98aae381 480 return $role->meta;
452bac1b 481 }
482 else {
483 confess "Cannot find delegate metaclass for attribute " . $self->name;
484 }
485}
486
487sub _get_delegate_method_list {
488 my $self = shift;
489 my $meta = $self->_find_delegate_metaclass;
490 if ($meta->isa('Class::MOP::Class')) {
093b12c2 491 return map { $_->{name} } # NOTE: !never! delegate &meta
492 grep { $_->{class} ne 'Moose::Object' && $_->{name} ne 'meta' }
452bac1b 493 $meta->compute_all_applicable_methods;
494 }
495 elsif ($meta->isa('Moose::Meta::Role')) {
496 return $meta->get_method_list;
497 }
498 else {
499 confess "Unable to recognize the delegate metaclass '$meta'";
500 }
501}
502
c0e30cf5 5031;
504
505__END__
506
507=pod
508
509=head1 NAME
510
6ba6d68c 511Moose::Meta::Attribute - The Moose attribute metaclass
c0e30cf5 512
513=head1 DESCRIPTION
514
e522431d 515This is a subclass of L<Class::MOP::Attribute> with Moose specific
6ba6d68c 516extensions.
517
518For the most part, the only time you will ever encounter an
519instance of this class is if you are doing some serious deep
520introspection. To really understand this class, you need to refer
521to the L<Class::MOP::Attribute> documentation.
e522431d 522
c0e30cf5 523=head1 METHODS
524
6ba6d68c 525=head2 Overridden methods
526
527These methods override methods in L<Class::MOP::Attribute> and add
528Moose specific features. You can safely assume though that they
529will behave just as L<Class::MOP::Attribute> does.
530
c0e30cf5 531=over 4
532
533=item B<new>
534
d500266f 535=item B<initialize_instance_slot>
536
a15dff8d 537=item B<generate_accessor_method>
538
539=item B<generate_writer_method>
540
d7f17ebb 541=item B<generate_reader_method>
542
452bac1b 543=item B<install_accessors>
544
a15dff8d 545=back
546
6ba6d68c 547=head2 Additional Moose features
548
8449e6e7 549Moose attributes support type-constraint checking, weak reference
6ba6d68c 550creation and type coercion.
551
a15dff8d 552=over 4
553
9e93dd19 554=item B<clone_and_inherit_options>
555
556This is to support the C<has '+foo'> feature, it clones an attribute
557from a superclass and allows a very specific set of changes to be made
558to the attribute.
559
a15dff8d 560=item B<has_type_constraint>
561
6ba6d68c 562Returns true if this meta-attribute has a type constraint.
563
a15dff8d 564=item B<type_constraint>
565
6ba6d68c 566A read-only accessor for this meta-attribute's type constraint. For
567more information on what you can do with this, see the documentation
568for L<Moose::Meta::TypeConstraint>.
a15dff8d 569
452bac1b 570=item B<has_handles>
571
572Returns true if this meta-attribute performs delegation.
573
574=item B<handles>
575
576This returns the value which was passed into the handles option.
577
6ba6d68c 578=item B<is_weak_ref>
a15dff8d 579
02a0fb52 580Returns true if this meta-attribute produces a weak reference.
4b598ea3 581
ca01a97b 582=item B<is_required>
583
02a0fb52 584Returns true if this meta-attribute is required to have a value.
ca01a97b 585
586=item B<is_lazy>
587
02a0fb52 588Returns true if this meta-attribute should be initialized lazily.
ca01a97b 589
590NOTE: lazy attributes, B<must> have a C<default> field set.
591
34a66aa3 592=item B<should_coerce>
4b598ea3 593
02a0fb52 594Returns true if this meta-attribute should perform type coercion.
6ba6d68c 595
536f0b17 596=item B<should_auto_deref>
597
598Returns true if this meta-attribute should perform automatic
599auto-dereferencing.
600
601NOTE: This can only be done for attributes whose type constraint is
602either I<ArrayRef> or I<HashRef>.
603
8c9d74e7 604=item B<has_trigger>
605
02a0fb52 606Returns true if this meta-attribute has a trigger set.
607
8c9d74e7 608=item B<trigger>
609
02a0fb52 610This is a CODE reference which will be executed every time the
611value of an attribute is assigned. The CODE ref will get two values,
612the invocant and the new value. This can be used to handle I<basic>
613bi-directional relations.
614
c0e30cf5 615=back
616
617=head1 BUGS
618
619All complex software has bugs lurking in it, and this module is no
620exception. If you find a bug please either email me, or add the bug
621to cpan-RT.
622
c0e30cf5 623=head1 AUTHOR
624
625Stevan Little E<lt>stevan@iinteractive.comE<gt>
626
98aae381 627Yuval Kogman E<lt>nothingmuch@woobling.comE<gt>
628
c0e30cf5 629=head1 COPYRIGHT AND LICENSE
630
631Copyright 2006 by Infinity Interactive, Inc.
632
633L<http://www.iinteractive.com>
634
635This library is free software; you can redistribute it and/or modify
636it under the same terms as Perl itself.
637
8a7a9c53 638=cut