X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMoose%2FUtil%2FTypeConstraints.pm;h=9dd4ff10a457a387a266c83e5b6a6b4a1dd75d13;hb=f75f625dfbf7c765bdfa127a59b49a4503344298;hp=7aa6f99af1729ba897e90a47a85a53f17942f8da;hpb=4c0b35998743adde444d886607da524c7094327c;p=gitmo%2FMoose.git diff --git a/lib/Moose/Util/TypeConstraints.pm b/lib/Moose/Util/TypeConstraints.pm index 7aa6f99..9dd4ff1 100644 --- a/lib/Moose/Util/TypeConstraints.pm +++ b/lib/Moose/Util/TypeConstraints.pm @@ -5,10 +5,11 @@ use strict; use warnings; use Carp (); -use Scalar::Util 'blessed'; +use List::MoreUtils qw( all any ); +use Scalar::Util qw( blessed reftype ); use Moose::Exporter; -our $VERSION = '0.57'; +our $VERSION = '0.72'; $VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; @@ -19,35 +20,11 @@ our $AUTHORITY = 'cpan:STEVAN'; # ensures the prototypes are in scope when consumers are # compiled. -# creation and location -sub find_type_constraint ($); -sub register_type_constraint ($); -sub find_or_create_type_constraint ($;$); -sub find_or_parse_type_constraint ($); -sub find_or_create_isa_type_constraint ($); -sub find_or_create_does_type_constraint ($); -sub create_type_constraint_union (@); -sub create_parameterized_type_constraint ($); -sub create_class_type_constraint ($;$); -sub create_role_type_constraint ($;$); -sub create_enum_type_constraint ($$); - # dah sugah! -sub type ($$;$$); -sub subtype ($$;$$$); -sub class_type ($;$); -sub coerce ($@); -sub as ($); -sub from ($); sub where (&); sub via (&); sub message (&); sub optimize_as (&); -sub enum ($;@); - -## private stuff ... -sub _create_type_constraint ($$$;$$); -sub _install_type_coercions ($$); ## -------------------------------------------------------- @@ -66,7 +43,8 @@ use Moose::Util::TypeConstraints::OptimizedConstraints; Moose::Exporter->setup_import_methods( as_is => [ qw( - type subtype class_type role_type as where message optimize_as + type subtype class_type role_type maybe_type + as where message optimize_as coerce from via enum find_type_constraint @@ -92,7 +70,7 @@ sub export_type_constraints_as_functions { } } -sub create_type_constraint_union (@) { +sub create_type_constraint_union { my @type_constraint_names; if (scalar @_ == 1 && _detect_type_constraint_union($_[0])) { @@ -101,48 +79,58 @@ sub create_type_constraint_union (@) { else { @type_constraint_names = @_; } - + (scalar @type_constraint_names >= 2) - || Moose::throw_error("You must pass in at least 2 type names to make a union"); + || __PACKAGE__->_throw_error("You must pass in at least 2 type names to make a union"); - ($REGISTRY->has_type_constraint($_)) - || Moose::throw_error("Could not locate type constraint ($_) for the union") - foreach @type_constraint_names; + my @type_constraints = map { + find_or_parse_type_constraint($_) || + __PACKAGE__->_throw_error("Could not locate type constraint ($_) for the union"); + } @type_constraint_names; return Moose::Meta::TypeConstraint::Union->new( - type_constraints => [ - map { - $REGISTRY->get_type_constraint($_) - } @type_constraint_names - ], + type_constraints => \@type_constraints ); } -sub create_parameterized_type_constraint ($) { +sub create_parameterized_type_constraint { my $type_constraint_name = shift; - my ($base_type, $type_parameter) = _parse_parameterized_type_constraint($type_constraint_name); (defined $base_type && defined $type_parameter) - || Moose::throw_error("Could not parse type name ($type_constraint_name) correctly"); - - ($REGISTRY->has_type_constraint($base_type)) - || Moose::throw_error("Could not locate the base type ($base_type)"); + || __PACKAGE__->_throw_error("Could not parse type name ($type_constraint_name) correctly"); - return Moose::Meta::TypeConstraint::Parameterized->new( - name => $type_constraint_name, - parent => $REGISTRY->get_type_constraint($base_type), - type_parameter => find_or_create_isa_type_constraint($type_parameter), - ); + if ($REGISTRY->has_type_constraint($base_type)) { + my $base_type_tc = $REGISTRY->get_type_constraint($base_type); + return _create_parameterized_type_constraint( + $base_type_tc, + $type_parameter + ); + } else { + __PACKAGE__->_throw_error("Could not locate the base type ($base_type)"); + } } +sub _create_parameterized_type_constraint { + my ( $base_type_tc, $type_parameter ) = @_; + if ( $base_type_tc->can('parameterize') ) { + return $base_type_tc->parameterize($type_parameter); + } else { + return Moose::Meta::TypeConstraint::Parameterized->new( + name => $base_type_tc->name . '[' . $type_parameter . ']', + parent => $base_type_tc, + type_parameter => find_or_create_isa_type_constraint($type_parameter), + ); + } +} + #should we also support optimized checks? -sub create_class_type_constraint ($;$) { +sub create_class_type_constraint { my ( $class, $options ) = @_; # too early for this check #find_type_constraint("ClassName")->check($class) - # || Moose::throw_error("Can't create a class type constraint because '$class' is not a class name"); + # || __PACKAGE__->_throw_error("Can't create a class type constraint because '$class' is not a class name"); my %options = ( class => $class, @@ -155,12 +143,12 @@ sub create_class_type_constraint ($;$) { Moose::Meta::TypeConstraint::Class->new( %options ); } -sub create_role_type_constraint ($;$) { +sub create_role_type_constraint { my ( $role, $options ) = @_; # too early for this check #find_type_constraint("ClassName")->check($class) - # || Moose::throw_error("Can't create a class type constraint because '$class' is not a class name"); + # || __PACKAGE__->_throw_error("Can't create a class type constraint because '$class' is not a class name"); my %options = ( role => $role, @@ -174,7 +162,7 @@ sub create_role_type_constraint ($;$) { } -sub find_or_create_type_constraint ($;$) { +sub find_or_create_type_constraint { my ( $type_constraint_name, $options_for_anon_type ) = @_; if ( my $constraint = find_or_parse_type_constraint($type_constraint_name) ) { @@ -201,39 +189,52 @@ sub find_or_create_type_constraint ($;$) { return; } -sub find_or_create_isa_type_constraint ($) { +sub find_or_create_isa_type_constraint { my $type_constraint_name = shift; find_or_parse_type_constraint($type_constraint_name) || create_class_type_constraint($type_constraint_name) } -sub find_or_create_does_type_constraint ($) { +sub find_or_create_does_type_constraint { my $type_constraint_name = shift; find_or_parse_type_constraint($type_constraint_name) || create_role_type_constraint($type_constraint_name) } -sub find_or_parse_type_constraint ($) { - my $type_constraint_name = shift; +sub find_or_parse_type_constraint { + my $type_constraint_name = normalize_type_constraint_name(shift); my $constraint; if ($constraint = find_type_constraint($type_constraint_name)) { return $constraint; } elsif (_detect_type_constraint_union($type_constraint_name)) { $constraint = create_type_constraint_union($type_constraint_name); - } elsif (_detect_parameterized_type_constraint($type_constraint_name)) { + } elsif (_detect_parameterized_type_constraint($type_constraint_name)) { $constraint = create_parameterized_type_constraint($type_constraint_name); } else { return; } - + $REGISTRY->add_type_constraint($constraint); return $constraint; } +sub normalize_type_constraint_name { + my $type_constraint_name = shift; + $type_constraint_name =~ s/\s//g; + return $type_constraint_name; +} + +sub _confess { + my $error = shift; + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + Carp::confess($error); +} + ## -------------------------------------------------------- ## exported functions ... ## -------------------------------------------------------- -sub find_type_constraint ($) { +sub find_type_constraint { my $type = shift; if ( blessed $type and $type->isa("Moose::Meta::TypeConstraint") ) { @@ -245,35 +246,68 @@ sub find_type_constraint ($) { } } -sub register_type_constraint ($) { +sub register_type_constraint { my $constraint = shift; - Moose::throw_error("can't register an unnamed type constraint") unless defined $constraint->name; + __PACKAGE__->_throw_error("can't register an unnamed type constraint") unless defined $constraint->name; $REGISTRY->add_type_constraint($constraint); return $constraint; } # type constructors -sub type ($$;$$) { - splice(@_, 1, 0, undef); - goto &_create_type_constraint; +sub type { + # back-compat version, called without sugar + if ( ! any { ( reftype($_) || '' ) eq 'HASH' } @_ ) { + return _create_type_constraint( $_[0], undef, $_[1] ); + } + + my $name = shift; + + my %p = map { %{$_} } @_; + + return _create_type_constraint( $name, undef, $p{where}, $p{message}, $p{optimize_as} ); } -sub subtype ($$;$$$) { - # NOTE: - # this adds an undef for the name - # if this is an anon-subtype: - # subtype(Num => where { $_ % 2 == 0 }) # anon 'even' subtype - # but if the last arg is not a code - # ref then it is a subtype alias: - # subtype(MyNumbers => as Num); # now MyNumbers is the same as Num - # ... yeah I know it's ugly code - # - SL - unshift @_ => undef if scalar @_ <= 2 && ('CODE' eq ref($_[1])); - goto &_create_type_constraint; +sub subtype { + # crazy back-compat code for being called without sugar ... + # + # subtype 'Parent', sub { where }; + if ( scalar @_ == 2 && ( reftype( $_[1] ) || '' ) eq 'CODE' ) { + return _create_type_constraint( undef, @_ ); + } + + # subtype 'Parent', sub { where }, sub { message }; + # subtype 'Parent', sub { where }, sub { message }, sub { optimized }; + if ( scalar @_ >= 3 && all { ( reftype($_) || '' ) eq 'CODE' } + @_[ 1 .. $#_ ] ) { + return _create_type_constraint( undef, @_ ); + } + + # subtype 'Name', 'Parent', ... + if ( scalar @_ >= 2 && all { !ref } @_[ 0, 1 ] ) { + return _create_type_constraint(@_); + } + + if ( @_ == 1 && ! ref $_[0] ) { + __PACKAGE__->_throw_error('A subtype cannot consist solely of a name, it must have a parent'); + } + + # The blessed check is mostly to accommodate MooseX::Types, which + # uses an object which overloads stringification as a type name. + my $name = ref $_[0] && ! blessed $_[0] ? undef : shift; + + my %p = map { %{$_} } @_; + + # subtype Str => where { ... }; + if ( ! exists $p{as} ) { + $p{as} = $name; + $name = undef; + } + + return _create_type_constraint( $name, $p{as}, $p{where}, $p{message}, $p{optimize_as} ); } -sub class_type ($;$) { +sub class_type { register_type_constraint( create_class_type_constraint( $_[0], @@ -291,20 +325,41 @@ sub role_type ($;$) { ); } -sub coerce ($@) { +sub maybe_type { + my ($type_parameter) = @_; + + register_type_constraint( + $REGISTRY->get_type_constraint('Maybe')->parameterize($type_parameter) + ); +} + +sub coerce { my ($type_name, @coercion_map) = @_; _install_type_coercions($type_name, \@coercion_map); } -sub as ($) { $_[0] } -sub from ($) { $_[0] } -sub where (&) { $_[0] } -sub via (&) { $_[0] } - -sub message (&) { +{ message => $_[0] } } -sub optimize_as (&) { +{ optimized => $_[0] } } - -sub enum ($;@) { +# The trick of returning @_ lets us avoid having to specify a +# prototype. Perl will parse this: +# +# subtype 'Foo' +# => as 'Str' +# => where { ... } +# +# as this: +# +# subtype( 'Foo', as( 'Str', where { ... } ) ); +# +# If as() returns all it's extra arguments, this just works, and +# preserves backwards compatibility. +sub as { { as => shift }, @_ } +sub where (&) { { where => $_[0] } } +sub message (&) { { message => $_[0] } } +sub optimize_as (&) { { optimize_as => $_[0] } } + +sub from {@_} +sub via (&) { $_[0] } + +sub enum { my ($type_name, @values) = @_; # NOTE: # if only an array-ref is passed then @@ -315,7 +370,7 @@ sub enum ($;@) { $type_name = undef; } (scalar @values >= 2) - || Moose::throw_error("You must have at least two values to enumerate through"); + || __PACKAGE__->_throw_error("You must have at least two values to enumerate through"); my %valid = map { $_ => 1 } @values; register_type_constraint( @@ -326,7 +381,7 @@ sub enum ($;@) { ); } -sub create_enum_type_constraint ($$) { +sub create_enum_type_constraint { my ( $type_name, $values ) = @_; Moose::Meta::TypeConstraint::Enum->new( @@ -340,59 +395,48 @@ sub create_enum_type_constraint ($$) { ## -------------------------------------------------------- sub _create_type_constraint ($$$;$$) { - my $name = shift; - my $parent = shift; - my $check = shift; - - my ($message, $optimized); - for (@_) { - $message = $_->{message} if exists $_->{message}; - $optimized = $_->{optimized} if exists $_->{optimized}; - } + my $name = shift; + my $parent = shift; + my $check = shift; + my $message = shift; + my $optimized = shift; - my $pkg_defined_in = scalar(caller(0)); + my $pkg_defined_in = scalar( caller(1) ); - if (defined $name) { + if ( defined $name ) { my $type = $REGISTRY->get_type_constraint($name); - ($type->_package_defined_in eq $pkg_defined_in) - || confess ("The type constraint '$name' has already been created in " - . $type->_package_defined_in . " and cannot be created again in " - . $pkg_defined_in) - if defined $type; + ( $type->_package_defined_in eq $pkg_defined_in ) + || _confess( + "The type constraint '$name' has already been created in " + . $type->_package_defined_in + . " and cannot be created again in " + . $pkg_defined_in ) + if defined $type; + + $name =~ /^[\w:\.]+$/ + or die qq{$name contains invalid characters for a type name.} + . qq{ Names can contain alphanumeric character, ":", and "."\n}; } - my $class = "Moose::Meta::TypeConstraint"; - - # FIXME should probably not be a special case - if ( defined $parent and $parent = find_or_parse_type_constraint($parent) ) { - $class = "Moose::Meta::TypeConstraint::Parameterizable" - if $parent->isa("Moose::Meta::TypeConstraint::Parameterizable"); - } - - my $constraint = $class->new( - name => $name || '__ANON__', + my %opts = ( + name => $name, package_defined_in => $pkg_defined_in, - ($parent ? (parent => $parent ) : ()), - ($check ? (constraint => $check) : ()), - ($message ? (message => $message) : ()), - ($optimized ? (optimized => $optimized) : ()), + ( $check ? ( constraint => $check ) : () ), + ( $message ? ( message => $message ) : () ), + ( $optimized ? ( optimized => $optimized ) : () ), ); - # NOTE: - # if we have a type constraint union, and no - # type check, this means we are just aliasing - # the union constraint, which means we need to - # handle this differently. - # - SL - if (not(defined $check) - && $parent->isa('Moose::Meta::TypeConstraint::Union') - && $parent->has_coercion - ){ - $constraint->coercion(Moose::Meta::TypeCoercion::Union->new( - type_constraint => $parent - )); + my $constraint; + if ( defined $parent + and $parent + = blessed $parent ? $parent : find_or_create_isa_type_constraint($parent) ) + { + $constraint = $parent->create_child_type(%opts); + } + else { + $constraint = Moose::Meta::TypeConstraint->new(%opts); } $REGISTRY->add_type_constraint($constraint) @@ -405,7 +449,7 @@ sub _install_type_coercions ($$) { my ($type_name, $coercion_map) = @_; my $type = find_type_constraint($type_name); (defined $type) - || Moose::throw_error("Cannot find type '$type_name', perhaps you forgot to load it."); + || __PACKAGE__->_throw_error("Cannot find type '$type_name', perhaps you forgot to load it."); if ($type->has_coercion) { $type->coercion->add_type_coercions(@$coercion_map); } @@ -431,14 +475,14 @@ sub _install_type_coercions ($$) { use re "eval"; - my $valid_chars = qr{[\w:]}; + my $valid_chars = qr{[\w:\.]}; my $type_atom = qr{ $valid_chars+ }; my $any; - my $type = qr{ $valid_chars+ (?: \[ (??{$any}) \] )? }x; - my $type_capture_parts = qr{ ($valid_chars+) (?: \[ ((??{$any})) \] )? }x; - my $type_with_parameter = qr{ $valid_chars+ \[ (??{$any}) \] }x; + my $type = qr{ $valid_chars+ (?: \[ \s* (??{$any}) \s* \] )? }x; + my $type_capture_parts = qr{ ($valid_chars+) (?: \[ \s* ((??{$any})) \s* \] )? }x; + my $type_with_parameter = qr{ $valid_chars+ \[ \s* (??{$any}) \s* \] }x; my $op_union = qr{ \s* \| \s* }x; my $union = qr{ $type (?: $op_union $type )+ }x; @@ -464,7 +508,7 @@ sub _install_type_coercions ($$) { push @rv => $1; } (pos($given) eq length($given)) - || Moose::throw_error("'$given' didn't parse (parse-pos=" + || __PACKAGE__->_throw_error("'$given' didn't parse (parse-pos=" . pos($given) . " and str-length=" . length($given) @@ -482,6 +526,27 @@ sub _install_type_coercions ($$) { # define some basic built-in types ## -------------------------------------------------------- +# By making these classes immutable before creating all the types we +# below, we avoid repeatedly calling the slow MOP-based accessors. +$_->make_immutable( + inline_constructor => 1, + constructor_name => "_new", + + # these are Class::MOP accessors, so they need inlining + inline_accessors => 1 + ) for grep { $_->is_mutable } + map { $_->meta } + qw( + Moose::Meta::TypeConstraint + Moose::Meta::TypeConstraint::Union + Moose::Meta::TypeConstraint::Parameterized + Moose::Meta::TypeConstraint::Parameterizable + Moose::Meta::TypeConstraint::Class + Moose::Meta::TypeConstraint::Role + Moose::Meta::TypeConstraint::Enum + Moose::Meta::TypeConstraint::Registry +); + type 'Any' => where { 1 }; # meta-type including all type 'Item' => where { 1 }; # base-type @@ -542,14 +607,18 @@ subtype 'Role' => where { $_->can('does') } => optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::Role; -my $_class_name_checker = sub { -}; +my $_class_name_checker = sub {}; subtype 'ClassName' => as 'Str' => where { Class::MOP::is_class_loaded($_) } => optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::ClassName; +subtype 'RoleName' + => as 'ClassName' + => where { (($_->can('meta') || return)->($_) || return)->isa('Moose::Meta::Role') } + => optimize_as \&Moose::Util::TypeConstraints::OptimizedConstraints::RoleName; ; + ## -------------------------------------------------------- # parameterizable types ... @@ -616,7 +685,7 @@ sub get_all_parameterizable_types { @PARAMETERIZABLE_TYPES } sub add_parameterizable_type { my $type = shift; (blessed $type && $type->isa('Moose::Meta::TypeConstraint::Parameterizable')) - || Moose::throw_error("Type must be a Moose::Meta::TypeConstraint::Parameterizable not $type"); + || __PACKAGE__->_throw_error("Type must be a Moose::Meta::TypeConstraint::Parameterizable not $type"); push @PARAMETERIZABLE_TYPES => $type; } @@ -629,6 +698,13 @@ sub add_parameterizable_type { sub list_all_builtin_type_constraints { @BUILTINS } } +sub _throw_error { + shift; + require Moose; + unshift @_, 'Moose'; + goto &Moose::throw_error; +} + 1; __END__ @@ -663,7 +739,7 @@ Moose::Util::TypeConstraints - Type constraint system for Moose =head1 DESCRIPTION This module provides Moose with the ability to create custom type -contraints to be used in attribute definition. +constraints to be used in attribute definition. =head2 Important Caveat @@ -716,6 +792,7 @@ that hierarchy represented visually. Int Str ClassName + RoleName Ref ScalarRef ArrayRef[`a] @@ -725,34 +802,45 @@ that hierarchy represented visually. GlobRef FileHandle Object - Role + Role B Any type followed by a type parameter C<[`a]> can be parameterized, this means you can say: - ArrayRef[Int] # an array of intergers + ArrayRef[Int] # an array of integers HashRef[CodeRef] # a hash of str to CODE ref mappings Maybe[Str] # value may be a string, may be undefined +If Moose finds a name in brackets that it does not recognize as an +existing type, it assumes that this is a class name, for example +C. + B Unless you parameterize a type, then it is invalid to include the square brackets. I.e. C will be literally interpreted as a type name. B The C type constraint for the most part works correctly now, but edge cases may still exist, please use it -sparringly. +sparingly. B The C type constraint does a complex package existence check. This means that your class B be loaded for this type constraint to pass. I know this is not ideal for all, but it is a saner restriction than most others. +B The C constraint checks a string is I +which is a role, like C<'MyApp::Role::Comparable'>. The C +constraint checks that an I does the named role. + =head2 Type Constraint Naming +Type name declared via this module can only contain alphanumeric +characters, colons (:), and periods (.). + Since the types created by this module are global, it is suggested that you namespace your types just as you would namespace your modules. So instead of creating a I type for your B -module, you would call the type I instead. +module, you would call the type I instead. =head2 Use with Other Constraint Modules @@ -766,10 +854,13 @@ them to work with Moose. For instance, this is how you could use it with L to declare a completely new type. - type 'HashOfArrayOfObjects' - => IsHashRef( + type 'HashOfArrayOfObjects', + { + where => IsHashRef( -keys => HasLength, - -values => IsArrayRef( IsObject )); + -values => IsArrayRef(IsObject) + ) + }; For more examples see the F test file. @@ -801,29 +892,60 @@ See the L for an example of how to use these. =over 4 -=item B +=item B where { } ... > This creates a base type, which has no parent. -=item B +The C function should either be called with the sugar helpers +(C, C, etc), or with a name and a hashref of +parameters: + + type( 'Foo', { where => ..., message => ... } ); + +The valid hashref keys are C, C, and C. + +=item B as 'Parent' => where { } ...> This creates a named subtype. -=item B +If you provide a parent that Moose does not recognize, it will +automatically create a new class type constraint for this name. + +When creating a named type, the C function should either be +called with the sugar helpers (C, C, etc), or with a +name and a hashref of parameters: + + subtype( 'Foo', { where => ..., message => ... } ); + +The valid hashref keys are C (the parent), C, C, +and C. + +=item B where { } ...> This creates an unnamed subtype and will return the type constraint meta-object, which will be an instance of L. +When creating an anonymous type, the C function should either +be called with the sugar helpers (C, C, etc), or with +just a hashref of parameters: + + subtype( { where => ..., message => ... } ); + =item B -Creates a type constraint with the name C<$class> and the metaclass -L. +Creates a new subtype of C with the name C<$class> and the +metaclass L. =item B -Creates a type constraint with the name C<$role> and the metaclass -L. +Creates a C type constraint with the name C<$role> and the +metaclass L. + +=item B + +Creates a type constraint for either C or something of the +given type. =item B @@ -832,8 +954,8 @@ The resulting constraint will be a subtype of C and will match any of the items in C<@values>. It is case sensitive. See the L for a simple example. -B This is not a true proper enum type, it is simple -a convient constraint builder. +B This is not a true proper enum type, it is simply +a convenient constraint builder. =item B @@ -872,7 +994,7 @@ exception thrown. This can be used to define a "hand optimized" version of your type constraint which can be used to avoid traversing a subtype -constraint heirarchy. +constraint hierarchy. B You should only use this if you know what you are doing, all the built in types use this, so your subtypes (assuming they @@ -908,9 +1030,14 @@ This is just sugar for the type coercion construction syntax. =over 4 -=item B +=item B + +Given a string that is expected to match a type constraint, will normalize the +string so that extra whitespace and newlines are removed. + +=item B -Given string with C<$pipe_seperated_types> or a list of C<@type_constraint_names>, +Given string with C<$pipe_separated_types> or a list of C<@type_constraint_names>, this will return a L instance. =item B @@ -955,7 +1082,7 @@ C. =item B -Attempts to parse the type name using L and if +Attempts to parse the type name using C and if no appropriate constraint is found will create a new anonymous one. The C variant will use C and the C @@ -1004,17 +1131,6 @@ Adds C<$type> to the list of parameterizable types =back -=head1 Error Management - -=over 4 - -=item B - -If the caller is a Moose metaclass, use its L -routine, otherwise use L. - -=back - =head2 Namespace Management =over 4 @@ -1038,7 +1154,7 @@ Stevan Little Estevan@iinteractive.comE =head1 COPYRIGHT AND LICENSE -Copyright 2006-2008 by Infinity Interactive, Inc. +Copyright 2006-2009 by Infinity Interactive, Inc. L