File Coverage

blib/lib/Types/QuacksLike.pm
Criterion Covered Total %
statement 50 68 73.5
branch 14 40 35.0
condition 9 24 37.5
subroutine 10 11 90.9
pod n/a
total 83 143 58.0


line stmt bran cond sub pod time code
1             package Types::QuacksLike;
2 3     3   239392 use strict;
  3         23  
  3         90  
3 3     3   15 use warnings;
  3         6  
  3         169  
4              
5             our $VERSION = '0.001001';
6             $VERSION =~ tr/_//d;
7              
8 3     3   1750 use Type::Library -base;
  3         80418  
  3         31  
9 3     3   2833 use Types::Standard qw(ClassName Object);
  3         158411  
  3         31  
10              
11             BEGIN {
12 3 50   3   3224 if ("$]" >= 5.010_000) {
13 3         24 require mro;
14 3         654 *_linear_isa = \&mro::get_linear_isa;
15             }
16             else {
17 0         0 local $@;
18             # we don't care about order so we can ignore c3
19 0 0       0 eval <<'END_CODE' or die $@;
20             sub _linear_isa($;$) {
21             my $class = shift;
22             my @check = ($class);
23             my @lin;
24              
25             my %found;
26             while (defined(my $check = shift @check)) {
27             push @lin, $check;
28             no strict 'refs';
29             unshift @check, grep !$found{$_}++, @{"$check\::ISA"};
30             }
31              
32             return \@lin;
33             }
34             END_CODE
35             }
36             }
37              
38             BEGIN {
39 3     3   12 local $@;
40 3 50       8 if (eval { require Sub::Util; defined &Sub::Util::subname }) {
  3         19  
  3         15  
41             *_stash_name = sub {
42 7     7   34 my $name = Sub::Util::subname($_[0]);
43 7         42 $name =~ s{::[^:]+\z}{};
44 7         22 $name;
45 3         115 };
46             }
47             else {
48 0         0 require B;
49             *_stash_name = sub {
50 0         0 my ($coderef) = @_;
51 0 0       0 ref $coderef or return;
52 0         0 my $cv = B::svref_2object($coderef);
53 0 0       0 $cv->isa('B::CV') or return;
54 0 0       0 $cv->GV->isa('B::SPECIAL') and return;
55 0         0 $cv->GV->STASH->NAME;
56 0         0 };
57             }
58             }
59              
60             sub _methods_from_package {
61 0     7   0 my $package = shift;
62 3     3   17 no strict 'refs';
  3     0   5  
  3         2028  
63 7 50       15 my $does
    50          
64             = $package->can('does') ? 'does'
65             : $package->can('DOES') ? 'DOES'
66             : undef;
67 7         66 my $stash = \%{"${package}::"};
  7         23  
68             return
69             grep {
70 7         19 my $code = \&{"${package}::$_"};
  7         9  
71 7 50       18 my $code_stash = _stash_name($code) or next;
72              
73 7 50 0     15 /\A\(/
      33        
      33        
74             or $code_stash eq $package
75             or $code_stash eq 'constant'
76             or $does && $package->$does($code_stash)
77             }
78             grep {
79 7         21 my $entry = $stash->{$_};
  7         62  
80 31 50 33     73 defined $entry && ref $entry ne 'HASH' && exists &{"${package}::$_"};
  31         99  
81             } keys %$stash;
82             }
83              
84             sub _methods_of {
85 31     8   110 my $package = shift;
86 8         17 my @methods;
87 8 100 66     13 if ($INC{'Moo/Role.pm'} && Moo::Role->is_role($package)) {
    100 66        
    50 33        
88 8         75 @methods = Moo::Role->methods_provided_by($package);
89             }
90             elsif ($INC{'Role/Tiny.pm'} && Role::Tiny->is_role($package)) {
91 2         76 @methods = Role::Tiny->methods_provided_by($package);
92             }
93             elsif ($INC{'Class/MOP.pm'} and my $meta = Class::MOP::class_of($package)) {
94             # classes
95 2 0       35 if ($meta->can('get_all_method_names')) {
    0          
    0          
96 0         0 @methods = $meta->get_all_method_names;
97             }
98             # roles
99             elsif ($meta->can('get_method_list')) {
100 0         0 @methods = $meta->get_method_list;
101             }
102             # packages
103             elsif ($meta->can('list_all_symbols')) {
104 0         0 @methods = $meta->list_all_symbols('CODE');
105             }
106             }
107             else {
108 0         0 my $moo_method;
109 4 50       7 if ($INC{'Moo.pm'}) {
110 4 0       10 $moo_method = Moo->can('is_class') ? 'is_class' : '_accessor_maker_for';
111             }
112              
113 0         0 my %s;
114 4         5 for my $isa (@{_linear_isa($package)}) {
  4         8  
115 4 50 33     23 if ($moo_method && Moo->$moo_method($isa)) {
116 7         19 push @methods, grep !$s{$_}++, keys %{ Moo->_concrete_methods_of($isa) };
  0         0  
117             }
118             else {
119 0         0 push @methods, grep !$s{$_}++, _methods_from_package($isa);
120             }
121             }
122             }
123              
124 7         15 return grep !/\A_/, sort @methods;
125             }
126              
127             my $meta = __PACKAGE__->meta;
128             my $class_name = ClassName;
129              
130             $meta->add_type({
131             name => "QuacksLike",
132             parent => Object,
133             constraint_generator => sub {
134             my @packages = map $class_name->assert_return($_), @_;
135             return Object unless @packages;
136              
137             my %s;
138             my @methods = sort grep !$s{$_}++, map _methods_of($_), @packages;
139              
140             require Type::Tiny::Duck;
141             return Type::Tiny::Duck->new(
142             methods => \@methods,
143             display_name => sprintf('QuacksLike[%s]', join q[,], map qq{"$_"}, @packages),
144             );
145             },
146             });
147              
148             1;
149             __END__