726 lines
20 KiB
Perl
726 lines
20 KiB
Perl
package Moose::Util;
|
|
our $VERSION = '2.2207';
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Module::Runtime 0.014 'use_package_optimistically', 'module_notional_filename';
|
|
use Data::OptList;
|
|
use Sub::Exporter;
|
|
use Scalar::Util 'blessed';
|
|
use List::Util 1.33 qw(first any all);
|
|
use overload ();
|
|
use Try::Tiny;
|
|
|
|
|
|
my @exports = qw[
|
|
find_meta
|
|
is_role
|
|
does_role
|
|
search_class_by_role
|
|
ensure_all_roles
|
|
apply_all_roles
|
|
with_traits
|
|
get_all_init_args
|
|
get_all_attribute_values
|
|
resolve_metatrait_alias
|
|
resolve_metaclass_alias
|
|
add_method_modifier
|
|
english_list
|
|
meta_attribute_alias
|
|
meta_class_alias
|
|
throw_exception
|
|
];
|
|
|
|
Sub::Exporter::setup_exporter({
|
|
exports => \@exports,
|
|
groups => { all => \@exports }
|
|
});
|
|
|
|
# Things that need to ->import from Moose::Util
|
|
# should be loaded after Moose::Util defines ->import
|
|
require Class::MOP;
|
|
|
|
sub throw_exception {
|
|
my ($class_name, @args_to_exception) = @_;
|
|
my $class = "Moose::Exception::$class_name";
|
|
_load_user_class( $class );
|
|
die $class->new( @args_to_exception );
|
|
}
|
|
|
|
## some utils for the utils ...
|
|
|
|
sub find_meta { Class::MOP::class_of(@_) }
|
|
|
|
## the functions ...
|
|
|
|
sub is_role {
|
|
my $package_or_obj = shift;
|
|
|
|
my $meta = find_meta($package_or_obj);
|
|
return if not $meta;
|
|
return $meta->isa('Moose::Meta::Role');
|
|
}
|
|
|
|
sub does_role {
|
|
my ($class_or_obj, $role) = @_;
|
|
|
|
if (try { $class_or_obj->isa('Moose::Object') }) {
|
|
return $class_or_obj->does($role);
|
|
}
|
|
|
|
my $meta = find_meta($class_or_obj);
|
|
|
|
return unless defined $meta;
|
|
return unless $meta->can('does_role');
|
|
return 1 if $meta->does_role($role);
|
|
return;
|
|
}
|
|
|
|
sub search_class_by_role {
|
|
my ($class_or_obj, $role) = @_;
|
|
|
|
my $meta = find_meta($class_or_obj);
|
|
|
|
return unless defined $meta;
|
|
|
|
my $role_name = blessed $role ? $role->name : $role;
|
|
|
|
foreach my $class ($meta->class_precedence_list) {
|
|
|
|
my $_meta = find_meta($class);
|
|
|
|
next unless defined $_meta;
|
|
|
|
foreach my $role (@{ $_meta->roles || [] }) {
|
|
return $class if $role->name eq $role_name;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
# this can possibly behave in unexpected ways because the roles being composed
|
|
# before being applied could differ from call to call; I'm not sure if or how
|
|
# to document this possible quirk.
|
|
sub ensure_all_roles {
|
|
my $applicant = shift;
|
|
_apply_all_roles($applicant, sub { !does_role($applicant, $_) }, @_);
|
|
}
|
|
|
|
sub apply_all_roles {
|
|
my $applicant = shift;
|
|
_apply_all_roles($applicant, undef, @_);
|
|
}
|
|
|
|
sub _apply_all_roles {
|
|
my $applicant = shift;
|
|
my $role_filter = shift;
|
|
|
|
unless (@_) {
|
|
require Moose;
|
|
throw_exception( MustSpecifyAtleastOneRoleToApplicant => applicant => $applicant );
|
|
}
|
|
|
|
# If @_ contains role meta objects, mkopt will think that they're values,
|
|
# because they're references. In other words (roleobj1, roleobj2,
|
|
# roleobj3) will become [ [ roleobj1, roleobj2 ], [ roleobj3, undef ] ]
|
|
# -- this is no good. We'll preprocess @_ first to eliminate the potential
|
|
# bug.
|
|
# -- rjbs, 2011-04-08
|
|
my $roles = Data::OptList::mkopt( [@_], {
|
|
moniker => 'role',
|
|
name_test => sub {
|
|
! ref $_[0] or blessed($_[0]) && $_[0]->isa('Moose::Meta::Role')
|
|
}
|
|
});
|
|
|
|
my @role_metas;
|
|
foreach my $role (@$roles) {
|
|
my $meta;
|
|
|
|
if ( blessed $role->[0] ) {
|
|
$meta = $role->[0];
|
|
}
|
|
else {
|
|
_load_user_class( $role->[0] , $role->[1] );
|
|
$meta = find_meta( $role->[0] );
|
|
}
|
|
|
|
unless ($meta && $meta->isa('Moose::Meta::Role') ) {
|
|
throw_exception( CanOnlyConsumeRole => role_name => $role->[0] );
|
|
}
|
|
|
|
push @role_metas, [ $meta, $role->[1] ];
|
|
}
|
|
|
|
if ( defined $role_filter ) {
|
|
@role_metas = grep { local $_ = $_->[0]; $role_filter->() } @role_metas;
|
|
}
|
|
|
|
return unless @role_metas;
|
|
|
|
_load_user_class($applicant)
|
|
unless blessed($applicant)
|
|
|| Class::MOP::class_of($applicant);
|
|
|
|
my $meta = ( blessed $applicant ? $applicant : Moose::Meta::Class->initialize($applicant) );
|
|
|
|
if ( scalar @role_metas == 1 ) {
|
|
my ( $role, $params ) = @{ $role_metas[0] };
|
|
$role->apply( $meta, ( defined $params ? %$params : () ) );
|
|
}
|
|
else {
|
|
Moose::Meta::Role->combine(@role_metas)->apply($meta);
|
|
}
|
|
}
|
|
|
|
sub with_traits {
|
|
my ($class, @roles) = @_;
|
|
return $class unless @roles;
|
|
return Moose::Meta::Class->create_anon_class(
|
|
superclasses => [$class],
|
|
roles => \@roles,
|
|
cache => 1,
|
|
)->name;
|
|
}
|
|
|
|
# instance deconstruction ...
|
|
|
|
sub get_all_attribute_values {
|
|
my ($class, $instance) = @_;
|
|
return +{
|
|
map { $_->name => $_->get_value($instance) }
|
|
grep { $_->has_value($instance) }
|
|
$class->get_all_attributes
|
|
};
|
|
}
|
|
|
|
sub get_all_init_args {
|
|
my ($class, $instance) = @_;
|
|
return +{
|
|
map { $_->init_arg => $_->get_value($instance) }
|
|
grep { $_->has_value($instance) }
|
|
grep { defined($_->init_arg) }
|
|
$class->get_all_attributes
|
|
};
|
|
}
|
|
|
|
sub resolve_metatrait_alias {
|
|
return resolve_metaclass_alias( @_, trait => 1 );
|
|
}
|
|
|
|
sub _build_alias_package_name {
|
|
my ($type, $name, $trait) = @_;
|
|
return 'Moose::Meta::'
|
|
. $type
|
|
. '::Custom::'
|
|
. ( $trait ? 'Trait::' : '' )
|
|
. $name;
|
|
}
|
|
|
|
{
|
|
my %cache;
|
|
|
|
sub resolve_metaclass_alias {
|
|
my ( $type, $metaclass_name, %options ) = @_;
|
|
|
|
my $cache_key = $type . q{ } . ( $options{trait} ? '-Trait' : '' );
|
|
return $cache{$cache_key}{$metaclass_name}
|
|
if $cache{$cache_key}{$metaclass_name};
|
|
|
|
my $possible_full_name = _build_alias_package_name(
|
|
$type, $metaclass_name, $options{trait}
|
|
);
|
|
|
|
my @possible = ($possible_full_name, $metaclass_name);
|
|
for my $package (@possible) {
|
|
use_package_optimistically($package);
|
|
if ($package->can('register_implementation')) {
|
|
return $cache{$cache_key}{$metaclass_name} =
|
|
$package->register_implementation;
|
|
}
|
|
elsif (find_meta($package)) {
|
|
return $cache{$cache_key}{$metaclass_name} = $package;
|
|
}
|
|
}
|
|
|
|
throw_exception( CannotLocatePackageInINC => possible_packages => _english_list_or(@possible),
|
|
INC => \@INC,
|
|
type => $type,
|
|
metaclass_name => $metaclass_name,
|
|
params => \%options
|
|
);
|
|
}
|
|
}
|
|
|
|
sub add_method_modifier {
|
|
my ( $class_or_obj, $modifier_name, $args ) = @_;
|
|
my $meta
|
|
= $class_or_obj->can('add_before_method_modifier')
|
|
? $class_or_obj
|
|
: find_meta($class_or_obj);
|
|
my $code = pop @{$args};
|
|
my $add_modifier_method = 'add_' . $modifier_name . '_method_modifier';
|
|
if ( my $method_modifier_type = ref( $args->[0] ) ) {
|
|
if ( $method_modifier_type eq 'Regexp' ) {
|
|
my @all_methods = $meta->get_all_methods;
|
|
my @matched_methods
|
|
= grep { $_->name =~ $args->[0] } @all_methods;
|
|
$meta->$add_modifier_method( $_->name, $code )
|
|
for @matched_methods;
|
|
}
|
|
elsif ($method_modifier_type eq 'ARRAY') {
|
|
$meta->$add_modifier_method( $_, $code ) for @{$args->[0]};
|
|
}
|
|
else {
|
|
throw_exception( IllegalMethodTypeToAddMethodModifier => class_or_object => $class_or_obj,
|
|
modifier_name => $modifier_name,
|
|
params => $args
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
$meta->$add_modifier_method( $_, $code ) for @{$args};
|
|
}
|
|
}
|
|
|
|
sub english_list {
|
|
_english_list_and(@_);
|
|
}
|
|
|
|
sub _english_list_and {
|
|
_english_list('and', \@_);
|
|
}
|
|
|
|
sub _english_list_or {
|
|
_english_list('or', \@_);
|
|
}
|
|
|
|
sub _english_list {
|
|
my ($conjunction, $items) = @_;
|
|
|
|
my @items = sort @$items;
|
|
|
|
return $items[0] if @items == 1;
|
|
return "$items[0] $conjunction $items[1]" if @items == 2;
|
|
|
|
my $tail = pop @items;
|
|
my $list = join ', ', @items;
|
|
$list .= ", $conjunction " . $tail;
|
|
|
|
return $list;
|
|
}
|
|
|
|
sub _caller_info {
|
|
my $level = @_ ? ($_[0] + 1) : 2;
|
|
my %info;
|
|
@info{qw(package file line)} = caller($level);
|
|
return %info;
|
|
}
|
|
|
|
sub _create_alias {
|
|
my ($type, $name, $trait, $for) = @_;
|
|
my $package = _build_alias_package_name($type, $name, $trait);
|
|
Class::MOP::Class->initialize($package)->add_method(
|
|
register_implementation => sub { $for }
|
|
);
|
|
}
|
|
|
|
sub meta_attribute_alias {
|
|
my ($to, $from) = @_;
|
|
$from ||= caller;
|
|
my $meta = Class::MOP::class_of($from);
|
|
my $trait = $meta->isa('Moose::Meta::Role');
|
|
_create_alias('Attribute', $to, $trait, $from);
|
|
}
|
|
|
|
sub meta_class_alias {
|
|
my ($to, $from) = @_;
|
|
$from ||= caller;
|
|
my $meta = Class::MOP::class_of($from);
|
|
my $trait = $meta->isa('Moose::Meta::Role');
|
|
_create_alias('Class', $to, $trait, $from);
|
|
}
|
|
|
|
sub _load_user_class {
|
|
my ($class, $opts) = @_;
|
|
&use_package_optimistically(
|
|
$class,
|
|
$opts && $opts->{-version} ? $opts->{-version} : ()
|
|
);
|
|
}
|
|
|
|
# XXX - this should be added to Params::Util
|
|
sub _STRINGLIKE0 ($) {
|
|
return 0 if !defined $_[0];
|
|
return 1 if !ref $_[0];
|
|
return 1 if overload::OverloadedStringify($_[0]);
|
|
return 0;
|
|
}
|
|
|
|
sub _reconcile_roles_for_metaclass {
|
|
my ($class_meta_name, $super_meta_name) = @_;
|
|
|
|
my @role_differences = _role_differences(
|
|
$class_meta_name, $super_meta_name,
|
|
);
|
|
|
|
# handle the case where we need to fix compatibility between a class and
|
|
# its parent, but all roles in the class are already also done by the
|
|
# parent
|
|
# see t/metaclasses/metaclass_compat_no_fixing_bug.t
|
|
return $super_meta_name
|
|
unless @role_differences;
|
|
|
|
return Moose::Meta::Class->create_anon_class(
|
|
superclasses => [$super_meta_name],
|
|
roles => [map { $_->name } @role_differences],
|
|
cache => 1,
|
|
)->name;
|
|
}
|
|
|
|
sub _role_differences {
|
|
my ($class_meta_name, $super_meta_name) = @_;
|
|
my @super_role_metas = map {
|
|
$_->isa('Moose::Meta::Role::Composite')
|
|
? (@{ $_->get_roles })
|
|
: ($_)
|
|
} $super_meta_name->meta->can('_roles_with_inheritance')
|
|
? $super_meta_name->meta->_roles_with_inheritance
|
|
: $super_meta_name->meta->can('roles')
|
|
? @{ $super_meta_name->meta->roles }
|
|
: ();
|
|
my @role_metas = map {
|
|
$_->isa('Moose::Meta::Role::Composite')
|
|
? (@{ $_->get_roles })
|
|
: ($_)
|
|
} $class_meta_name->meta->can('_roles_with_inheritance')
|
|
? $class_meta_name->meta->_roles_with_inheritance
|
|
: $class_meta_name->meta->can('roles')
|
|
? @{ $class_meta_name->meta->roles }
|
|
: ();
|
|
my @differences;
|
|
for my $role_meta (@role_metas) {
|
|
push @differences, $role_meta
|
|
unless any { $_->name eq $role_meta->name } @super_role_metas;
|
|
}
|
|
return @differences;
|
|
}
|
|
|
|
sub _classes_differ_by_roles_only {
|
|
my ( $self_meta_name, $super_meta_name ) = @_;
|
|
|
|
my $common_base_name
|
|
= _find_common_base( $self_meta_name, $super_meta_name );
|
|
|
|
return unless defined $common_base_name;
|
|
|
|
my @super_meta_name_ancestor_names
|
|
= _get_ancestors_until( $super_meta_name, $common_base_name );
|
|
my @class_meta_name_ancestor_names
|
|
= _get_ancestors_until( $self_meta_name, $common_base_name );
|
|
|
|
return
|
|
unless all { _is_role_only_subclass($_) }
|
|
@super_meta_name_ancestor_names,
|
|
@class_meta_name_ancestor_names;
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub _find_common_base {
|
|
my ($meta1, $meta2) = map { Class::MOP::class_of($_) } @_;
|
|
return unless defined $meta1 && defined $meta2;
|
|
|
|
# FIXME? This doesn't account for multiple inheritance (not sure
|
|
# if it needs to though). For example, if somewhere in $meta1's
|
|
# history it inherits from both ClassA and ClassB, and $meta2
|
|
# inherits from ClassB & ClassA, does it matter? And what crazy
|
|
# fool would do that anyway?
|
|
|
|
my %meta1_parents = map { $_ => 1 } $meta1->linearized_isa;
|
|
|
|
return first { $meta1_parents{$_} } $meta2->linearized_isa;
|
|
}
|
|
|
|
sub _get_ancestors_until {
|
|
my ($start_name, $until_name) = @_;
|
|
|
|
my @ancestor_names;
|
|
for my $ancestor_name (Class::MOP::class_of($start_name)->linearized_isa) {
|
|
last if $ancestor_name eq $until_name;
|
|
push @ancestor_names, $ancestor_name;
|
|
}
|
|
return @ancestor_names;
|
|
}
|
|
|
|
sub _is_role_only_subclass {
|
|
my ($meta_name) = @_;
|
|
my $meta = Class::MOP::Class->initialize($meta_name);
|
|
my @parent_names = $meta->superclasses;
|
|
|
|
# XXX: don't feel like messing with multiple inheritance here... what would
|
|
# that even do?
|
|
return unless @parent_names == 1;
|
|
my ($parent_name) = @parent_names;
|
|
my $parent_meta = Class::MOP::Class->initialize($parent_name);
|
|
|
|
# only get the roles attached to this particular class, don't look at
|
|
# superclasses
|
|
my @roles = $meta->can('calculate_all_roles')
|
|
? $meta->calculate_all_roles
|
|
: ();
|
|
|
|
# it's obviously not a role-only subclass if it doesn't do any roles
|
|
return unless @roles;
|
|
|
|
# loop over all methods that are a part of the current class
|
|
# (not inherited)
|
|
for my $method ( $meta->_get_local_methods ) {
|
|
# always ignore meta
|
|
next if $method->isa('Class::MOP::Method::Meta');
|
|
# we'll deal with attributes below
|
|
next if $method->can('associated_attribute');
|
|
# if the method comes from a role we consumed, ignore it
|
|
next if $meta->can('does_role')
|
|
&& $meta->does_role($method->original_package_name);
|
|
# FIXME - this really isn't right. Just because a modifier is
|
|
# defined in a role doesn't mean it isn't _also_ defined in the
|
|
# subclass.
|
|
next if $method->isa('Class::MOP::Method::Wrapped')
|
|
&& (
|
|
(!scalar($method->around_modifiers)
|
|
|| any { $_->has_around_method_modifiers($method->name) } @roles)
|
|
&& (!scalar($method->before_modifiers)
|
|
|| any { $_->has_before_method_modifiers($method->name) } @roles)
|
|
&& (!scalar($method->after_modifiers)
|
|
|| any { $_->has_after_method_modifiers($method->name) } @roles)
|
|
);
|
|
|
|
return 0;
|
|
}
|
|
|
|
# loop over all attributes that are a part of the current class
|
|
# (not inherited)
|
|
# FIXME - this really isn't right. Just because an attribute is
|
|
# defined in a role doesn't mean it isn't _also_ defined in the
|
|
# subclass.
|
|
for my $attr (map { $meta->get_attribute($_) } $meta->get_attribute_list) {
|
|
next if any { $_->has_attribute($attr->name) } @roles;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub _is_package_loaded {
|
|
my ($package) = @_;
|
|
defined $INC{module_notional_filename($package)};
|
|
}
|
|
|
|
1;
|
|
|
|
# ABSTRACT: Utilities for working with Moose classes
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=encoding UTF-8
|
|
|
|
=head1 NAME
|
|
|
|
Moose::Util - Utilities for working with Moose classes
|
|
|
|
=head1 VERSION
|
|
|
|
version 2.2207
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Moose::Util qw/find_meta does_role search_class_by_role/;
|
|
|
|
my $meta = find_meta($object) || die "No metaclass found";
|
|
|
|
if (does_role($object, $role)) {
|
|
print "The object can do $role!\n";
|
|
}
|
|
|
|
my $class = search_class_by_role($object, 'FooRole');
|
|
print "Nearest class with 'FooRole' is $class\n";
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module provides a set of utility functions. Many of these
|
|
functions are intended for use in Moose itself or MooseX modules, but
|
|
some of them may be useful for use in your own code.
|
|
|
|
=head1 EXPORTED FUNCTIONS
|
|
|
|
=head2 find_meta($class_or_obj)
|
|
|
|
This method takes a class name or object and attempts to find a
|
|
metaclass for the class, if one exists. It will B<not> create one if it
|
|
does not yet exist.
|
|
|
|
=head2 is_role($package_or_obj)
|
|
|
|
Returns true if the provided package name or object is a L<Moose::Role>.
|
|
|
|
=head2 does_role($class_or_obj, $role_or_obj)
|
|
|
|
Returns true if C<$class_or_obj> does the given C<$role_or_obj>. The role can
|
|
be provided as a name or a L<Moose::Meta::Role> object.
|
|
|
|
The class must already have a metaclass for this to work. If it doesn't, this
|
|
function simply returns false.
|
|
|
|
=head2 search_class_by_role($class_or_obj, $role_or_obj)
|
|
|
|
Returns the first class in the class's precedence list that does
|
|
C<$role_or_obj>, if any. The role can be either a name or a
|
|
L<Moose::Meta::Role> object.
|
|
|
|
The class must already have a metaclass for this to work.
|
|
|
|
=head2 apply_all_roles($applicant, @roles)
|
|
|
|
This function applies one or more roles to the given C<$applicant>. The
|
|
applicant can be a role name, class name, or object.
|
|
|
|
The C<$applicant> must already have a metaclass object.
|
|
|
|
The list of C<@roles> should a list of names or L<Moose::Meta::Role> objects,
|
|
each of which can be followed by an optional hash reference of options
|
|
(C<-excludes> and C<-alias>).
|
|
|
|
=head2 ensure_all_roles($applicant, @roles)
|
|
|
|
This function is similar to C<apply_all_roles>, but only applies roles that
|
|
C<$applicant> does not already consume.
|
|
|
|
=head2 with_traits($class_name, @role_names)
|
|
|
|
This function creates a new class from C<$class_name> with each of
|
|
C<@role_names> applied. It returns the name of the new class.
|
|
|
|
=head2 get_all_attribute_values($meta, $instance)
|
|
|
|
Returns a hash reference containing all of the C<$instance>'s
|
|
attributes. The keys are attribute names.
|
|
|
|
=head2 get_all_init_args($meta, $instance)
|
|
|
|
Returns a hash reference containing all of the C<init_arg> values for
|
|
the instance's attributes. The values are the associated attribute
|
|
values. If an attribute does not have a defined C<init_arg>, it is
|
|
skipped.
|
|
|
|
This could be useful in cloning an object.
|
|
|
|
=head2 resolve_metaclass_alias($category, $name, %options)
|
|
|
|
=head2 resolve_metatrait_alias($category, $name, %options)
|
|
|
|
Resolves a short name to a full class name. Short names are often used
|
|
when specifying the C<metaclass> or C<traits> option for an attribute:
|
|
|
|
has foo => (
|
|
metaclass => "Bar",
|
|
);
|
|
|
|
The name resolution mechanism is covered in
|
|
L<Moose/Metaclass and Trait Name Resolution>.
|
|
|
|
=head2 meta_class_alias($to[, $from])
|
|
|
|
=head2 meta_attribute_alias($to[, $from])
|
|
|
|
Create an alias from the class C<$from> (or the current package, if
|
|
C<$from> is unspecified), so that
|
|
L<Moose/Metaclass and Trait Name Resolution> works properly.
|
|
|
|
=head2 english_list(@items)
|
|
|
|
Given a list of scalars, turns them into a proper list in English
|
|
("one and two", "one, two, three, and four"). This is used to help us
|
|
make nicer error messages.
|
|
|
|
=head2 throw_exception( $class_name, %arguments_to_exception)
|
|
|
|
Calls die with an object of Moose::Exception::$class_name, with
|
|
%arguments_to_exception passed as arguments.
|
|
|
|
=head1 TODO
|
|
|
|
Here is a list of possible functions to write
|
|
|
|
=over 4
|
|
|
|
=item discovering original method from modified method
|
|
|
|
=item search for origin class of a method or attribute
|
|
|
|
=back
|
|
|
|
=head1 BUGS
|
|
|
|
See L<Moose/BUGS> for details on reporting bugs.
|
|
|
|
=head1 AUTHORS
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
Stevan Little <stevan@cpan.org>
|
|
|
|
=item *
|
|
|
|
Dave Rolsky <autarch@urth.org>
|
|
|
|
=item *
|
|
|
|
Jesse Luehrs <doy@cpan.org>
|
|
|
|
=item *
|
|
|
|
Shawn M Moore <sartak@cpan.org>
|
|
|
|
=item *
|
|
|
|
יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
|
|
|
|
=item *
|
|
|
|
Karen Etheridge <ether@cpan.org>
|
|
|
|
=item *
|
|
|
|
Florian Ragwitz <rafl@debian.org>
|
|
|
|
=item *
|
|
|
|
Hans Dieter Pearcey <hdp@cpan.org>
|
|
|
|
=item *
|
|
|
|
Chris Prather <chris@prather.org>
|
|
|
|
=item *
|
|
|
|
Matt S Trout <mstrout@cpan.org>
|
|
|
|
=back
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
This software is copyright (c) 2006 by Infinity Interactive, Inc.
|
|
|
|
This is free software; you can redistribute it and/or modify it under
|
|
the same terms as the Perl 5 programming language system itself.
|
|
|
|
=cut
|