smerged changes from with_proxy_subs branch
John Napiorkowski [Thu, 23 Oct 2008 20:56:15 +0000 (20:56 +0000)]
Changes
Makefile.PL
lib/MooseX/Types.pm
lib/MooseX/Types/TypeDecorator.pm [new file with mode: 0644]
t/13_typedecorator.t [new file with mode: 0644]
t/lib/DecoratorLibrary.pm [new file with mode: 0644]

diff --git a/Changes b/Changes
index a2b51c1..89eec4b 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,9 @@
 
-0.05    ...
+0.06    Fri Sep  25 12:00:00 EST 2008
+        - Added support for parameterized types and type unions, tests for all
+        that and documentation updates.
+        
+0.05    [Indetermined]
         - moved export mechanism to Sub::Exporter. ::Base contains
           a bunch of wrapping logic to allow the export-along functionality
           for the helper symbols
index e92285d..51c3b3b 100644 (file)
@@ -9,21 +9,19 @@ license         q{perl};
 author          q{Robert 'phaylon' Sedlacek <rs@474.at>};
 all_from        q{lib/MooseX/Types.pm};
 
-build_requires  q{Test::More},                  0.62;
+build_requires  q{Test::More},                  0.80;
 build_requires  q{FindBin},                     0;
 
-requires        q{Moose},                       0.24;
-requires        q{Sub::Install},                0.922;
-requires        q{namespace::clean},            0.04;
+requires        q{Moose},                       0.60;
+requires        q{Sub::Install},                0.924;
+requires        q{namespace::clean},            0.08;
 requires        q{Carp},                        0;
-requires        q{Carp::Clan},                  0;
-requires        q{Class::MOP},                  0;
+requires        q{Carp::Clan},                  6.00;
+requires        q{Scalar::Util},                1.19;
 
 system 'pod2text lib/MooseX/Types.pm > README'
     if -e 'MANIFEST.SKIP';
 
 auto_provides;
-
 auto_install;
-
 WriteAll;
index f97ff02..0791eda 100644 (file)
@@ -11,6 +11,7 @@ MooseX::Types - Organise your Moose types in libraries
 #use strict;
 
 use Moose::Util::TypeConstraints;
+use MooseX::Types::TypeDecorator;
 use MooseX::Types::Base             ();
 use MooseX::Types::Util             qw( filter_tags );
 use MooseX::Types::UndefinedType;
@@ -18,8 +19,7 @@ use Carp::Clan                      qw( ^MooseX::Types );
 
 use namespace::clean -except => [qw( meta )];
 
-our $VERSION = 0.05;
-
+our $VERSION = 0.06;
 my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
 
 =head1 SYNOPSIS
@@ -30,7 +30,11 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
 
   # predeclare our own types
   use MooseX::Types 
-      -declare => [qw( PositiveInt NegativeInt )];
+    -declare => [qw(
+        PositiveInt NegativeInt
+        ArrayRefOfPositiveInt ArrayRefOfAtLeastThreeNegativeInts
+        LotsOfInnerConstraints StrOrArrayRef
+    )];
 
   # import builtin types
   use MooseX::Types::Moose 'Int';
@@ -51,6 +55,23 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
       from Int,
           via { 1 };
 
+  # with parameterized constraints.
+  
+  subtype ArrayRefOfPositiveInt,
+    as ArrayRef[PositiveInt];
+    
+  subtype ArrayRefOfAtLeastThreeNegativeInts,
+    as ArrayRef[NegativeInt],
+    where { scalar(@$_) > 2 };
+
+  subtype LotsOfInnerConstraints,
+    as ArrayRef[ArrayRef[HashRef[Int]]];
+    
+  # with TypeConstraint Unions
+  
+  subtype StrOrArrayRef,
+    as Str|ArrayRef;
+
   1;
 
 =head2 Usage
@@ -241,8 +262,24 @@ The fully qualified name of this type as L<Moose> knows it.
 A message that will be thrown when type functionality is used but the
 type does not yet exist.
 
-=back
+=head1 NOTES REGARDING TYPE UNIONS
 
