Style
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
1 package Mouse::Util::TypeConstraints;
2 use strict;
3 use warnings;
4
5 use Carp ();
6 use Scalar::Util qw/blessed looks_like_number openhandle/;
7
8 my %TYPE;
9 my %COERCE;
10 my %COERCE_KEYS;
11
12 #find_type_constraint register_type_constraint
13 sub import {
14     my $class  = shift;
15     my %args   = @_;
16     my $caller = $args{callee} || caller(0);
17
18     no strict 'refs';
19     *{"$caller\::as"}          = \&_as;
20     *{"$caller\::where"}       = \&_where;
21     *{"$caller\::message"}     = \&_message;
22     *{"$caller\::from"}        = \&_from;
23     *{"$caller\::via"}         = \&_via;
24     *{"$caller\::type"}        = \&_type;
25     *{"$caller\::subtype"}     = \&_subtype;
26     *{"$caller\::coerce"}      = \&_coerce;
27     *{"$caller\::class_type"}  = \&_class_type;
28     *{"$caller\::role_type"}   = \&_role_type;
29 }
30
31
32 sub _as ($) {
33     as => $_[0]
34 }
35 sub _where (&) {
36     where => $_[0]
37 }
38 sub _message ($) {
39     message => $_[0]
40 }
41
42 sub _from { @_ }
43 sub _via (&) {
44     $_[0]
45 }
46
47 my $optimized_constraints;
48 my $optimized_constraints_base;
49 {
50     no warnings 'uninitialized';
51     %TYPE = (
52         Any        => sub { 1 },
53         Item       => sub { 1 },
54         Bool       => sub {
55             !defined($_) || $_ eq "" || "$_" eq '1' || "$_" eq '0'
56         },
57         Undef      => sub { !defined($_) },
58         Defined    => sub { defined($_) },
59         Value      => sub { defined($_) && !ref($_) },
60         Num        => sub { !ref($_) && looks_like_number($_) },
61         Int        => sub { defined($_) && !ref($_) && /^-?[0-9]+$/ },
62         Str        => sub { defined($_) && !ref($_) },
63         ClassName  => sub { Mouse::is_class_loaded($_) },
64         Ref        => sub { ref($_) },
65
66         ScalarRef  => sub { ref($_) eq 'SCALAR' },
67         ArrayRef   => sub { ref($_) eq 'ARRAY'  },
68         HashRef    => sub { ref($_) eq 'HASH'   },
69         CodeRef    => sub { ref($_) eq 'CODE'   },
70         RegexpRef  => sub { ref($_) eq 'Regexp' },
71         GlobRef    => sub { ref($_) eq 'GLOB'   },
72
73         FileHandle => sub {
74             ref($_) eq 'GLOB' && openhandle($_)
75             or
76             blessed($_) && $_->isa("IO::Handle")
77         },
78
79         Object     => sub { blessed($_) && blessed($_) ne 'Regexp' },
80     );
81
82     sub optimized_constraints { \%TYPE }
83     my @TYPE_KEYS = keys %TYPE;
84     sub list_all_builtin_type_constraints { @TYPE_KEYS }
85 }
86
87 sub _type {
88     my $pkg = caller(0);
89     my($name, %conf) = @_;
90     if (my $type = $TYPE{$name}) {
91         Carp::croak "The type constraint '$name' has already been created, cannot be created again in $pkg";
92     };
93     my $stuff = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
94     $TYPE{$name} = $stuff;
95 }
96
97 sub _subtype {
98     my $pkg = caller(0);
99     my($name, %conf) = @_;
100     if (my $type = $TYPE{$name}) {
101         Carp::croak "The type constraint '$name' has already been created, cannot be created again in $pkg";
102     };
103     my $stuff = $conf{where} || do { $TYPE{delete $conf{as} || 'Any' } };
104     my $as    = $conf{as} || '';
105     if ($as = $TYPE{$as}) {
106         $TYPE{$name} = sub { $as->($_) && $stuff->($_) };
107     } else {
108         $TYPE{$name} = $stuff;
109     }
110 }
111
112 sub _coerce {
113     my($name, %conf) = @_;
114
115     Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
116         unless $TYPE{$name};
117
118     unless ($COERCE{$name}) {
119         $COERCE{$name}      = {};
120         $COERCE_KEYS{$name} = [];
121     }
122     while (my($type, $code) = each %conf) {
123         Carp::croak "A coercion action already exists for '$type'"
124             if $COERCE{$name}->{$type};
125
126         Carp::croak "Could not find the type constraint ($type) to coerce from"
127             unless $TYPE{$type};
128
129         push @{ $COERCE_KEYS{$name} }, $type;
130         $COERCE{$name}->{$type} = $code;
131     }
132 }
133
134 sub _class_type {
135     my $pkg = caller(0);
136     my($name, $conf) = @_;
137     my $class = $conf->{class};
138     Mouse::load_class($class);
139     _subtype(
140         $name => where => sub { $_->isa($class) }
141     );
142 }
143
144 sub _role_type {
145     my($name, $conf) = @_;
146     my $role = $conf->{role};
147     _subtype(
148         $name => where => sub {
149             return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
150             $_->meta->does_role($role);
151         }
152     );
153 }
154
155 sub typecast_constraints {
156     my($class, $pkg, $type_constraint, $types, $value) = @_;
157
158     local $_;
159     for my $type (ref($types) eq 'ARRAY' ? @{ $types } : ( $types )) {
160         next unless $COERCE{$type};
161         for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
162             $_ = $value;
163             next unless $TYPE{$coerce_type}->();
164             $_ = $value;
165             $_ = $COERCE{$type}->{$coerce_type}->();
166             return $_ if $type_constraint->();
167         }
168     }
169     return $value;
170 }
171
172 1;
173
174 __END__
175
176 =head1 NAME
177
178 Mouse::Util::TypeConstraints - simple type constraints
179
180 =head1 METHODS
181
182 =head2 optimized_constraints -> HashRef[CODE]
183
184 Returns the simple type constraints that Mouse understands.
185
186 =cut
187
188