File Coverage

blib/lib/Callable.pm
Criterion Covered Total %
statement 98 98 100.0
branch 34 42 80.9
condition 18 26 69.2
subroutine 19 19 100.0
pod 1 1 100.0
total 170 186 91.4


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