+L<MooseX::Types> uses L<MooseX::Types::TypeDecorator> to do some overloading
+which generally allows you to easily create union types:
+
+  subtype StrOrArrayRef,
+    as Str|ArrayRef;    
+
+As with parameterized constrains, this overloading extends to modules using the
+types you define in a type library.
+
+    use Moose;
+    use MooseX::Types::Moose qw(HashRef Int);
+    
+    has 'attr' => (isa=>HashRef|Int);
+
+And everything should just work as you'd think.
+    
 =head1 METHODS
 
 =head2 import
@@ -300,13 +337,78 @@ yet defined.
 =cut
 
 sub type_export_generator {
-    my ($class, $type, $full) = @_;
-    return sub { 
-        return find_type_constraint($full)
-            || MooseX::Types::UndefinedType->new($full);
+    my ($class, $type, $name) = @_;
+    
+    ## Return an anonymous subroutine that will generate the proxied type
+    ## constraint for you.
+    
+    return sub {
+        my $type_constraint;
+        if(defined(my $params = shift @_)) {
+            ## We currently only allow a TC to accept a single, ArrayRef
+            ## parameter, as in HashRef[Int], where [Int] is what's inside the
+            ## ArrayRef passed.
+            if(ref $params eq 'ARRAY') {
+                $type_constraint = $class->create_arged_type_constraint($name, @$params);
+            } else {
+                croak 'Arguments must be an ArrayRef, not '. ref $params;
+            }
+        } else {
+            $type_constraint = $class->create_base_type_constraint($name);
+        }
+        $type_constraint = defined($type_constraint) ? $type_constraint
+         : MooseX::Types::UndefinedType->new($name);
+         
+        my $type_decorator = $class->create_type_decorator($type_constraint);
+        
+        ## If there are additional args, that means it's probably stuff that
+        ## needs to be returned to the subtype.  Not an ideal solution here but
+        ## doesn't seem to cause trouble.
+        
+        if(@_) {
+            return ($type_decorator, @_);
+        } else {
+            return $type_decorator;
+        }
     };
 }
 
+=head2 create_arged_type_constraint ($name, @args)
+
+Given a String $name with @args find the matching typeconstraint and parameterize
+it with @args.
+
+=cut
+
+sub create_arged_type_constraint {
+    my ($class, $name, @args) = @_;  
+    my $type_constraint = Moose::Util::TypeConstraints::find_or_create_type_constraint("$name");
+       return $type_constraint->parameterize(@args);
+}
+
+=head2 create_base_type_constraint ($name)
+
+Given a String $name, find the matching typeconstraint.
+
+=cut
+
+sub create_base_type_constraint {
+    my ($class, $name) = @_;
+    return find_type_constraint($name);
+}
+
+=head2 create_type_decorator ($type_constraint)
+
+Given a $type_constraint, return a lightweight L<MooseX::Types::TypeDecorator>
+instance.
+
+=cut
+
+sub create_type_decorator {
+    my ($class, $type_constraint) = @_;
+    return MooseX::Types::TypeDecorator->new($type_constraint);
+}
+
 =head2 coercion_export_generator
 
 This generates a coercion handler function, e.g. C<to_Int($value)>. 
@@ -349,11 +451,36 @@ sub check_export_generator {
 
 =head1 CAVEATS
 
+The following are lists of gotcha's and their workarounds for developers coming
+from the standard string based type constraint names
+
+=head2 Uniqueness
+
 A library makes the types quasi-unique by prefixing their names with (by
 default) the library package name. If you're only using the type handler
 functions provided by MooseX::Types, you shouldn't ever have to use
 a type's actual full name.
 
+=head2 Argument separation ('=>' versus ',')
+
+The Perlop manpage has this to say about the '=>' operator: "The => operator is
+a synonym for the comma, but forces any word (consisting entirely of word
+characters) to its left to be interpreted as a string (as of 5.001). This
+includes words that might otherwise be considered a constant or function call."
+
+Due to this stringification, the following will NOT work as you might think:
+
+  subtype StrOrArrayRef => as Str|ArrayRef;
+  
+The 'StrOrArrayRef' will have it's stringification activated this causes the
+subtype to not be created.  Since the bareword type constraints are not strings
+you really should not try to treat them that way.  You will have to use the ','
+operator instead.  The author's of this package realize that all the L<Moose>
+documention and examples nearly uniformly use the '=>' version of the comma
+operator and this could be an issue if you are converting code.
+
+Patches welcome for discussion.
+    
 =head1 SEE ALSO
 
 L<Moose>, 
@@ -366,6 +493,8 @@ L<Sub::Exporter>
 Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>, with many thanks to
 the C<#moose> cabal on C<irc.perl.org>.
 
+Additional features by John Napiorkowski (jnapiorkowski) <jjnapiork@cpan.org>.
+
 =head1 LICENSE
 
 This program is free software; you can redistribute it and/or modify
diff --git a/lib/MooseX/Types/TypeDecorator.pm b/lib/MooseX/Types/TypeDecorator.pm
new file mode 100644 (file)
index 0000000..31dd3fd
--- /dev/null
@@ -0,0 +1,153 @@
+package MooseX::Types::TypeDecorator;
+
+use strict;
+use warnings;
+
+use Carp::Clan qw( ^MooseX::Types );
+use Moose::Util::TypeConstraints ();
+use Moose::Meta::TypeConstraint::Union;
+use Scalar::Util qw(blessed);
+
+use overload(
+    '""' => sub {
+        return shift->__type_constraint->name; 
+    },
+    '|' => sub {
+        
+        ## It's kind of ugly that we need to know about Union Types, but this
+        ## is needed for syntax compatibility.  Maybe someday we'll all just do
+        ## Or[Str,Str,Int]
+        
+        my @tc = grep {blessed $_} @_;
+        my $union = Moose::Meta::TypeConstraint::Union->new(type_constraints=>\@tc);
+        return Moose::Util::TypeConstraints::register_type_constraint($union);
+    },
+    fallback => 1,
+    
+);
+
+
+=head1 NAME
+
+MooseX::Types::TypeDecorator - More flexible access to a Type Constraint
+
+=head1 DESCRIPTION
+
+This is a decorator object that contains an underlying type constraint.  We use
+this to control access to the type constraint and to add some features.
+
+=head1 METHODS
+
+This class defines the following methods.
+
+=head2 new
+
+Old school instantiation
+
+=cut
+
+sub new {
+    my $class = shift @_;
+    if(my $arg = shift @_) {
+        if(blessed $arg && $arg->isa('Moose::Meta::TypeConstraint')) {
+            return bless {'__type_constraint'=>$arg}, $class;
+        } elsif(blessed $arg && $arg->isa('MooseX::Types::UndefinedType')) {
+            ## stub in case we'll need to handle these types differently
+            return bless {'__type_constraint'=>$arg}, $class;
+        } elsif(blessed $arg) {
+            croak "Argument must be ->isa('Moose::Meta::TypeConstraint') or ->isa('MooseX::Types::UndefinedType'), not ". blessed $arg;
+        } else {
+            croak "Argument cannot be '$arg'";
+        }
+    } else {
+        croak "This method [new] requires a single argument of 'arg'.";        
+    }
+}
+
+=head __type_constraint ($type_constraint)
+
+Set/Get the type_constraint.
+
+=cut
+
+sub __type_constraint {
+    my $self = shift @_;
+    
+    if(blessed $self) {
+        if(defined(my $tc = shift @_)) {
+            $self->{__type_constraint} = $tc;
+        }
+        return $self->{__type_constraint};        
+    } else {
+        croak 'cannot call __type_constraint as a class method';
+    }
+}
+
+=head2 isa
+
+handle $self->isa since AUTOLOAD can't.
+
+=cut
+
+sub isa {
+    my ($self, $target) = @_;  
+    if(defined $target) {
+        return $self->__type_constraint->isa($target);
+    } else {
+        return;
+    }
+}
+
+=head2 can
+
+handle $self->can since AUTOLOAD can't.
+
+=cut
+
+sub can {
+    my ($self, $target) = @_;
+    if(defined $target) {
+        return $self->__type_constraint->can($target);
+    } else {
+        return;
+    }
+}
+
+=head2 DESTROY
+
+We might need it later
+
+=cut
+
+sub DESTROY {
+    return;
+}
+
+=head2 AUTOLOAD
+
+Delegate to the decorator targe
+
+=cut
+
+sub AUTOLOAD {
+    my ($self, @args) = @_;
+    my ($method) = (our $AUTOLOAD =~ /([^:]+)$/);
+    if($self->__type_constraint->can($method)) {
+        return $self->__type_constraint->$method(@args);
+    } else {
+        croak "Method '$method' is not supported";   
+    }
+}
+
+=head1 AUTHOR AND COPYRIGHT
+
+John Napiorkowski (jnapiorkowski) <jjnapiork@cpan.org>
+
+=head1 LICENSE
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as perl itself.
+
+=cut
+
+1;
diff --git a/t/13_typedecorator.t b/t/13_typedecorator.t
new file mode 100644 (file)
index 0000000..3f250e6
--- /dev/null
@@ -0,0 +1,215 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More tests => 49;
+use Test::Exception;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+
+{
+    package Test::MooseX::TypeLibrary::TypeDecorator;
+    
+    use Moose;
+    use MooseX::Types::Moose qw(
+        Int Str ArrayRef HashRef Object
+    );
+    use DecoratorLibrary qw(
+        MyArrayRefBase MyArrayRefInt01 MyArrayRefInt02 StrOrArrayRef
+        AtLeastOneInt Jobs
+    );
+    
+    has 'arrayrefbase' => (is=>'rw', isa=>MyArrayRefBase, coerce=>1);
+    has 'arrayrefint01' => (is=>'rw', isa=>MyArrayRefInt01, coerce=>1);
+    has 'arrayrefint02' => (is=>'rw', isa=>MyArrayRefInt02, coerce=>1);
+    has 'arrayrefint03' => (is=>'rw', isa=>MyArrayRefBase[Int]);
+    has 'StrOrArrayRef' => (is=>'rw', isa=>StrOrArrayRef);
+    has 'AtLeastOneInt' => (is=>'rw', isa=>AtLeastOneInt);
+    has 'pipeoverloading' => (is=>'rw', isa=>Int|Str);   
+    has 'deep' => (is=>'rw', isa=>ArrayRef[ArrayRef[HashRef[Int]]] );
+    has 'deep2' => (is=>'rw', isa=>ArrayRef[Int|ArrayRef[HashRef[Int|Object]]] );
+    has 'enum' => (is=>'rw', isa=>Jobs);
+}
+
+## Make sure we have a 'create object sanity check'
+
+ok my $type = Test::MooseX::TypeLibrary::TypeDecorator->new(),
+ => 'Created some sort of object';
+isa_ok $type, 'Test::MooseX::TypeLibrary::TypeDecorator'
+ => "Yes, it's the correct kind of object";
+
+## test arrayrefbase normal and coercion
+
+ok $type->arrayrefbase([qw(a b c d e)])
+ => 'Assigned arrayrefbase qw(a b c d e)';
+is_deeply $type->arrayrefbase, [qw(a b c d e)],
+ => 'Assignment is correct';
+
+ok $type->arrayrefbase('d,e,f')
+ => 'Assignment arrayrefbase d,e,f to test coercion';
+is_deeply $type->arrayrefbase, [qw(d e f)],
+ => 'Assignment and coercion is correct';
+
+## test arrayrefint01 normal and coercion
+
+ok $type->arrayrefint01([qw(1 2 3)])
+ => 'Assignment arrayrefint01 qw(1 2 3)';
+is_deeply $type->arrayrefint01, [qw(1 2 3)],
+ => 'Assignment is correct';
+
+ok $type->arrayrefint01('4.5.6')
+ => 'Assigned arrayrefint01 4.5.6 to test coercion from Str';
+is_deeply $type->arrayrefint01, [qw(4 5 6)],
+ => 'Assignment and coercion is correct';
+
+ok $type->arrayrefint01({a=>7,b=>8})
+ => 'Assigned arrayrefint01 {a=>7,b=>8} to test coercion from HashRef';
+is_deeply $type->arrayrefint01, [qw(7 8)],
+ => 'Assignment and coercion is correct';
+throws_ok sub {
+    $type->arrayrefint01([qw(a b c)])
+}, qr/Attribute \(arrayrefint01\) does not pass the type constraint/ => 'Dies when values are strings';
+
+## test arrayrefint02 normal and coercion
+
+ok $type->arrayrefint02([qw(1 2 3)])
+ => 'Assigned arrayrefint02 qw(1 2 3)';
+is_deeply $type->arrayrefint02, [qw(1 2 3)],
+ => 'Assignment is correct';
+
+ok $type->arrayrefint02('4:5:6')
+ => 'Assigned arrayrefint02 4:5:6 to test coercion from Str';
+is_deeply $type->arrayrefint02, [qw(4 5 6)],
+ => 'Assignment and coercion is correct';
+
+ok $type->arrayrefint02({a=>7,b=>8})
+ => 'Assigned arrayrefint02 {a=>7,b=>8} to test coercion from HashRef';
+is_deeply $type->arrayrefint02, [qw(7 8)],
+ => 'Assignment and coercion is correct';
+ok $type->arrayrefint02({a=>'AA',b=>'BBB', c=>'CCCCCCC'})
+ => "Assigned arrayrefint02 {a=>'AA',b=>'BBB', c=>'CCCCCCC'} to test coercion from HashRef";
+is_deeply $type->arrayrefint02, [qw(2 3 7)],
+ => 'Assignment and coercion is correct';
+
+ok $type->arrayrefint02({a=>[1,2],b=>[3,4]})
+ => "Assigned arrayrefint02 {a=>[1,2],b=>[3,4]} to test coercion from HashRef";
+is_deeply $type->arrayrefint02, [qw(1 2 3 4)],
+ => 'Assignment and coercion is correct';
+# test arrayrefint03 
+
+ok $type->arrayrefint03([qw(11 12 13)])
+ => 'Assigned arrayrefint01 qw(11 12 13)';
+is_deeply $type->arrayrefint03, [qw(11 12 13)],
+ => 'Assignment is correct';
+throws_ok sub {
+    $type->arrayrefint03([qw(a b c)])
+}, qr/Attribute \(arrayrefint03\) does not pass the type constraint/ => 'Dies when values are strings';
+
+# TEST StrOrArrayRef
+
+ok $type->StrOrArrayRef('string')
+ => 'String part of union is good';
+
+ok $type->StrOrArrayRef([1,2,3])
+ => 'arrayref part of union is good';
+throws_ok sub {
+    $type->StrOrArrayRef({a=>111});
+}, qr/Attribute \(StrOrArrayRef\) does not pass the type constraint/ => 'Correctly failed to use a hashref';
+
+# Test AtLeastOneInt
+
+ok $type->AtLeastOneInt([1,2]),
+ => 'Good assignment';
+
+is_deeply $type->AtLeastOneInt, [1,2]
+ => "Got expected values.";
+throws_ok sub {
+    $type->AtLeastOneInt([]);
+}, qr/Attribute \(AtLeastOneInt\) does not pass the type constraint/ => 'properly fails to assign as []';
+
+throws_ok sub {
+    $type->AtLeastOneInt(['a','b']);
+}, qr/Attribute \(AtLeastOneInt\) does not pass the type constraint/ => 'properly fails arrayref of strings';
+
+## Test pipeoverloading
+
+ok $type->pipeoverloading(1)
+ => 'Integer for union test accepted';
+ok $type->pipeoverloading('a')
+ => 'String for union test accepted';
+
+throws_ok sub {
+    $type->pipeoverloading({a=>1,b=>2});
+}, qr/Validation failed for 'Int|Str'/ => 'Union test corrected fails a HashRef';
+
+## test deep (ArrayRef[ArrayRef[HashRef[Int]]])
+
+ok $type->deep([[{a=>1,b=>2},{c=>3,d=>4}],[{e=>5}]])
+ => 'Assigned deep to [[{a=>1,b=>2},{c=>3,d=>4}],[{e=>5}]]';
+
+is_deeply $type->deep, [[{a=>1,b=>2},{c=>3,d=>4}],[{e=>5}]],
+ => 'Assignment is correct';
+throws_ok sub {
+    $type->deep({a=>1,b=>2});
+}, qr/Attribute \(deep\) does not pass the type constraint/ => 'Deep Constraints properly fail';
+
+# test deep2 (ArrayRef[Int|ArrayRef[HashRef[Int|Object]]])
+
+ok $type->deep2([[{a=>1,b=>2},{c=>3,d=>4}],[{e=>5}]])
+ => 'Assigned deep2 to [[{a=>1,b=>2},{c=>3,d=>4}],[{e=>5}]]';
+
+is_deeply $type->deep2, [[{a=>1,b=>2},{c=>3,d=>4}],[{e=>5}]],
+ => 'Assignment is correct';
+throws_ok sub {
+    $type->deep2({a=>1,b=>2});
+}, qr/Attribute \(deep2\) does not pass the type constraint/ => 'Deep Constraints properly fail';
+
+throws_ok sub {
+    $type->deep2([[{a=>1,b=>2},{c=>3,d=>'noway'}],[{e=>5}]]);
+}, qr/Attribute \(deep2\) does not pass the type constraint/ => 'Deep Constraints properly fail';
+
+
+ok $type->deep2([[{a=>1,b=>2},{c=>3,d=>$type}],[{e=>5}]])
+ => 'Assigned deep2 to [[{a=>1,b=>2},{c=>3,d=>$type}],[{e=>5}]]';
+
+
+is_deeply $type->deep2, [[{a=>1,b=>2},{c=>3,d=>$type}],[{e=>5}]],
+ => 'Assignment is correct';
+ok $type->deep2([1,2,3])
+ => 'Assigned deep2 to [1,2,3]';
+
+
+is_deeply $type->deep2, [1,2,3],
+ => 'Assignment is correct';
+## Test jobs
+
+ok $type->enum('Programming')
+ => 'Good Assignment of Programming to Enum';
+
+
+throws_ok sub {
+    $type->enum('ddddd');
+}, qr/Attribute \(enum\) does not pass the type constraint/ => 'Enum properly fails';
diff --git a/t/lib/DecoratorLibrary.pm b/t/lib/DecoratorLibrary.pm
new file mode 100644 (file)
index 0000000..880b456
--- /dev/null
@@ -0,0 +1,76 @@
+package DecoratorLibrary;
+
+use MooseX::Types::Moose qw( Str ArrayRef HashRef Int );
+use MooseX::Types
+    -declare => [qw(
+        MyArrayRefBase
+        MyArrayRefInt01
+        MyArrayRefInt02
+        MyHashRefOfInts
+        MyHashRefOfStr
+        StrOrArrayRef
+        AtLeastOneInt
+        Jobs
+    )];
+
+## Some questionable messing around
+    sub my_subtype {
+        my ($subtype, $basetype, @rest) = @_;
+        return subtype($subtype, $basetype, shift @rest, shift @rest);
+    }
+    
+    sub my_from {
+        return @_;
+        
+    }
+    sub my_as {
+        return @_;
+    }
+## End
+
+subtype MyArrayRefBase,
+    as ArrayRef;
+    
+coerce MyArrayRefBase,
+    from Str,
+    via {[split(',', $_)]};
+    
+subtype MyArrayRefInt01,
+    as ArrayRef[Int];
+
+coerce MyArrayRefInt01,
+    from Str,
+    via {[split('\.',$_)]},
+    from HashRef,
+    via {[sort values(%$_)]};
+    
+subtype MyArrayRefInt02,
+    as MyArrayRefBase[Int];
+    
+subtype MyHashRefOfInts,
+    as HashRef[Int];
+    
+subtype MyHashRefOfStr,
+    as HashRef[Str];
+
+coerce MyArrayRefInt02,
+    from Str,
+    via {[split(':',$_)]},
+    from MyHashRefOfInts,
+    via {[sort values(%$_)]},
+    from MyHashRefOfStr,
+    via {[ sort map { length $_ } values(%$_) ]},
+    from HashRef[ArrayRef],
+    via {[ sort map { @$_ } values(%$_) ]};
+
+subtype StrOrArrayRef,
+    as Str|ArrayRef;
+
+subtype AtLeastOneInt,
+    as ArrayRef[Int],
+    where { @$_ > 0 };
+    
+enum Jobs,
+    (qw/Programming Teaching Banking/);
+    
+1;