Added wrapper functionality
phaylon [Thu, 12 Apr 2007 20:51:36 +0000 (20:51 +0000)]
lib/MooseX/TypeLibrary.pm
lib/MooseX/TypeLibrary/Base.pm
lib/MooseX/TypeLibrary/Wrapper.pm [new file with mode: 0644]
t/12_wrapper-definition.t [new file with mode: 0644]
t/lib/TestWrapper.pm [new file with mode: 0644]

index 4e50dbb..4489bc3 100644 (file)
@@ -11,7 +11,7 @@ use strict;
 
 use Sub::Uplevel;
 use Moose::Util::TypeConstraints;
-use MooseX::TypeLibrary::Base;
+use MooseX::TypeLibrary::Base           ();
 use MooseX::TypeLibrary::Util           qw( filter_tags );
 use MooseX::TypeLibrary::UndefinedType;
 use Sub::Install                        qw( install_sub );
@@ -157,6 +157,82 @@ you want all of them, use the C<:all> tag. For example:
 MooseX::TypeLibrary comes with a library of Moose' built-in types called
 L<MooseX::TypeLibrary::Moose>.
 
+=head1 WRAPPING A LIBRARY
+
+You can define your own wrapper subclasses to manipulate the behaviour
+of a set of library exports. Here is an example:
+
+  package MyWrapper;
+  use strict;
+  use Class::C3;
+  use base 'MooseX::TypeLibrary::Wrapper';
+
+  sub coercion_export_generator {
+      my $class = shift;
+      my $code = $class->next::method(@_);
+      return sub {
+          my $value = $code->(@_);
+          warn "Coercion returned undef!"
+              unless defined $value;
+          return $value;
+      };
+  }
+
+  1;
+
+This class wraps the coercion generator (e.g., C<to_Int()>) and warns
+if a coercion returned an undefined value. You can wrap any library
+with this:
+
+  package Foo;
+  use strict;
+  use MyWrapper MyLibrary => [qw( Foo Bar )],
+                Moose     => [qw( Str Int )];
+
+  ...
+  1;
+
+The C<Moose> library name is a special shortcut for 
+L<MooseX::TypeLibrary::Moose>.
+
+=head2 Generator methods you can overload
+
+=over 4
+
+=item type_export_generator( $short, $full )
+
+Creates a closure returning the type's L<Moose::Meta::TypeConstraint> 
+object. 
+
+=item check_export_generator( $short, $full, $undef_message )
+
+This creates the closure used to test if a value is valid for this type.
+
+=item coercion_export_generator( $short, $full, $undef_message )
+
+This is the closure that's doing coercions.
+
+=back
+
+=head2 Provided Parameters
+
+=over 4
+
+=item $short
+
+The short, exported name of the type.
+
+=item $full
+
+The fully qualified name of this type as L<Moose> knows it.
+
+=item $undef_message
+
+A message that will be thrown when type functionality is used but the
+type does not yet exist.
+
+=back
+
 =head1 METHODS
 
 =head2 import
@@ -193,11 +269,7 @@ sub import {
     }
 
     # run type constraints import
-    return Moose::Util::TypeConstraints
-        ->import({ into => $callee });
-#    return uplevel 1, 
-#        Moose::Util::TypeConstraints->can('import'), 
-#        'Moose::Util::TypeConstraints';
+    return Moose::Util::TypeConstraints->import({ into => $callee });
 }
 
 =head2 type_export_generator
