File Coverage

blib/lib/MooseX/Types/VariantTable.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package MooseX::Types::VariantTable;
4 2     2   60912 use Moose;
  0            
  0            
5              
6             use Hash::Util::FieldHash::Compat qw(idhash);
7             use Scalar::Util qw(refaddr);
8              
9             use Moose::Util::TypeConstraints;
10              
11             use namespace::clean -except => 'meta';
12              
13             with qw(MooseX::Clone);
14              
15             use Carp qw(croak);
16              
17             our $VERSION = "0.04";
18              
19             has _sorted_variants => (
20             traits => [qw(NoClone)],
21             #isa => "ArrayRef[ArrayRef[HashRef]]",
22             is => "ro",
23             lazy_build => 1,
24             );
25              
26             has variants => (
27             traits => [qw(Copy)],
28             isa => "ArrayRef[HashRef]",
29             is => "rw",
30             init_arg => undef,
31             default => sub { [] },
32             trigger => sub { $_[0]->_clear_sorted_variants },
33             );
34              
35             has ambigious_match_callback => (
36             is => 'ro',
37             isa => 'CodeRef',
38             default => sub {
39             sub {
40             my ($self, $value, @matches) = @_;
41             croak "Ambiguous match " . join(", ", map { $_->{type} } @matches);
42             };
43             },
44             );
45              
46             sub BUILD {
47             my ( $self, $params ) = @_;
48              
49             if ( my $variants = $params->{variants} ) {
50             foreach my $variant ( @$variants ) {
51             $self->add_variant( @{ $variant }{qw(type value)} );
52             }
53             }
54             }
55              
56             sub merge {
57             my ( @selves ) = @_; # our @selves reads better =/
58              
59             my $self = $selves[0];
60              
61             return ( ref $self )->new(
62             variants => [ map { @{ $_->variants } } @selves ],
63             );
64             }
65              
66             sub has_type {
67             my ( $self, $type_or_name ) = @_;
68              
69             my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
70             or croak "No such type constraint: $type_or_name";
71              
72             foreach my $existing_type ( map { $_->{type} } @{ $self->variants } ) {
73             return 1 if $type->equals($existing_type);
74             }
75              
76             return;
77             }
78              
79             sub has_parent {
80             my ( $self, $type_or_name ) = @_;
81              
82             my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
83             or croak "No such type constraint: $type_or_name";
84              
85             foreach my $existing_type ( map { $_->{type} } @{ $self->variants } ) {
86             return 1 if $type->is_subtype_of($existing_type);
87             }
88              
89             return;
90             }
91              
92             sub add_variant {
93             my ( $self, $type_or_name, $value ) = @_;
94              
95             croak "Duplicate variant entry for $type_or_name"
96             if $self->has_type($type_or_name);
97              
98             my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
99             or croak "No such type constraint: $type_or_name";
100              
101             my $entry = { type => $type, value => $value };
102              
103             push @{ $self->variants }, $entry;
104              
105             $self->_clear_sorted_variants;
106              
107             return;
108             }
109              
110             sub remove_variant {
111             my ( $self, $type_or_name, $value ) = @_;
112              
113             my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
114             or croak "No such type constraint: $type_or_name";
115              
116             my $list = $self->variants;
117              
118             @$list = grep { not $_->{type}->equals($type) } @$list;
119              
120             $self->_clear_sorted_variants;
121              
122             return;
123             }
124              
125             sub _build__sorted_variants {
126             my $self = shift;
127              
128             my @entries = @{ $self->variants };
129              
130             idhash my %out;
131              
132             foreach my $entry ( @entries ) {
133             $out{$entry} = [];
134             foreach my $other ( @entries ) {
135             next if refaddr($entry) == refaddr($other);
136              
137             if ( $other->{type}->is_subtype_of($entry->{type}) ) {
138             push @{ $out{$entry} }, $other;
139             }
140             }
141             }
142              
143             my @sorted;
144              
145             while ( keys %out ) {
146             my @slot;
147              
148             foreach my $entry ( @entries ) {
149             if ( $out{$entry} and not @{ $out{$entry} } ) {
150             push @slot, $entry;
151             delete $out{$entry};
152             }
153             }
154              
155             idhash my %filter;
156             @filter{@slot} = ();
157              
158             foreach my $entry ( @entries ) {
159             if ( my $out = $out{$entry} ) {
160             @$out = grep { not exists $filter{$_} } @$out;
161             }
162             }
163              
164             push @sorted, \@slot;
165             }
166              
167             return \@sorted;
168             }
169              
170             sub find_variant {
171             my ( $self, @args ) = @_;
172              
173             if ( my $entry = $self->_find_variant(@args) ) {
174             if ( wantarray ) {
175             return @{ $entry }{qw(value type)};
176             } else {
177             return $entry->{value};
178             }
179             }
180              
181             return;
182             }
183              
184             sub _find_variant {
185             my ( $self, $value ) = @_;
186              
187             foreach my $slot ( @{ $self->_sorted_variants } ) {
188             my @matches;
189             foreach my $entry ( @$slot ) {
190             if ( $entry->{type}->check($value) ) {
191             push @matches, $entry;
192             }
193             }
194             if ( @matches == 1 ) {
195             return $matches[0];
196             } elsif ( @matches > 1 ) {
197             $self->ambigious_match_callback->($self, $value, @matches);
198             }
199             }
200              
201             return;
202             }
203              
204             sub dispatch {
205             my $self = shift;
206             my $value = $_[0];
207              
208             if ( my $result = $self->find_variant($value) ) {
209             if ( (ref($result)||'') eq 'CODE' ) {
210             goto &$result;
211             } else {
212             return $result;
213             }
214             }
215              
216             return;
217             }
218              
219             __PACKAGE__
220              
221             __END__
222              
223             =pod
224              
225             =head1 NAME
226              
227             MooseX::Types::VariantTable - Type constraint based variant table
228              
229             =head1 SYNOPSIS
230              
231             # see also MooseX::Types::VariantTable::Declare for a way to
232             # declare variant table based methods
233              
234             use MooseX::Types::VariantTable;
235              
236             my $dispatch_table = MooseX::Types::VariantTable->new(
237             variants => [
238             { type => "Foo", value => \&foo_handler },
239             { type => "Bar", value => \&bar_handler },
240             { type => "Item", value => \&fallback },
241             ],
242             );
243              
244             # look up the correct handler for $thingy based on the type constraints it passes
245             my $entry = $dispatch_table->find_variant($thingy);
246              
247             # or use the 'dispatch' convenience method if the entries are code refs
248             $dispatch_table->dispatch( $thingy, @args );
249              
250             =head1 DESCRIPTION
251              
252             This object implements a simple dispatch table based on L<Moose> type constraints.
253              
254             Subtypes will be checked before their parents, meaning that the order of the
255             declaration does not matter.
256              
257             This object is used internally by L<Moose::Meta::Method::VariantTable> and
258             L<MooseX::Types::VariantTable::Declare> to provide primitive multi
259             sub support.
260              
261             =head1 ATTRIBUTES
262              
263             =head2 ambigious_match_callback
264              
265             A code reference that'll be executed when find_variant found more than one
266             matching variant for a value. It defaults to something that simply croaks with
267             an error message like this:
268              
269             Ambiguous match %s
270              
271             where %s contains a list of stringified types that matched.
272              
273             =head1 METHODS
274              
275             =over 4
276              
277             =item new
278              
279             =item add_variant $type, $value
280              
281             Registers C<$type>, such that C<$value> will be returned by C<find_variant> for
282             items passing $type.
283              
284             Subtyping is respected in the table.
285              
286             =item find_variant $value
287              
288             Returns the registered value for the most specific type that C<$value> passes.
289              
290             =item dispatch $value, @args
291              
292             A convenience method for when the registered values are code references.
293              
294             Calls C<find_variant> and if the result is a code reference, it will C<goto>
295             this code reference with the value and any additional arguments.
296              
297             =item has_type $type
298              
299             Returns true if an entry for C<$type> is registered.
300              
301             =item has_parent $type
302              
303             Returns true if a parent type of C<$type> is registered.
304              
305             =back
306              
307             =head1 TODO
308              
309             The meta method composes in multiple inheritence but not yet with roles due to
310             extensibility issues with the role application code.
311              
312             When L<Moose::Meta::Role> can pluggably merge methods variant table methods can
313             gain role composition.
314              
315             =head1 AUTHOR
316              
317             Yuval Kogman E<lt>nothingmuch@woobling.orgE<gt>
318              
319             Florian Ragwitz E<lt>rafl@debian.orgE<gt>
320              
321             =head1 COPYRIGHT
322              
323             Copyright (c) 2008 Yuval Kogman. All rights reserved
324             This program is free software; you can redistribute
325             it and/or modify it under the same terms as Perl itself.
326              
327             =cut