File Coverage

blib/lib/Class/Closure.pm
Criterion Covered Total %
statement 120 126 95.2
branch 12 18 66.6
condition 3 9 33.3
subroutine 31 32 96.8
pod 0 6 0.0
total 166 191 86.9


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