MooseX-TypeLibrary with tests and first pod (phaylon)
phaylon [Sat, 17 Mar 2007 14:33:05 +0000 (14:33 +0000)]
MANIFEST.SKIP [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
lib/MooseX/TypeLibrary.pm [new file with mode: 0644]
lib/MooseX/TypeLibrary/Base.pm [new file with mode: 0644]
lib/MooseX/TypeLibrary/Moose.pm [new file with mode: 0644]
t/10_moose-types.t [new file with mode: 0644]
t/11_library-definition.t [new file with mode: 0644]
t/lib/TestLibrary.pm [new file with mode: 0644]

diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP
new file mode 100644 (file)
index 0000000..f414ead
--- /dev/null
@@ -0,0 +1,40 @@
+#
+#   This file is a modified version of the MANIFEST.SKIP file
+#   from DBIx-Class.
+#
+
+# Avoid version control files.
+\bRCS\b
+\bCVS\b
+,v$
+\B\.svn\b
+
+# Avoid Makemaker generated and utility files.
+\bMakefile$
+\bblib
+\bMakeMaker-\d
+\bpm_to_blib$
+\bblibdirs$
+^MANIFEST\.SKIP$
+
+# for developers only :)
+^TODO$
+
+# Avoid temp and backup files.
+~$
+\.tmp$
+\.old$
+\.bak$
+\.swp$
+\.swo$
+\#$
+\b\.#
+
+# avoid OS X finder files
+\.DS_Store$
+
+# Don't ship the last dist we built :)
+\.tar\.gz$
+
+# Skip maint stuff
+^maint/
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644 (file)
index 0000000..4dfa6ce
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use inc::Module::Install;
+
+name            q{MooseX-TypeLibrary};
+license         q{perl};
+author          q{Robert 'phaylon' Sedlacek <rs@474.at>};
+all_from        q{lib/MooseX/TypeLibrary.pm};
+
+build_requires  q{Test::More},                  '0.62';
+build_requires  q{FindBin},                     0;
+
+requires        q{Moose},                       '0.19';
+requires        q{Sub::Uplevel},                '0.14';
+requires        q{Sub::Install},                '0.922';
+requires        q{namespace::clean},            0;
+requires        q{Carp},                        0;
+
+WriteAll;
diff --git a/lib/MooseX/TypeLibrary.pm b/lib/MooseX/TypeLibrary.pm
new file mode 100644 (file)
index 0000000..6dedb27
--- /dev/null
@@ -0,0 +1,252 @@
+package MooseX::TypeLibrary;
+
+=head1 NAME
+
+MooseX::TypeLibrary - Organise your Moose types in libraries
+
+=cut
+
+use warnings;
+use strict;
+
+use Sub::Uplevel;
+use Moose::Util::TypeConstraints;
+use MooseX::TypeLibrary::Base;
+use Sub::Install    qw( install_sub );
+use namespace::clean;
+
+our $VERSION = 0.01;
+
+my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'};
+
+=head1 SYNOPSIS
+
+  #
+  # Library Definition
+  #
+  package MyLibrary;
+  use strict;
+
+  # predeclare our own types
+  use MooseX::TypeLibrary 
+      -declare => [qw( PositiveInt NegativeInt )];
+
+  # import builtin types
+  use MooseX::TypeLibrary::Moose 'Int';
+
+  # type definition
+  subtype PositiveInt, 
+      as Int, 
+      where { $_ > 0 },
+      message { "Int is not larger than 0" };
+  
+  subtype NegativeInt,
+      as Int,
+      where { $_ < 0 },
+      message { "Int is not smaller than 0" };
+
+  # type coercion
+  coerce PositiveInt,
+      from Int,
+          via { 1 };
+
+  1;
+
+  #
+  # Usage
+  #
+  package Foo;
+  use Moose;
+  use MyLibrary qw( PositiveInt NegativeInt );
+
+  # use the exported constants as type names
+  has 'bar',
+      isa    => PositiveInt,
+      is     => 'rw';
+  has 'baz',
+      isa    => NegativeInt,
+      is     => 'rw';
+
+  sub quux {
+      my ($self, $value);
+
+      # test the value
+      print "positive\n" if is_PositiveInt($value);
+      print "negative\n" if is_NegativeInt($value);
+
+      # coerce the value, NegativeInt doesn't have a coercion
+      # helper, since it didn't define any coercions.
+      $value = to_PositiveInt($value) or die "Cannot coerce";
+  }
+
+  1;
+
+=head1 DESCRIPTION
+
+The types provided with L<Moose> are by design global. This package helps
+you to organise and selectively import your own and the built-in types in
+libraries. As a nice side effect, it catches typos at compile-time too.
+
+However, the main reason for this module is to provide an easy way to not
+have conflicts with your type names, since the internal fully qualified
+names of the types will be prefixed with the library's name.
+
+This module will also provide you with some helper functions to make it 
+easier to use Moose types in your code.
+
+=head1 TYPE HANDLER FUNCTIONS
+
+=head2 $type
+
+A constant with the name of your type. It contains the type's fully
+qualified name. Takes no value, as all constants.
+
+=head2 is_$type
+
+This handler takes a value and tests if it is a valid value for this
+C<$type>. It will return true or false.
+
+=head2 to_$type
+
+A handler that will take a value and coerce it into the C<$type>. It will
+return a false value if the type could not be coerced.
+
+B<Important Note>: This handler will only be exported for types that can
+do type coercion. This has the advantage that a coercion to a type that
+cannot hasn't defined any coercions will lead to a compile-time error.
+
+=head1 LIBRARY DEFINITION
+
+A MooseX::TypeLibrary is just a normal Perl module. Unlike Moose 
+itself, it does not install C<use strict> and C<use warnings> in your
+class by default, so this is up to you.
+
+The only thing a library is required to do is
+
+  use MooseX::TypeLibrary -declare => \@types;
+
+with C<@types> being a list of types you wish to define in this library.
+This line will install a proper base class in your package as well as the
+full set of L<handlers|/"TYPE HANDLER FUNCTIONS"> for your declared 
+types. It will then hand control over to L<Moose::Util::TypeConstraints>'
+C<import> method to export the functions you will need to declare your
+types.
+
+If you want to use Moose' built-in types (e.g. for subtyping) you will 
+want to 
+
+  use MooseX::TypeLibrary::Moose @types;
+
+to import the helpers from the shipped L<MooseX::TypeLibrary::Moose>
+library which can export all types that come with Moose.
+
+You will have to define coercions for your types or your library won't
+export a L</to_$type> coercion helper for it.
+
+=head1 LIBRARY USAGE
+
+You can import the L<"type helpers"|/"TYPE HANDLER FUNCTIONS"> of a
+library by C<use>ing it with a list of types to import as arguments. If
+you want all of them, use the C<:all> tag. For example:
+
+  use MyLibrary      ':all';
+  use MyOtherLibrary qw( TypeA TypeB );
+
+MooseX::TypeLibrary comes with a library of Moose' built-in types called
+L<MooseX::TypeLibrary::Moose>.
+
+=head1 METHODS
+
+=head2 import
+
+=cut
+
+sub import {
+    my ($class, %args) = @_;
+    my  $callee = caller;
+
+    # inject base class into new library
+    {   no strict 'refs';
+        unshift @{ $callee . '::ISA' }, 'MooseX::TypeLibrary::Base';
+    }
+
+    # generate predeclared type helpers
+    if (my @declare = @{ $args{ -declare } || [] }) {
+        for my $type (@declare) {
+            $callee->add_type($type);
+            $callee->export_type_into(
+                $callee, $type, 
+                sprintf($UndefMsg, $type, $callee), 
+                -full => 1,
+            );
+        }
+    }
+
+    # run type constraints import
+    return uplevel 1, 
+        Moose::Util::TypeConstraints->can('import'), 
+        'Moose::Util::TypeConstraints';
+}
+
+=head2 type_export_generator
+
+=cut
+
+sub type_export_generator {
+    my ($class, $type, $full) = @_;
+    return sub { $full };
+}
+
+=head2 coercion_export_generator
+
+=cut
+
+sub coercion_export_generator {
+    my ($class, $type, $full, $undef_msg) = @_;
+    return sub {
+        my ($value) = @_;
+
+        # we need a type object
+        my $tobj = find_type_constraint($full) or croak $undef_msg;
+        my $return = $tobj->coerce($value);
+
+        # non-successful coercion returns false
+        return unless $tobj->check($return);
+
+        return $return;
+    }
+}
+
+=head2 check_export_generator
+
+=cut
+
+sub check_export_generator {
+    my ($class, $type, $full, $undef_msg) = @_;
+    return sub {
+        my ($value) = @_;
+
+        # we need a type object
+        my $tobj = find_type_constraint($full) or croak $undef_msg;
+
+        return $tobj->check($value);
+    }
+}
+
+=head1 SEE ALSO
+
+L<Moose>, L<Moose::Util::TypeConstraints>, L<MooseX::TypeLibrary::Moose>
+
+=head1 AUTHOR AND COPYRIGHT
+
+Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>, with many thanks to
+the C<#moose> cabal on C<irc.perl.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/lib/MooseX/TypeLibrary/Base.pm b/lib/MooseX/TypeLibrary/Base.pm
new file mode 100644 (file)
index 0000000..497737b
--- /dev/null
@@ -0,0 +1,106 @@
+package MooseX::TypeLibrary::Base;
+use warnings;
+use strict;
+
+#use Smart::Comments;
+use Sub::Install    qw( install_sub );
+use Carp            qw( croak );
+use Moose::Util::TypeConstraints;
+use namespace::clean;
+
+my $UndefMsg = q{Unable to find type '%s' in library '%s'};
+
+sub import {
+    my ($class, @types) = @_;
+
+    # flatten out tags
+    @types = map { $_ eq ':all' ? $class->type_names : $_ } @types;
+
+  TYPE:
+    # export all requested types
+    for my $type (@types) {
+        $class->export_type_into(
+            scalar(caller), $type, sprintf $UndefMsg, $type, $class );
+    }
+    return 1;
+}
+
+sub export_type_into {
+    my ($class, $target, $type, $undef_msg, %args) = @_;
+    
+    # the real type name and its type object
+    my $full = $class->get_type($type);
+    my $tobj = find_type_constraint($full);
+    ### Exporting: $full
+
+    # install Type name constant
+    install_sub({
+        code => MooseX::TypeLibrary->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),
+        into => $target,
+        as   => "is_$type",
+    });
+
+    # only install to_Type coercion handler if type can coerce
+    return 1 unless $args{ -full } or $tobj->has_coercion;
+    
+    # install to_Type coercion handler
+    install_sub({
+        code => MooseX::TypeLibrary
+                    ->coercion_export_generator($type, $full, $undef_msg),
+        into => $target,
+        as   => "to_$type",
+    });
+
+    return 1;
+}
+
+sub get_type {
+    my ($class, $type) = @_;
+
+    # useful message if the type couldn't be found
+    croak "Unknown type '$type' in library '$class'"
+        unless $class->has_type($type);
+
+    # return real name of the type
+    return $class->type_storage->{ $type };
+}
+
+sub type_names {
+    my ($class) = @_;
+
+    # return short names of all stored types
+    return keys %{ $class->type_storage };
+}
+
+sub add_type {
+    my ($class, $type) = @_;
+
+    # store type with library prefix as real name
+    $class->type_storage->{ $type } = "${class}::${type}";
+}
+
+sub has_type {
+    my ($class, $type) = @_;
+
+    # check if we stored a type under that name
+    return ! ! $class->type_storage->{ $type };
+}
+
+sub type_storage {
+    my ($class) = @_;
+
+    # return a reference to the storage in ourself
+    {   no strict 'refs';
+        return \%{ $class . '::__MOOSEX_TYPELIBRARY_STORAGE' };
+    }
+}
+
+1;
diff --git a/lib/MooseX/TypeLibrary/Moose.pm b/lib/MooseX/TypeLibrary/Moose.pm
new file mode 100644 (file)
index 0000000..aea1ac8
--- /dev/null
@@ -0,0 +1,17 @@
+package MooseX::TypeLibrary::Moose;
+use warnings;
+use strict;
+
+use MooseX::TypeLibrary;
+use Moose::Util::TypeConstraints ();
+use namespace::clean;
+
+# all available builtin types as short and long name
+my %BuiltIn_Storage 
+  = map { ($_) x 2 } 
+    Moose::Util::TypeConstraints->list_all_type_constraints;
+
+# use prepopulated builtin hash as type storage
+sub type_storage { \%BuiltIn_Storage }
+
+1;
diff --git a/t/10_moose-types.t b/t/10_moose-types.t
new file mode 100644 (file)
index 0000000..c7713b8
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use MooseX::TypeLibrary::Moose ':all';
+
+my @types = MooseX::TypeLibrary::Moose->type_names;
+
+plan tests => @types * 3;
+
+for my $t (@types) {
+    ok my $code = __PACKAGE__->can($t), "$t() was exported";
+    is $code->(), $t, "$t() returns '$t'";
+    ok __PACKAGE__->can("is_$t"), "is_$t() was exported";
+}
+
diff --git a/t/11_library-definition.t b/t/11_library-definition.t
new file mode 100644 (file)
index 0000000..c71fede
--- /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 TestLibrary ':all';
+
+my @tests = (
+    [ 'NonEmptyStr', 12, "12", [], "foobar", "" ],
+    [ 'IntArrayRef', 12, [12], {}, [17, 23], {} ],
+);
+
+plan tests => (@tests * 8) + 1;
+
+# 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";
+    }
+}
+
+# coercion not available
+ok ! __PACKAGE__->can('to_TwentyThree'), "type without coercion doesn't have to_* helper";
diff --git a/t/lib/TestLibrary.pm b/t/lib/TestLibrary.pm
new file mode 100644 (file)
index 0000000..53b01de
--- /dev/null
@@ -0,0 +1,32 @@
+package TestLibrary;
+use warnings;
+use strict;
+
+use MooseX::TypeLibrary::Moose qw( Str ArrayRef Int );
+use MooseX::TypeLibrary
+    -declare => [qw( NonEmptyStr IntArrayRef TwentyThree )];
+
+subtype NonEmptyStr,
+    as Str,
+    where { length $_ },
+    message { 'Str must not be empty' };
+
+coerce NonEmptyStr,
+    from Int,
+        via { "$_" };
+
+subtype IntArrayRef,
+    as ArrayRef,
+    where { not grep { $_ !~ /^\d+$/ } @$_ },
+    message { 'ArrayRef contains non-Int value' };
+
+coerce IntArrayRef,
+    from Int,
+        via { [$_] };
+
+subtype TwentyThree,
+    as Int,
+    where { $_ == 23 },
+    message { 'Int is not 23' };
+
+1;