index da140b5..fcb1aa6 100644 (file)
@@ -37,10 +37,15 @@ L<MooseX::TypeLibrary/"LIBRARY USAGE"> for syntax details on this.
 =cut
 
 sub import {
-    my ($class, @orig_types) = @_;
+    my ($class, @args) = @_;
 
-    # separate tags from types
-    my ($tags, $types) = filter_tags @orig_types;
+    # separate tags from types and possible options
+    my ($options) = grep { ref $_ eq 'HASH' } @args;
+    my ($tags, $types) 
+      = filter_tags
+        grep { ref $_ ne 'HASH' }
+        @args;
+    my $callee = ($options && $options->{ -into } || scalar(caller));
 
     # :all replaces types with full list
     @$types = $class->type_names if $tags->{all};
@@ -49,9 +54,10 @@ sub import {
     # export all requested types
     for my $type (@$types) {
         $class->export_type_into(
-            scalar(caller), 
+            $callee, 
             $type, 
             sprintf($UndefMsg, $type, $class),
+            ($options ? %$options : ()),
         );
     }
     return 1;
@@ -69,19 +75,20 @@ sub export_type_into {
     # the real type name and its type object
     my $full = $class->get_type($type);
     my $tobj = find_type_constraint($full);
-    ### Exporting: $full
+
+    # a possible wrapper around library functionality
+    my $wrap = $args{ -wrapper } || 'MooseX::TypeLibrary';
 
     # install Type name constant
     install_sub({
-        code => MooseX::TypeLibrary->type_export_generator($type, $full),
+        code => $wrap->type_export_generator($type, $full),
         into => $target,
         as   => $type,
     });
 
     # install is_Type test function
     install_sub({
-        code => MooseX::TypeLibrary
-                    ->check_export_generator($type, $full, $undef_msg),
+        code => $wrap->check_export_generator($type, $full, $undef_msg),
         into => $target,
         as   => "is_$type",
     });
@@ -92,8 +99,7 @@ sub export_type_into {
     
         # install to_Type coercion handler
         install_sub({
-            code => MooseX::TypeLibrary->coercion_export_generator(
-                        $type, $full, $undef_msg ),
+            code => $wrap->coercion_export_generator($type, $full, $undef_msg),
             into => $target,
             as   => "to_$type",
         });
diff --git a/lib/MooseX/TypeLibrary/Wrapper.pm b/lib/MooseX/TypeLibrary/Wrapper.pm
new file mode 100644 (file)
index 0000000..3e4e387
--- /dev/null
@@ -0,0 +1,32 @@
+package MooseX::TypeLibrary::Wrapper;
+use warnings;
+use strict;
+use base 'MooseX::TypeLibrary';
+
+use Carp    qw( croak );
+use Class::Inspector;
+use namespace::clean;
+
+sub import {
+    my ($class, @args) = @_;
+    my %libraries = @args == 1 ? (Moose => $args[0]) : @args;
+
+    for my $l (keys %libraries) {
+
+        croak qq($class expects an array reference as import spec)
+            unless ref $libraries{ $l } eq 'ARRAY';
+
+        my $library_class 
+          = ($l eq 'Moose' ? 'MooseX::TypeLibrary::Moose' : $l );
+        require Class::Inspector->filename($library_class)
+            unless Class::Inspector->loaded($library_class);
+
+        $library_class->import( 
+            @{ $libraries{ $l } }, 
+            { -into => scalar(caller) } 
+        );
+    }
+    return 1;
+}
+
+1;
diff --git a/t/12_wrapper-definition.t b/t/12_wrapper-definition.t
new file mode 100644 (file)
index 0000000..2735246
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use TestWrapper TestLibrary => [qw( NonEmptyStr IntArrayRef )],
+                Moose       => [qw( Str Int )];
+
+my @tests = (
+    [ 'NonEmptyStr', 12, "12", [], "foobar", "" ],
+    [ 'IntArrayRef', 12, [12], {}, [17, 23], {} ],
+);
+
+plan tests => (@tests * 8);
+
+# new array ref so we can safely shift from it
+for my $data (map { [@$_] } @tests) {
+    my $type = shift @$data;
+
+    # Type name export
+    {
+        ok my $code = __PACKAGE__->can($type), "$type() was exported";
+        is $code->(), "TestLibrary::$type", "$type() returned correct type name";
+    }
+
+    # coercion handler export
+    {   
+        my ($coerce, $coercion_result, $cannot_coerce) = map { shift @$data } 1 .. 3;
+        ok my $code = __PACKAGE__->can("to_$type"), "to_$type() coercion was exported";
+        is_deeply scalar $code->($coerce), $coercion_result, "to_$type() coercion works";
+        ok ! $code->($cannot_coerce), "to_$type() returns false on invalid value";
+    }
+
+    # type test handler
+    {
+        my ($valid, $invalid) = map { shift @$data } 1 .. 2;
+        ok my $code = __PACKAGE__->can("is_$type"), "is_$type() check was exported";
+        ok $code->($valid), "is_$type() check true on valid value";
+        ok ! $code->($invalid), "is_$type() check false on invalid value";
+    }
+}
+
+
diff --git a/t/lib/TestWrapper.pm b/t/lib/TestWrapper.pm
new file mode 100644 (file)
index 0000000..187e1b5
--- /dev/null
@@ -0,0 +1,36 @@
+package TestWrapper;
+use strict;
+use warnings;
+
+use Class::C3;
+use base 'MooseX::TypeLibrary::Wrapper';
+
+sub type_export_generator {
+    my $class = shift;
+    my ($type, $full) = @_;
+    my $code = $class->next::method(@_);
+    return sub { $code->(@_) };
+}
+
+sub check_export_generator {
+    my $class = shift;
+    my ($type, $full, $undef_msg) = @_;
+    my $code = $class->next::method(@_);
+    return sub {
+        return $code unless @_;
+        return $code->(@_);
+    };
+}
+
+sub coercion_export_generator {
+    my $class = shift;
+    my ($type, $full, $undef_msg) = @_;
+    my $code = $class->next::method(@_);
+    return sub {
+        my $val = $code->(@_);
+        die "coercion returned undef\n" unless defined $val;
+        return $val;
+    };
+}
+
+1;