File Coverage

blib/lib/Callable.pm
Criterion Covered Total %
statement 102 102 100.0
branch 34 42 80.9
condition 18 26 69.2
subroutine 20 20 100.0
pod 1 1 100.0
total 175 191 91.6


line stmt bran cond sub pod time code
1             package Callable;
2              
3 2     2   74138 use 5.010;
  2         15  
4 2     2   11 use strict;
  2         4  
  2         41  
5 2     2   1416 use utf8;
  2         28  
  2         12  
6 2     2   68 use warnings;
  2         5  
  2         79  
7              
8 2     2   11 use Carp qw(croak);
  2         4  
  2         142  
9 2     2   1817 use Module::Load;
  2         2508  
  2         14  
10 2     2   136 use Scalar::Util qw(blessed);
  2         4  
  2         126  
11              
12 2     2   1204 use overload '&{}' => '_to_sub', '""' => '_to_string';
  2         998  
  2         17  
13 2         2821 use constant ( USAGE =>
14             'Usage: Callable->new(&|$|[object|"class"|"class->constructor", "method"])'
15 2     2   220 );
  2         4  
16              
17             our $VERSION = "0.02";
18              
19             our $DEFAULT_CLASS_CONSTRUCTOR = 'new';
20              
21             sub new {
22 23     23 1 18859 my ( $class, @options ) = @_;
23              
24 23 100 66     171 if ( @options
      66        
25             && blessed( $options[0] )
26             && $options[0]->isa(__PACKAGE__) )
27             {
28 5         17 return $options[0]->_clone( splice @options, 1 );
29             }
30              
31 18         59 my $self = bless { options => \@options }, $class;
32 18         45 $self->_validate_options();
33              
34 18         52 return $self;
35             }
36              
37             sub _clone {
38 5     5   11 my ( $self, @options ) = @_;
39              
40 5 100       11 if (@options) {
41 1         3 unshift @options, $self->{options}->[0];
42             }
43             else {
44 4         6 @options = @{ $self->{options} };
  4         10  
45             }
46              
47 5         25 return bless { options => \@options }, ref($self);
48             }
49              
50             sub _first_arg {
51 27     27   46 my ( $self, $value ) = @_;
52              
53 27 100       56 if ( @_ > 1 ) {
54 9         21 $self->{__first_arg} = $value;
55             }
56              
57 27 100       53 if (wantarray) {
58 18 100       53 return unless exists $self->{__first_arg};
59 9         25 return ( $self->{__first_arg} );
60             }
61              
62 9   50     27 return $self->{__first_arg} // undef;
63             }
64              
65             sub _handler {
66 35     35   65 my ( $self, $caller ) = @_;
67              
68 35 100       81 unless ( exists $self->{__handler} ) {
69 18         39 $self->{__handler} = $self->_make_handler($caller);
70             }
71              
72 35         102 return $self->{__handler};
73             }
74              
75             sub _make_handler {
76 18     18   26 my ( $self, $caller ) = @_;
77              
78 18         23 my ( $source, @default_args ) = @{ $self->{options} };
  18         38  
79 18         31 my $ref = ref $source;
80              
81 18 100       58 my $handler =
    100          
82             $ref eq 'CODE' ? $source
83             : (
84             $ref eq 'ARRAY' ? $self->_make_object_handler( $source, $caller )
85             : $self->_make_scalar_handler( $source, $caller )
86             );
87 18         37 my @args = ( $self->_first_arg, @default_args );
88              
89 18 100       42 if (@args) {
90 12         16 my $inner = $handler;
91 12     24   48 $handler = sub { $inner->( @args, @_ ) };
  24         63  
92             }
93              
94 18         48 return $handler;
95             }
96              
97             sub _make_object_handler {
98 7     7   13 my ( $self, $source, $caller ) = @_;
99              
100 7         9 my ( $object, $method, @args ) = @{$source};
  7         17  
101              
102 7 100       21 unless ( blessed $object) {
103 6         21 my ( $class, $constructor, $garbage ) = split /\Q->\E/, $object;
104              
105 6 50       13 croak "Wrong class name format: $object" if defined $garbage;
106              
107 6         27 load $class;
108              
109 6   66     1500 $constructor //= $DEFAULT_CLASS_CONSTRUCTOR;
110              
111 6         21 $object = $class->$constructor(@args);
112             }
113              
114 7         39 $self->_first_arg($object);
115              
116 7         29 return $object->can($method);
117             }
118              
119             sub _make_scalar_handler {
120 9     9   17 my ( $self, $name, $caller ) = @_;
121              
122 9         29 my @path = split /\Q->\E/, $name;
123 9 50       23 croak "Wrong subroutine name format: $name" if @path > 2;
124              
125 9 100       20 if ( @path == 2 ) {
126 2   66     9 $path[0] ||= $caller;
127 2         5 $self->_first_arg( $path[0] );
128 2         4 $name = join '::', @path;
129             }
130              
131 9         23 @path = split /::/, $name;
132              
133 9 100       18 if ( @path == 1 ) {
134 4         9 unshift @path, $caller;
135             }
136              
137 9         20 $name = join( '::', @path );
138 9         11 my $handler = \&{$name};
  9         27  
139              
140 9 50       19 croak "Unable to find subroutine: $name" if not $handler;
141              
142 9         20 return $handler;
143             }
144              
145             sub _to_string {
146 17     17   10928 my ($self) = @_;
147              
148 17         50 return $self->_to_sub( scalar caller )->();
149             }
150              
151             sub _to_sub {
152 35     35   411 my ( $self, $caller ) = @_;
153              
154 35   66     121 $caller //= caller;
155              
156 35         412 return $self->_handler($caller);
157             }
158              
159             sub _validate_options {
160 18     18   30 my ($self) = @_;
161              
162 18 50       22 croak USAGE unless @{ $self->{options} };
  18         77  
163              
164 18         55 my $source = $self->{options}->[0];
165 18         31 my $ref = ref($source);
166 18 50 100     98 croak USAGE unless $ref eq 'CODE' || $ref eq 'ARRAY' || $ref eq '';
      66        
167              
168 18 100       44 if ( $ref eq 'ARRAY' ) {
169 7 50       9 croak USAGE if @{$source} < 2;
  7         19  
170 7 50 66     37 croak USAGE unless blessed $source->[0] || ref( $source->[0] ) eq '';
171 7 50       18 croak USAGE if ref $source->[1];
172             }
173             }
174              
175             1;
176             __END__