Implement basic usage of "enum" type constraints
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
index 812ecd6..885788a 100644 (file)
@@ -1,45 +1,32 @@
 package Mouse::Util::TypeConstraints;
 use strict;
 use warnings;
+use base 'Exporter';
 
 use Carp ();
 use Scalar::Util qw/blessed looks_like_number openhandle/;
 
-my %SUBTYPE;
+our @EXPORT = qw(
+    as where message from via type subtype coerce class_type role_type enum
+);
+
+my %TYPE;
+my %TYPE_SOURCE;
 my %COERCE;
 my %COERCE_KEYS;
 
-#find_type_constraint register_type_constraint
-sub import {
-    my $class  = shift;
-    my %args   = @_;
-    my $caller = $args{callee} || caller(0);
-
-    no strict 'refs';
-    *{"$caller\::as"}          = \&_as;
-    *{"$caller\::where"}       = \&_where;
-    *{"$caller\::message"}     = \&_message;
-    *{"$caller\::from"}        = \&_from;
-    *{"$caller\::via"}         = \&_via;
-    *{"$caller\::subtype"}     = \&_subtype;
-    *{"$caller\::coerce"}      = \&_coerce;
-    *{"$caller\::class_type"}  = \&_class_type;
-    *{"$caller\::role_type"}   = \&_role_type;
-}
-
-
-sub _as ($) {
+sub as ($) {
     as => $_[0]
 }
-sub _where (&) {
+sub where (&) {
     where => $_[0]
 }
-sub _message ($) {
+sub message (&) {
     message => $_[0]
 }
 
-sub _from { @_ }
-sub _via (&) {
+sub from { @_ }
+sub via (&) {
     $_[0]
 }
 
@@ -47,7 +34,7 @@ my $optimized_constraints;
 my $optimized_constraints_base;
 {
     no warnings 'uninitialized';
-    %SUBTYPE = (
+    %TYPE = (
         Any        => sub { 1 },
         Item       => sub { 1 },
         Bool       => sub {
@@ -70,41 +57,56 @@ my $optimized_constraints_base;
         GlobRef    => sub { ref($_) eq 'GLOB'   },
 
         FileHandle => sub {
-                ref($_) eq 'GLOB'
-                && openhandle($_)
+            ref($_) eq 'GLOB' && openhandle($_)
             or
-                blessed($_)
-                && $_->isa("IO::Handle")
-            },
+            blessed($_) && $_->isa("IO::Handle")
+        },
 
         Object     => sub { blessed($_) && blessed($_) ne 'Regexp' },
     );
 
-    sub optimized_constraints { \%SUBTYPE }
-    my @SUBTYPE_KEYS = keys %SUBTYPE;
-    sub list_all_builtin_type_constraints { @SUBTYPE_KEYS }
+    sub optimized_constraints { \%TYPE }
+    my @TYPE_KEYS = keys %TYPE;
+    sub list_all_builtin_type_constraints { @TYPE_KEYS }
+
+    @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
+}
+
+sub type {
+    my $pkg = caller(0);
+    my($name, %conf) = @_;
+    if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
+        Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
+    };
+    my $constraint = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
+
+    $TYPE_SOURCE{$name} = $pkg;
+    $TYPE{$name} = $constraint;
 }
 
-sub _subtype {
+sub subtype {
     my $pkg = caller(0);
     my($name, %conf) = @_;
-    if (my $type = $SUBTYPE{$name}) {
-        Carp::croak "The type constraint '$name' has already been created, cannot be created again in $pkg";
+    if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
+        Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
     };
-    my $stuff = $conf{where} || do { $SUBTYPE{delete $conf{as} || 'Any' } };
-    my $as    = $conf{as} || '';
-    if ($as = $SUBTYPE{$as}) {
-        $SUBTYPE{$name} = sub { $as->($_) && $stuff->($_) };
+    my $constraint = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
+    my $as         = $conf{as} || '';
+
+    $TYPE_SOURCE{$name} = $pkg;
+
+    if ($as = $TYPE{$as}) {
+        $TYPE{$name} = sub { $as->($_) && $constraint->($_) };
     } else {
-        $SUBTYPE{$name} = $stuff;
+        $TYPE{$name} = $constraint;
     }
 }
 
-sub _coerce {
+sub coerce {
     my($name, %conf) = @_;
 
     Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
-        unless $SUBTYPE{$name};
+        unless $TYPE{$name};
 
     unless ($COERCE{$name}) {
         $COERCE{$name}      = {};
@@ -115,27 +117,26 @@ sub _coerce {
             if $COERCE{$name}->{$type};
 
         Carp::croak "Could not find the type constraint ($type) to coerce from"
-            unless $SUBTYPE{$type};
+            unless $TYPE{$type};
 
         push @{ $COERCE_KEYS{$name} }, $type;
         $COERCE{$name}->{$type} = $code;
     }
 }
 
-sub _class_type {
+sub class_type {
     my $pkg = caller(0);
     my($name, $conf) = @_;
     my $class = $conf->{class};
-    Mouse::load_class($class);
-    _subtype(
+    subtype(
         $name => where => sub { $_->isa($class) }
     );
 }
 
-sub _role_type {
+sub role_type {
     my($name, $conf) = @_;
     my $role = $conf->{role};
-    _subtype(
+    subtype(
         $name => where => sub {
             return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
             $_->meta->does_role($role);
@@ -151,7 +152,7 @@ sub typecast_constraints {
         next unless $COERCE{$type};
         for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
             $_ = $value;
-            next unless $SUBTYPE{$coerce_type}->();
+            next unless $TYPE{$coerce_type}->();
             $_ = $value;
             $_ = $COERCE{$type}->{$coerce_type}->();
             return $_ if $type_constraint->();
@@ -160,6 +161,15 @@ sub typecast_constraints {
     return $value;
 }
 
+sub enum {
+    my $name = shift;
+    my %is_valid = map { $_ => 1 } @_;
+
+    subtype(
+        $name => where => sub { $is_valid{$_} }
+    );
+}
+
 1;
 
 __END__