X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMooseX%2FTypes.pm;h=4a39855c3d7d1e5215acc06adfcf630d2e2a2999;hb=e50bf0d982a7e75adb8106c092080d06cee427c3;hp=c93591fe2815bc657a222423c41586825d0b7251;hpb=8cd6ad6c50e44e264bdaa33a68e80b6c58d9b325;p=gitmo%2FMooseX-Types.git diff --git a/lib/MooseX/Types.pm b/lib/MooseX/Types.pm index c93591f..4a39855 100644 --- a/lib/MooseX/Types.pm +++ b/lib/MooseX/Types.pm @@ -1,4 +1,5 @@ package MooseX::Types; +use Moose; =head1 NAME @@ -6,21 +7,17 @@ MooseX::Types - Organise your Moose types in libraries =cut -#use warnings; -#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; -use Sub::Install qw( install_sub ); -use Carp qw( croak ); -use Moose; +use Carp::Clan qw( ^MooseX::Types ); use namespace::clean -except => [qw( meta )]; -our $VERSION = 0.05; - +use 5.008; +our $VERSION = 0.10; my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'}; =head1 SYNOPSIS @@ -31,12 +28,16 @@ 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'; - # type definition + # type definition. subtype PositiveInt, as Int, where { $_ > 0 }, @@ -52,6 +53,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 @@ -162,6 +180,12 @@ you want all of them, use the C<:all> tag. For example: MooseX::Types comes with a library of Moose' built-in types called L. +The exporting mechanism is, since version 0.5, implemented via a wrapper +around L. This means you can do something like this: + + use MyLibrary TypeA => { -as => 'MyTypeA' }, + TypeB => { -as => 'MyTypeB' }; + =head1 WRAPPING A LIBRARY You can define your own wrapper subclasses to manipulate the behaviour @@ -236,7 +260,38 @@ The fully qualified name of this type as L knows it. A message that will be thrown when type functionality is used but the type does not yet exist. -=back +=head1 RECURSIVE SUBTYPES + +As of version 0.08, L has experimental support for Recursive +subtypes. This will allow: + + subtype Tree() => as HashRef[Str|Tree]; + +Which validates things like: + + {key=>'value'}; + {key=>{subkey1=>'value', subkey2=>'value'}} + +And so on. This feature is new and there may be lurking bugs so don't be afraid +to hunt me down with patches and test cases if you have trouble. + +=head1 NOTES REGARDING TYPE UNIONS + +L uses L 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 @@ -266,19 +321,19 @@ sub import { # generate predeclared type helpers if (my @orig_declare = @{ $args{ -declare } || [] }) { my ($tags, $declare) = filter_tags @orig_declare; + my @to_export; for my $type (@$declare) { croak "Cannot create a type containing '::' ($type) at the moment" if $type =~ /::/; + # add type to library and remember to export $callee->add_type($type); - $callee->export_type_into( - $callee, $type, - sprintf($UndefMsg, $type, $callee), - -full => 1, - ); + push @to_export, $type; } + + $callee->import({ -full => 1, -into => $callee }, @to_export); } # run type constraints import @@ -295,13 +350,79 @@ 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 +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. @@ -344,20 +465,86 @@ 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 +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. + +=head2 Compatibility with Sub::Exporter + +If you want to use L with a Type Library, you need to make sure +you export all the type constraints declared AS WELL AS any additional export +targets. For example if you do: + + package TypeAndSubExporter; { + + use MooseX::Types::Moose qw(Str); + use MooseX::Types -declare => [qw(MyStr)]; + use Sub::Exporter -setup => { exports => [ qw(something) ] }; + + subtype MyStr, + as Str; + + sub something { + return 1; + } + + } 1; + + package Foo; { + use TypeAndSubExporter qw(MyStr); + } 1; + +You'll get a '"MyStr" is not exported by the TypeAndSubExporter module' error. +Upi can workaround by: + + - use Sub::Exporter -setup => { exports => [ qw(something) ] }; + + use Sub::Exporter -setup => { exports => [ qw(something MyStr) ] }; + +This is a workaround and I am exploring how to make these modules work better +together. I realize this workaround will lead a lot of duplication in your +export declarations and will be onerous for large type libraries. Patches and +detailed test cases welcome. See the tests directory for a start on this. + =head1 SEE ALSO -L, L, L +L, +L, +L, +L =head1 AUTHOR AND COPYRIGHT Robert 'phaylon' Sedlacek Crs@474.atE>, with many thanks to the C<#moose> cabal on C. +Additional features by John Napiorkowski (jnapiorkowski) . + =head1 LICENSE This program is free software; you can redistribute it and/or modify