File Coverage

blib/lib/Type/Tiny/Duck.pm
Criterion Covered Total %
statement 79 81 97.5
branch 17 26 65.3
condition 4 9 44.4
subroutine 22 22 100.0
pod 5 5 100.0
total 127 143 88.8


line stmt bran cond sub pod time code
1             package Type::Tiny::Duck;
2              
3 33     33   72662 use 5.008001;
  33         135  
4 33     33   180 use strict;
  33         71  
  33         761  
5 33     33   208 use warnings;
  33         94  
  33         1564  
6              
7             BEGIN {
8 33     33   128 $Type::Tiny::Duck::AUTHORITY = 'cpan:TOBYINK';
9 33         1517 $Type::Tiny::Duck::VERSION = '2.003_000';
10             }
11              
12             $Type::Tiny::Duck::VERSION =~ tr/_//d;
13              
14 33     33   249 use Scalar::Util qw< blessed >;
  33         68  
  33         3358  
15              
16 1     1   6 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  1         4  
17              
18 33     33   852 use Exporter::Tiny 1.004001 ();
  33         5321  
  33         803  
19 33     33   2269 use Type::Tiny::ConstrainedObject ();
  33         95  
  33         45361  
20             our @ISA = qw( Type::Tiny::ConstrainedObject Exporter::Tiny );
21              
22 3     3   10 sub _short_name { 'Duck' }
23              
24             sub _exporter_fail {
25 1     1   169 my ( $class, $type_name, $methods, $globals ) = @_;
26 1         2 my $caller = $globals->{into};
27 1         4 my $type = $class->new(
28             name => $type_name,
29             methods => [ @$methods ],
30             );
31             $INC{'Type/Registry.pm'}
32             ? 'Type::Registry'->for_class( $caller )->add_type( $type, $type_name )
33             : ( $Type::Registry::DELAYED{$caller}{$type_name} = $type )
34 1 50 33     24 unless( ref($caller) or $caller eq '-lexical' or $globals->{'lexical'} );
    50 33        
35 1         2 return map +( $_->{name} => $_->{code} ), @{ $type->exportables };
  1         11  
36             }
37              
38             sub new {
39 66     66 1 229 my $proto = shift;
40            
41 66 50       339 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
42 66 100       217 _croak "Need to supply list of methods" unless exists $opts{methods};
43            
44 65 50       193 $opts{methods} = [ $opts{methods} ] unless ref $opts{methods};
45            
46 65         115 if ( Type::Tiny::_USE_XS ) {
47 65         111 my $methods = join ",", sort( @{ $opts{methods} } );
  65         318  
48 65         979 my $xsub = Type::Tiny::XS::get_coderef_for( "HasMethods[$methods]" );
49 65 100       4290 $opts{compiled_type_constraint} = $xsub if $xsub;
50             }
51             elsif ( Type::Tiny::_USE_MOUSE ) {
52             require Mouse::Util::TypeConstraints;
53             my $maker = "Mouse::Util::TypeConstraints"->can( "generate_can_predicate_for" );
54             $opts{compiled_type_constraint} = $maker->( $opts{methods} ) if $maker;
55             }
56            
57 65         470 return $proto->SUPER::new( %opts );
58             } #/ sub new
59              
60             sub _lockdown {
61 62     62   159 my ( $self, $callback ) = @_;
62 62         209 $callback->( $self->{methods} );
63             }
64              
65 147     147 1 982 sub methods { $_[0]{methods} }
66 220   66 220 1 1348 sub inlined { $_[0]{inlined} ||= $_[0]->_build_inlined }
67              
68 441     441 1 1472 sub has_inlined { !!1 }
69              
70 718     718   1826 sub _is_null_constraint { 0 }
71              
72             sub _build_constraint {
73 9     9   24 my $self = shift;
74 9         18 my @methods = @{ $self->methods };
  9         25  
75             return sub {
76 29 50   29   309 blessed( $_[0] )
77             and not grep( !$_[0]->can( $_ ), @methods );
78 9         72 };
79             }
80              
81             sub _build_inlined {
82 59     59   480 my $self = shift;
83 59         99 my @methods = @{ $self->methods };
  59         146  
84            
85 59         102 my $xsub;
86 59         91 if ( Type::Tiny::_USE_XS ) {
87 59         117 my $methods = join ",", sort( @{ $self->methods } );
  59         118  
88 59         670 $xsub = Type::Tiny::XS::get_subname_for( "HasMethods[$methods]" );
89             }
90            
91             sub {
92 220     220   330 my $var = $_[1];
93 220         359 local $" = q{ };
94            
95             # If $var is $_ or $_->{foo} or $foo{$_} or somesuch, then we
96             # can't use it within the grep expression, so we need to save
97             # it into a temporary variable ($tmp).
98 220 100       1123 my $code =
99             ( $var =~ /\$_/ )
100             ? qq{ Scalar::Util::blessed($var) and not do { my \$tmp = $var; grep(!\$tmp->can(\$_), qw/@methods/) } }
101             : qq{ Scalar::Util::blessed($var) and not grep(!$var->can(\$_), qw/@methods/) };
102            
103 220 100       574 return qq{do { $Type::Tiny::SafePackage use Scalar::Util (); $code }}
104             if $Type::Tiny::AvoidCallbacks;
105 195 50       735 return "$xsub\($var\)"
106             if $xsub;
107 0         0 $code;
108 59         921 };
109             } #/ sub _build_inlined
110              
111             sub _instantiate_moose_type {
112 1     1   2 my $self = shift;
113 1         3 my %opts = @_;
114 1         3 delete $opts{parent};
115 1         1 delete $opts{constraint};
116 1         3 delete $opts{inlined};
117            
118 1         4 require Moose::Meta::TypeConstraint::DuckType;
119 1         5 return "Moose::Meta::TypeConstraint::DuckType"
120             ->new( %opts, methods => $self->methods );
121             } #/ sub _instantiate_moose_type
122              
123             sub validate_explain {
124 1     1 1 2 my $self = shift;
125 1         3 my ( $value, $varname ) = @_;
126 1 50       3 $varname = '$_' unless defined $varname;
127            
128 1 50       9 return undef if $self->check( $value );
129 1 50       7 return ["Not a blessed reference"] unless blessed( $value );
130            
131 1         577 require Type::Utils;
132             return [
133             sprintf(
134             '"%s" requires that the reference can %s',
135             $self,
136 1         11 Type::Utils::english_list( map qq["$_"], @{ $self->methods } ),
137             ),
138             map sprintf( 'The reference cannot "%s"', $_ ),
139             grep !$value->can( $_ ),
140 1         26 @{ $self->methods }
  1         10  
141             ];
142             } #/ sub validate_explain
143              
144             push @Type::Tiny::CMP, sub {
145             my $A = shift->find_constraining_type;
146             my $B = shift->find_constraining_type;
147             return Type::Tiny::CMP_UNKNOWN
148             unless $A->isa( __PACKAGE__ ) && $B->isa( __PACKAGE__ );
149            
150             my %seen;
151             for my $word ( @{ $A->methods } ) {
152             $seen{$word} += 1;
153             }
154             for my $word ( @{ $B->methods } ) {
155             $seen{$word} += 2;
156             }
157            
158             my $values = join( '', CORE::values %seen );
159             if ( $values =~ /^3*$/ ) {
160             return Type::Tiny::CMP_EQUIVALENT;
161             }
162             elsif ( $values !~ /2/ ) {
163             return Type::Tiny::CMP_SUBTYPE;
164             }
165             elsif ( $values !~ /1/ ) {
166             return Type::Tiny::CMP_SUPERTYPE;
167             }
168            
169             return Type::Tiny::CMP_UNKNOWN;
170             };
171              
172             1;
173              
174             __END__
175              
176             =pod
177              
178             =encoding utf-8
179              
180             =head1 NAME
181              
182             Type::Tiny::Duck - type constraints based on the "can" method
183              
184             =head1 SYNOPSIS
185              
186             Using via L<Types::Standard>:
187              
188             package Logger {
189             use Moo;
190             use Types::Standard qw( HasMethods Bool );
191            
192             has debugging => ( is => 'rw', isa => Bool, default => 0 );
193             has output => ( is => 'ro', isa => HasMethods[ 'print' ] );
194            
195             sub warn {
196             my ( $self, $message ) = @_;
197             $self->output->print( "[WARNING] $message\n" );
198             }
199            
200             sub debug {
201             my ( $self, $message ) = @_;
202             $self->output->print( "[DEBUG] $message\n" ) if $self->debugging;
203             }
204             }
205              
206             Using Type::Tiny::Duck's export feature:
207              
208             package Logger {
209             use Moo;
210             use Types::Standard qw( Bool );
211             use Type::Tiny::Duck Printable => [ 'print' ];
212            
213             has debugging => ( is => 'rw', isa => Bool, default => 0 );
214             has output => ( is => 'ro', isa => Printable );
215            
216             sub warn {
217             my ( $self, $message ) = @_;
218             $self->output->print( "[WARNING] $message\n" );
219             }
220            
221             sub debug {
222             my ( $self, $message ) = @_;
223             $self->output->print( "[DEBUG] $message\n" ) if $self->debugging;
224             }
225             }
226              
227             Using Type::Tiny::Duck's object-oriented interface:
228              
229             package Logger {
230             use Moo;
231             use Types::Standard qw( Bool );
232             use Type::Tiny::Duck;
233            
234             my $Printable = Type::Type::Duck->new(
235             name => 'Printable',
236             methods => [ 'print' ],
237             );
238            
239             has debugging => ( is => 'rw', isa => Bool, default => 0 );
240             has output => ( is => 'ro', isa => $Printable );
241            
242             sub warn {
243             my ( $self, $message ) = @_;
244             $self->output->print( "[WARNING] $message\n" );
245             }
246            
247             sub debug {
248             my ( $self, $message ) = @_;
249             $self->output->print( "[DEBUG] $message\n" ) if $self->debugging;
250             }
251             }
252              
253             =head1 STATUS
254              
255             This module is covered by the
256             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
257              
258             =head1 DESCRIPTION
259              
260             Type constraints of the general form C<< { $_->can("method") } >>.
261              
262             The name refers to the saying, "If it looks like a duck, swims like a duck,
263             and quacks like a duck, then it probably is a duck". Duck typing can be
264             a more flexible way of testing objects than relying on C<isa>, as it allows
265             people to easily substitute mock objects.
266              
267             This package inherits from L<Type::Tiny>; see that for most documentation.
268             Major differences are listed below:
269              
270             =head2 Attributes
271              
272             =over
273              
274             =item C<methods>
275              
276             An arrayref of method names.
277              
278             =item C<constraint>
279              
280             Unlike Type::Tiny, you I<cannot> pass a constraint coderef to the constructor.
281             Instead rely on the default.
282              
283             =item C<inlined>
284              
285             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
286             Instead rely on the default.
287              
288             =item C<parent>
289              
290             Parent is always B<Types::Standard::Object>, and cannot be passed to the
291             constructor.
292              
293             =back
294              
295             =head2 Methods
296              
297             =over
298              
299             =item C<< stringifies_to($constraint) >>
300              
301             See L<Type::Tiny::ConstrainedObject>.
302              
303             =item C<< numifies_to($constraint) >>
304              
305             See L<Type::Tiny::ConstrainedObject>.
306              
307             =item C<< with_attribute_values($attr1 => $constraint1, ...) >>
308              
309             See L<Type::Tiny::ConstrainedObject>.
310              
311             =back
312              
313             =head2 Exports
314              
315             Type::Tiny::Duck can be used as an exporter.
316              
317             use Type::Tiny::Duck HttpClient => [ 'get', 'post' ];
318              
319             This will export the following functions into your namespace:
320              
321             =over
322              
323             =item C<< HttpClient >>
324              
325             =item C<< is_HttpClient( $value ) >>
326              
327             =item C<< assert_HttpClient( $value ) >>
328              
329             =item C<< to_HttpClient( $value ) >>
330              
331             =back
332              
333             Multiple types can be exported at once:
334              
335             use Type::Tiny::Duck (
336             HttpClient => [ 'get', 'post' ],
337             FtpClient => [ 'upload', 'download' ],
338             );
339              
340             =head1 BUGS
341              
342             Please report any bugs to
343             L<https://github.com/tobyink/p5-type-tiny/issues>.
344              
345             =head1 SEE ALSO
346              
347             L<Type::Tiny::Manual>.
348              
349             L<Type::Tiny>.
350              
351             L<Moose::Meta::TypeConstraint::DuckType>.
352              
353             =head1 AUTHOR
354              
355             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
356              
357             =head1 COPYRIGHT AND LICENCE
358              
359             This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
360              
361             This is free software; you can redistribute it and/or modify it under
362             the same terms as the Perl 5 programming language system itself.
363              
364             =head1 DISCLAIMER OF WARRANTIES
365              
366             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
367             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
368             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.