File Coverage

blib/lib/Class/Tiny.pm
Criterion Covered Total %
statement 109 117 93.1
branch 43 54 79.6
condition 12 18 66.6
subroutine 19 21 90.4
pod 0 10 0.0
total 183 220 83.1


line stmt bran cond sub pod time code
1 9     9   15765 use 5.006;
  9         31  
  9         327  
2 9     9   48 use strict;
  9         15  
  9         312  
3 9     9   56 no strict 'refs';
  9         17  
  9         223  
4 9     9   43 use warnings;
  9         15  
  9         526  
5              
6             package Class::Tiny;
7             # ABSTRACT: Minimalist class construction
8             our $VERSION = '0.014'; # VERSION
9              
10 9     9   51 use Carp ();
  9         28  
  9         8846  
11              
12             # load as .pm to hide from min version scanners
13             require( $] >= 5.010 ? "mro.pm" : "MRO/Compat.pm" ); ## no critic:
14              
15             my %CLASS_ATTRIBUTES;
16              
17             sub import {
18 16     16   899 my $class = shift;
19 16         631 my $pkg = caller;
20 16         905 $class->prepare_class($pkg);
21 16 50       1835 $class->create_attributes( $pkg, @_ ) if @_;
22             }
23              
24             sub prepare_class {
25 16     16 0 41 my ( $class, $pkg ) = @_;
26 16 100       23 @{"${pkg}::ISA"} = "Class::Tiny::Object" unless @{"${pkg}::ISA"};
  9         166  
  16         652  
27             }
28              
29             # adapted from Object::Tiny and Object::Tiny::RW
30             sub create_attributes {
31 16     16 0 64 my ( $class, $pkg, @spec ) = @_;
32 16 100       30 my %defaults = map { ref $_ eq 'HASH' ? %$_ : ( $_ => undef ) } @spec;
  30         150  
33 34 50 33     397 my @attr = grep {
      33        
34 16         49 defined and !ref and /^[^\W\d]\w*$/s
35             or Carp::croak "Invalid accessor name '$_'"
36             } keys %defaults;
37 16         981 $CLASS_ATTRIBUTES{$pkg}{$_} = $defaults{$_} for @attr;
38 16         32 _gen_accessor( $pkg, $_ ) for grep { !*{"$pkg\::$_"}{CODE} } @attr;
  34         45  
  34         190  
39 16 50       859 Carp::croak("Failed to generate attributes for $pkg: $@\n") if $@;
40             }
41              
42             sub _gen_accessor {
43 33     33   56 my ( $pkg, $name ) = @_;
44 33         104 my $outer_default = $CLASS_ATTRIBUTES{$pkg}{$name};
45              
46 33         69 my $sub = "sub $name { if (\@_ == 1) {";
47 33 100 100     145 if ( defined $outer_default && ref $outer_default eq 'CODE' ) {
    100          
48 4         29 $sub .= "if ( !exists \$_[0]{$name} ) { \$_[0]{$name} = \$default->(\$_[0]) }";
49             }
50             elsif ( defined $outer_default ) {
51 4         13 $sub .= "if ( !exists \$_[0]{$name} ) { \$_[0]{$name} = \$default }";
52             }
53 33         79 $sub .= "return \$_[0]{$name} } else { return \$_[0]{$name}=\$_[1] } }";
54              
55             # default = outer_default avoids "won't stay shared" bug
56 33 100   39 0 3564 eval "package $pkg; my \$default=\$outer_default; $sub"; ## no critic
  39 100   55 0 461  
  39 100   29 0 251  
  0 100   12 0 0  
  56 100   0 0 16705  
  52 50   0 0 263  
  3 0       19  
  29 0       104  
  21         103  
  11         62  
  11         79  
  7         36  
  5         349  
  4         14  
  3         13  
  4         183  
  0            
  0            
  0            
  0            
  0            
  0            
  0            
57 33 50       145 Carp::croak("Failed to generate attributes for $pkg: $@\n") if $@;
58             }
59              
60             sub get_all_attributes_for {
61 4     4 0 5317 my ( $class, $pkg ) = @_;
62 19         43 my %attr =
63 13 100       75 map { $_ => undef }
64 4         26 map { keys %{ $CLASS_ATTRIBUTES{$_} || {} } } @{ mro::get_linear_isa($pkg) };
  13         21  
  4         44  
65 4         58 return keys %attr;
66             }
67              
68             sub get_all_attribute_defaults_for {
69 2     2 0 9190 my ( $class, $pkg ) = @_;
70 2         6 my $defaults = {};
71 2         6 for my $p ( reverse @{ mro::get_linear_isa($pkg) } ) {
  2         12  
72 5 100       7 while ( my ( $k, $v ) = each %{ $CLASS_ATTRIBUTES{$p} || {} } ) {
  16         82  
73 11         43 $defaults->{$k} = $v;
74             }
75             }
76 2         7 return $defaults;
77             }
78              
79             package Class::Tiny::Object;
80             # ABSTRACT: Base class for classes built with Class::Tiny
81             our $VERSION = '0.014'; # VERSION
82              
83             my ( %LINEAR_ISA_CACHE, %BUILD_CACHE, %DEMOLISH_CACHE, %CAN_CACHE );
84              
85             my $_PRECACHE = sub {
86             my ($class) = @_;
87             $LINEAR_ISA_CACHE{$class} =
88             @{"$class\::ISA"} == 1 && ${"$class\::ISA"}[0] eq "Class::Tiny::Object"
89             ? [$class]
90             : mro::get_linear_isa($class);
91             for my $s ( @{ $LINEAR_ISA_CACHE{$class} } ) {
92 9     9   66 no warnings 'once'; # needed to avoid downstream warnings
  9         24  
  9         4942  
93             $BUILD_CACHE{$s} = *{"$s\::BUILD"}{CODE};
94             $DEMOLISH_CACHE{$s} = *{"$s\::DEMOLISH"}{CODE};
95             }
96             return $LINEAR_ISA_CACHE{$class};
97             };
98              
99             sub new {
100 44     44   61482 my $class = shift;
101 44   66     214 my $linear_isa = $LINEAR_ISA_CACHE{$class} || $_PRECACHE->($class);
102              
103             # handle hash ref or key/value arguments
104 44         56 my $args;
105 44 100 66     276 if ( @_ == 1 && ref $_[0] ) {
    100          
106 10         19 my %copy = eval { %{ $_[0] } }; # try shallow copy
  10         17  
  10         47  
107 10 100       146 Carp::croak("Argument to $class->new() could not be dereferenced as a hash") if $@;
108 9         19 $args = \%copy;
109             }
110             elsif ( @_ % 2 == 0 ) {
111 33         109 $args = {@_};
112             }
113             else {
114 1         130 Carp::croak("$class->new() got an odd number of elements");
115             }
116              
117             # create object and invoke BUILD
118 42         172 my $self = bless {%$args}, $class;
119 42         99 for my $s ( reverse @$linear_isa ) {
120 97 100       307 next unless my $builder = $BUILD_CACHE{$s};
121 17         36 $builder->( $self, $args );
122             }
123              
124             # unknown attributes still in $args are fatal
125 40         88 my @bad;
126 40         98 for my $k ( keys %$args ) {
127 75 100 100     620 push( @bad, $k )
128             unless $CAN_CACHE{$class}{$k} ||= $self->can($k); # a heuristic to catch typos
129             }
130 40 100       1137 Carp::croak("Invalid attributes for $class: @bad") if @bad;
131              
132 36         147 return $self;
133             }
134              
135             # Adapted from Moo and its dependencies
136             require Devel::GlobalDestruction unless defined ${^GLOBAL_PHASE};
137              
138             sub DESTROY {
139 42     42   20262 my $self = shift;
140 42         85 my $class = ref $self;
141 42 50       198 my $in_global_destruction =
142             defined ${^GLOBAL_PHASE}
143             ? ${^GLOBAL_PHASE} eq 'DESTRUCT'
144             : Devel::GlobalDestruction::in_global_destruction();
145 42         65 for my $s ( @{ $LINEAR_ISA_CACHE{$class} } ) {
  42         114  
146 98 100       545 next unless my $demolisher = $DEMOLISH_CACHE{$s};
147 18         18 my $e = do {
148 18         34 local ( $?, $@ );
149 18         21 eval { $demolisher->( $self, $in_global_destruction ) };
  18         40  
150 18         105 $@;
151             };
152 9     9   55 no warnings 'misc'; # avoid (in cleanup) warnings
  9         21  
  9         698  
153 18 50       70 die $e if $e; # rethrow
154             }
155             }
156              
157             1;
158              
159              
160             # vim: ts=4 sts=4 sw=4 et:
161              
162             __END__