File Coverage

blib/lib/Class/Closure.pm
Criterion Covered Total %
statement 119 125 95.2
branch 12 18 66.6
condition 3 9 33.3
subroutine 31 32 96.8
pod 0 6 0.0
total 165 190 86.8


line stmt bran cond sub pod time code
1 1     1   56614 use 5.006; use warnings; use strict;
  1     1   3  
  1     1   4  
  1         2  
  1         20  
  1         4  
  1         2  
  1         43  
2              
3             package Class::Closure;
4             our $VERSION = '0.303';
5              
6 1     1   5 use Exporter ();
  1         1  
  1         27  
7 1     1   5 use Carp ();
  1         2  
  1         11  
8 1     1   420 use Symbol ();
  1         662  
  1         81  
9              
10             our @EXPORT = qw(
11             has
12             public
13             method
14             accessor
15             extends
16             does
17             destroy
18             );
19              
20             our $PACKAGE;
21             our $EXTENDS;
22              
23 2     2   18 sub import { _make_new( scalar caller ); goto &Exporter::import }
  2         1706  
24              
25             sub _install ($$) {
26 64     64   86 my ( $name, $thing ) = @_;
27 1     1   6 no strict 'refs';
  1         2  
  1         307  
28 64         66 *{ "$PACKAGE\::$name" } = $thing;
  64         261  
29             }
30              
31             sub _make_new {
32 2     2   4 my ( $pkg ) = @_;
33              
34 2         3 $PACKAGE = $pkg;
35             _install new => sub {
36 6   33 6   2151 my $base = ref $_[0] || $_[0];
37 6         12 local $PACKAGE = my $package = _make_package();
38              
39 6         18 _install ISA => [ $base ];
40              
41 6         12 my ( @reblessed, @subisa, %subobj );
42              
43             _install DESTROY => sub {
44 6     6   583 bless $_->[0], $_->[1] for @reblessed; # bless them back into their original class
45 6         16 Symbol::delete_package( $package );
46 6         21 };
47              
48             _install isa => sub {
49 0     0   0 my ( $self, $class ) = @_;
50 0 0       0 do { return 1 if $base->isa( $class ) };
  0         0  
51 0 0       0 do { return 1 if $_->isa( $class ) } for @subisa;
  0         0  
52 0         0 return;
53 6         22 };
54              
55             local $EXTENDS = sub {
56 4     4   7 my ( $var ) = @_;
57              
58 4 50       13 $var = $var->new if not ref $var;
59              
60 4         11 my $pkg = ref $var;
61 4         7 bless $var, $PACKAGE; # Rebless for virtual behavior
62              
63 4         7 push @reblessed, [ $var, $pkg ]; # bookkeeping for DESTROY
64              
65 4         5 push @subisa, $pkg;
66 4         5 $subobj{ $pkg } = $var;
67              
68 4         7 return;
69 6         20 };
70              
71             _install can => sub {
72 18     18   33 my ( $self, $method ) = @_;
73              
74 1     1   6 my $code = do { no strict 'refs'; *{ "$package\::$method" }{'CODE'} };
  1         2  
  1         110  
  18         19  
  18         17  
  18         52  
75 18 100       45 return $code if $code;
76              
77 13         20 for my $pkg ( @subisa ) {
78 6         10 my $obj = $subobj{ $pkg };
79 6 100       14 $code = $pkg->can( $method ) or next;
80             my $delegate = sub {
81 3         6 splice @_, 0, 1, $obj;
82 3         9 goto &$code;
83 3         10 };
84 1     1   6 { no strict 'refs'; *{ "$package\::$method" } = $delegate };
  1         2  
  1         145  
  3         5  
  3         4  
  3         7  
85 3         8 return $delegate;
86             }
87              
88 10         47 return;
89 6         30 };
90              
91             _install AUTOLOAD => sub {
92 5     5   465 our $AUTOLOAD =~ s/.*:://;
93 5 100       11 if ( my $code = $_[0]->can( $AUTOLOAD ) ) {
    100          
94 3         7 goto &$code;
95             }
96             elsif ( my $fallback = $_[0]->can( 'FALLBACK' ) ) {
97 1     1   17 no strict 'refs';
  1         3  
  1         677  
98 1         2 local *{ "$base\::AUTOLOAD" } = \$AUTOLOAD;
  1         3  
99 1         4 goto &$fallback;
100             }
101             else {
102 1         138 Carp::croak "Method $AUTOLOAD not found in class $base";
103             }
104 6         20 };
105              
106 6         32 $pkg->can( 'CLASS' )->( @_ );
107              
108 6         16 my $self = bless {}, $PACKAGE;
109              
110 6 100       14 $self->BUILD( @_[ 1 .. $#_ ] ) if $self->can( 'BUILD' );
111              
112 6         27 $self;
113 2         9 };
114             }
115              
116             {
117             my $counter = 0;
118             sub _make_package {
119 6     6   16 "Class::Closure::_package_" . $counter++;
120             }
121             }
122              
123             sub _find_name {
124 10     10   83 my ( $var, $code ) = @_;
125 10         27 require PadWalker;
126 10         14 my %names = reverse %{ PadWalker::peek_sub( $code ) };
  10         105  
127 10   33     36 my $name = $names{ $var } || Carp::croak "Couldn't find lexical name for $var";
128 10         37 $name =~ s/^[\$\@%]//;
129 10         26 $name;
130             }
131              
132             sub has (\$) : lvalue {
133 6     6 0 20 my ( $var ) = @_;
134              
135 6         398 require Devel::Caller;
136 6         2444 my $name = _find_name $var, Devel::Caller::caller_cv(1);
137              
138 6     3   22 _install $name, sub { $$var };
  3         292  
139 6         16 $$var;
140             }
141              
142             sub public (\$) : lvalue {
143 4     4 0 12 my ( $var ) = @_;
144              
145 4         11 require Devel::Caller;
146 4         9 my $name = _find_name $var, Devel::Caller::caller_cv(1);
147              
148 4     6   13 _install $name, sub : lvalue { $$var };
  6         25  
149 4         10 $$var;
150             }
151              
152             sub method ($&) {
153 12     12 0 51 &_install;
154 12         18 return;
155             }
156              
157             sub accessor ($@) {
158 4     4 0 24 my ( $name, %arg ) = @_;
159 4 50 33     19 Carp::croak "accessor needs 'get' and 'set' attributes" unless $arg{'get'} && $arg{'set'};
160 4         407 require Sentinel;
161             _install $name, sub : lvalue {
162 2     2   3 my $self = shift;
163             Sentinel::sentinel(
164 2         26 get => sub { $arg{'get'}->( $self ) },
165 1         4 set => sub { $arg{'set'}->( $self, @_ ) },
166 2         15 );
167 4         824 };
168 4         9 return;
169             }
170              
171 4     4 0 10 sub extends($) { &$EXTENDS }
172              
173 6     6 0 30 sub destroy(&) { _install DESTROY => \Class::Closure::DestroyDelegate->new( $_[0] ) }
174              
175             package Class::Closure::DestroyDelegate;
176             our $VERSION = '0.303';
177              
178 6     6   10 sub new { bless $_[1] }
179 6     6   330 sub DESTROY { goto &{$_[0]} }
  6         20  
180              
181             1;
182              
183             __END__