X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=gitmo%2FMooseX-Types.git;a=blobdiff_plain;f=lib%2FMooseX%2FTypes.pm;h=e5047fdf23a3b47cc2e9430b90b0180da4a2326c;hp=364faa29ca789df2ff87448a3f4294d5aa92d974;hb=5c3e47c7cf6e85aebff0ac06a454a54f66b3106f;hpb=575e6fc140b973656787c1317d69cedf42d1b565 diff --git a/lib/MooseX/Types.pm b/lib/MooseX/Types.pm index 364faa2..e5047fd 100644 --- a/lib/MooseX/Types.pm +++ b/lib/MooseX/Types.pm @@ -1,26 +1,21 @@ package MooseX::Types; use Moose; -=head1 NAME - -MooseX::Types - Organise your Moose types in libraries - -=cut - -#use warnings; -#use strict; +# ABSTRACT: Organise your Moose types in libraries use Moose::Util::TypeConstraints; use MooseX::Types::TypeDecorator; -use MooseX::Types::Base (); -use MooseX::Types::Util qw( filter_tags ); +use MooseX::Types::Base (); +use MooseX::Types::Util qw( filter_tags ); use MooseX::Types::UndefinedType; -use Carp::Clan qw( ^MooseX::Types ); +use MooseX::Types::CheckedUtilExports (); +use Carp::Clan qw( ^MooseX::Types ); +use Sub::Name; +use Scalar::Util 'reftype'; use namespace::clean -except => [qw( meta )]; -our $VERSION = 0.06; - +use 5.008; my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'}; =head1 SYNOPSIS @@ -30,22 +25,27 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'}; package MyLibrary; # predeclare our own types - use MooseX::Types - -declare => [qw( - PositiveInt NegativeInt - ArrayRefOfPositiveInt ArrayRefOfAtLeastThreeNegativeInts - LotsOfInnerConstraints StrOrArrayRef - )]; + use MooseX::Types -declare => [ + qw( + PositiveInt + NegativeInt + ArrayRefOfPositiveInt + ArrayRefOfAtLeastThreeNegativeInts + LotsOfInnerConstraints + StrOrArrayRef + MyDateTime + ) + ]; # import builtin types - use MooseX::Types::Moose 'Int'; + use MooseX::Types::Moose qw/Int HashRef/; - # type definition - subtype PositiveInt, - as Int, + # type definition. + subtype PositiveInt, + as Int, where { $_ > 0 }, message { "Int is not larger than 0" }; - + subtype NegativeInt, as Int, where { $_ < 0 }, @@ -56,23 +56,35 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'}; from Int, via { 1 }; - # with parameterized constraints. Please note the containing '(...)' - + # with parameterized constraints. + subtype ArrayRefOfPositiveInt, - as (ArrayRef[PositiveInt]); - + as ArrayRef[PositiveInt]; + subtype ArrayRefOfAtLeastThreeNegativeInts, - as (ArrayRef[NegativeInt]), + as ArrayRef[NegativeInt], where { scalar(@$_) > 2 }; subtype LotsOfInnerConstraints, - as (ArrayRef[ArrayRef[HashRef[Int]]]); - + as ArrayRef[ArrayRef[HashRef[Int]]]; + # with TypeConstraint Unions - + subtype StrOrArrayRef, as Str|ArrayRef; + # class types + + class_type 'DateTime'; + + # or better + + class_type MyDateTime, { class => 'DateTime' }; + + coerce MyDateTime, + from HashRef, + via { DateTime->new(%$_) }; + 1; =head2 Usage @@ -105,37 +117,30 @@ my $UndefMsg = q{Action for type '%s' not yet defined in library '%s'}; =head1 DESCRIPTION -The types provided with L 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. +The type system provided by Moose effectively makes all of its builtin type +global, as are any types you declare with Moose. This means that every module +that declares a type named "PositiveInt" is sharing the same type object. This +can be a problem when different parts of the code base want to use the same +name for different things. -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 package lets you declare types using short names, but behind the scenes +it namespaces all your type declarations, effectively prevent name clashes +between packages. -This module will also provide you with some helper functions to make it -easier to use Moose types in your code. +This is done by creating a type library module like C and then +importing types from that module into other modules. -=head1 TYPE HANDLER FUNCTIONS +As a side effect, the declaration mechanism allows you to write type names as +barewords (really function calls), which catches typos in names at compile +time rather than run time. -=head2 $type +This module also provides some helper functions for using Moose types outside +of attribute declarations. -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: 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. +If you mix string-based names with types created by this module, it will warn, +with a few exceptions. If you are declaring a C or +c within your type library, or if you use a fully qualified name +like C<"MyApp::Foo">. =head1 LIBRARY DEFINITION @@ -189,6 +194,27 @@ around L. This means you can do something like this: use MyLibrary TypeA => { -as => 'MyTypeA' }, TypeB => { -as => 'MyTypeB' }; +=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: This handler will only be exported for types that can +do type coercion. This has the advantage that a coercion to a type that +has not defined any coercions will lead to a compile-time error. + =head1 WRAPPING A LIBRARY You can define your own wrapper subclasses to manipulate the behaviour @@ -196,7 +222,7 @@ of a set of library exports. Here is an example: package MyWrapper; use strict; - use Class::C3; + use MRO::Compat; use base 'MooseX::Types::Wrapper'; sub coercion_export_generator { @@ -224,8 +250,7 @@ with this: ... 1; -The C library name is a special shortcut for -L. +The C library name is a special shortcut for L. =head2 Generator methods you can overload @@ -233,8 +258,7 @@ L. =item type_export_generator( $short, $full ) -Creates a closure returning the type's L -object. +Creates a closure returning the type's L object. =item check_export_generator( $short, $full, $undef_message ) @@ -265,25 +289,20 @@ type does not yet exist. =back -=head1 NOTES REGARDING PARAMETERIZED CONSTRAINTS +=head1 RECURSIVE SUBTYPES -L uses L to do some overloading -which generally allows you to easily create types with parameters such as: +As of version 0.08, L has experimental support for Recursive +subtypes. This will allow: - subtype ParameterType, - as (ArrayRef[Int]); + subtype Tree() => as HashRef[Str|Tree]; -However, due to an outstanding issue you will need to wrap the parameterized -type inside parenthesis, as in the example above. Hopefully this limitation -will be lifted in a future version of this module. +Which validates things like: -If you are using paramterized types in the options section of an attribute -declaration, the parenthesis are not needed: + {key=>'value'}; + {key=>{subkey1=>'value', subkey2=>'value'}} - use Moose; - use MooseX::Types::Moose qw(HashRef Int); - - has 'attr' => (isa=>HashRef[Str]); +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 @@ -291,33 +310,32 @@ L uses L to do some overloading which generally allows you to easily create union types: subtype StrOrArrayRef, - as Str|ArrayRef; + 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); + 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 -Installs the L class into the caller and -exports types according to the specification described in -L. This will continue to -L' C method to export helper -functions you will need to declare your types. +Installs the L class into the caller and exports types +according to the specification described in L. This +will continue to L' C method to export +helper functions you will need to declare your types. =cut sub import { my ($class, %args) = @_; - my $callee = caller; + my $caller = caller; # everyone should want this strict->import; @@ -325,7 +343,7 @@ sub import { # inject base class into new library { no strict 'refs'; - unshift @{ $callee . '::ISA' }, 'MooseX::Types::Base'; + unshift @{ $caller . '::ISA' }, 'MooseX::Types::Base'; } # generate predeclared type helpers @@ -339,62 +357,98 @@ sub import { if $type =~ /::/; # add type to library and remember to export - $callee->add_type($type); + $caller->add_type($type); push @to_export, $type; } - $callee->import({ -full => 1, -into => $callee }, @to_export); + $caller->import({ -full => 1, -into => $caller }, @to_export); } # run type constraints import - return Moose::Util::TypeConstraints->import({ into => $callee }); + Moose::Util::TypeConstraints->import({ into => $caller }); + + # override some with versions that check for syntax errors + MooseX::Types::CheckedUtilExports->import({ into => $caller }); + + 1; } =head2 type_export_generator Generate a type export, e.g. C. This will return either a L object, or alternatively a -L object if the type was not -yet defined. +L object if the type was not yet defined. =cut sub type_export_generator { my ($class, $type, $name) = @_; - return sub { - my $type_constraint; + + ## Return an anonymous subroutine that will generate the proxied type + ## constraint for you. + + return subname "__TYPE__::$name" => sub { + my $type_constraint = $class->create_base_type_constraint($name); + if(defined(my $params = shift @_)) { - if(ref $params eq 'ARRAY') { + ## 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(reftype $params eq 'ARRAY') { $type_constraint = $class->create_arged_type_constraint($name, @$params); + } elsif(!defined $type_constraint) { + croak "Syntax error in type definition (did you forget a comma" + . " after $type?)"; } else { - croak 'Arguments must be an ArrayRef, not '. ref $params; + croak "Argument must be an ArrayRef to create a parameterized " + . "type, Eg.: ${type}[Int]. Got: ".ref($params)."." } - } else { - $type_constraint = $class->create_base_type_constraint($name); } + $type_constraint = defined($type_constraint) ? $type_constraint : MooseX::Types::UndefinedType->new($name); - return $class->create_type_decorator($type_constraint); + 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. +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) + my ($class, $name, @args) = @_; + my $type_constraint = Moose::Util::TypeConstraints::find_or_create_type_constraint("$name"); + my $parameterized = $type_constraint->parameterize(@args); + # It's obnoxious to have to parameterize before looking for the TC, but the + # alternative is to hard-code the assumption that the name is + # "$name[$args[0]]", which would be worse. + # This breaks MXMS, unfortunately, which relies on things like Tuple[...] + # creating new type objects each time. + # if (my $existing = + # Moose::Util::TypeConstraints::find_type_constraint($parameterized->name)) { + # return $existing; + # } + # Moose::Util::TypeConstraints::register_type_constraint($parameterized); + return $parameterized; } =head2 create_base_type_constraint ($name) -Given a String $name, find the matching typeconstraint. +Given a String $name, find the matching type constraint. =cut @@ -417,7 +471,7 @@ sub create_type_decorator { =head2 coercion_export_generator -This generates a coercion handler function, e.g. C. +This generates a coercion handler function, e.g. C. =cut @@ -457,29 +511,98 @@ sub check_export_generator { =head1 CAVEATS +The following are lists of gotchas 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 L 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 its 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; + } + + # then in another module ... + + package Foo; + use TypeAndSubExporter qw(MyStr); + +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 COMBINING TYPE LIBRARIES + +You may want to combine a set of types for your application with other type +libraries, like L or L. + +The L module provides a simple API for combining a set +of type libraries together. + =head1 SEE ALSO -L, -L, -L, +L, L, L, L -=head1 AUTHOR AND COPYRIGHT +=head1 ACKNOWLEDGEMENTS + +Many thanks to the C<#moose> cabal on C. + +=head1 CONTRIBUTORS + +jnapiorkowski: John Napiorkowski -Robert 'phaylon' Sedlacek Crs@474.atE>, with many thanks to -the C<#moose> cabal on C. +caelum: Rafael Kitover -Additional features by John Napiorkowski (jnapiorkowski) . +rafl: Florian Ragwitz -=head1 LICENSE +hdp: Hans Dieter Pearcey -This program is free software; you can redistribute it and/or modify -it under the same terms as perl itself. +autarch: Dave Rolsky =cut