File Coverage

blib/lib/Object/HashBase.pm
Criterion Covered Total %
statement 140 157 89.1
branch 51 70 72.8
condition 11 21 52.3
subroutine 20 23 86.9
pod 1 4 25.0
total 223 275 81.0


line stmt bran cond sub pod time code
1             package Object::HashBase;
2 2     2   128542 use strict;
  2         6  
  2         87  
3 2     2   12 use warnings;
  2         5  
  2         234  
4              
5             our $VERSION = '0.015';
6             our $HB_VERSION = $VERSION;
7             # The next line is for inlining
8             # <-- START -->
9              
10             require Carp;
11             {
12 2     2   17 no warnings 'once';
  2         4  
  2         529  
13             $Carp::Internal{+__PACKAGE__} = 1;
14             }
15              
16             BEGIN {
17             {
18             # Make sure none of these get messed up.
19 2     2   8 local ($SIG{__DIE__}, $@, $?, $!, $^E);
  2         34  
20 2 50       6 if (eval { require Class::XSAccessor; Class::XSAccessor->VERSION(1.19); 1 }) {
  2         1198  
  2         6555  
  2         13  
21             *CLASS_XS_ACCESSOR = sub() { 1 }
22 2         21 }
23             else {
24             *CLASS_XS_ACCESSOR = sub() { 0 }
25 0         0 }
26             }
27              
28             # these are not strictly equivalent, but for out use we don't care
29             # about order
30             *_isa = ($] >= 5.010 && require mro) ? \&mro::get_linear_isa : sub {
31 2     2   15 no strict 'refs';
  2         45  
  2         359  
32 0         0 my @packages = ($_[0]);
33 0         0 my %seen;
34 0         0 for my $package (@packages) {
35 0         0 push @packages, grep !$seen{$_}++, @{"$package\::ISA"};
  0         0  
36             }
37 0         0 return \@packages;
38             }
39 2 50 33     942 }
40              
41             my %SPEC = (
42             '^' => {reader => 1, writer => 0, dep_writer => 1, read_only => 0, strip => 1},
43             '-' => {reader => 1, writer => 0, dep_writer => 0, read_only => 1, strip => 1},
44             '>' => {reader => 0, writer => 1, dep_writer => 0, read_only => 0, strip => 1},
45             '<' => {reader => 1, writer => 0, dep_writer => 0, read_only => 0, strip => 1},
46             '+' => {reader => 0, writer => 0, dep_writer => 0, read_only => 0, strip => 1},
47             '~' => {reader => 1, writer => 1, dep_writer => 0, read_only => 0, strip => 1, no_xs => 1},
48             );
49              
50 53     53 0 326 sub spec { \%SPEC }
51              
52             sub import {
53 25     25   1975 my $class = shift;
54 25         69 my $into = caller;
55 25         91 $class->do_import($into, @_);
56             }
57              
58             sub do_import {
59 25     25 0 64 my $class = shift;
60 25         75 my $into = shift;
61              
62             # Make sure we list the OLDEST version used to create this class.
63 25   33     83 my $ver = $Object::HashBase::HB_VERSION || $Object::HashBase::VERSION;
64 25 50 33     125 $Object::HashBase::VERSION{$into} = $ver if !$Object::HashBase::VERSION{$into} || $Object::HashBase::VERSION{$into} > $ver;
65              
66 25         162 my $isa = _isa($into);
67 25   50     163 my $attr_list = $Object::HashBase::ATTR_LIST{$into} ||= [];
68 25   50     103 my $attr_subs = $Object::HashBase::ATTR_SUBS{$into} ||= {};
69              
70 25         56 my @pre_init;
71             my @post_init;
72              
73 25         43 my $add_new = 1;
74              
75 25 100       282 if (my $have_new = $into->can('new')) {
76 9   50     24 my $new_lookup = $Object::HashBase::NEW_LOOKUP //= {};
77 9 100       42 $add_new = 0 unless $new_lookup->{$have_new};
78             }
79              
80             my %subs = (
81             ($add_new ? ($class->_build_new($into, \@pre_init, \@post_init)) : ()),
82 25 100       112 (map %{$Object::HashBase::ATTR_SUBS{$_} || {}}, @{$isa}[1 .. $#$isa]),
  8 50       84  
  25         100  
83             ($class->args_to_subs($attr_list, $attr_subs, \@_, $into)),
84             );
85              
86 2     2   18 no strict 'refs';
  2         5  
  2         3647  
87 25         131 while (my ($k, $v) = each %subs) {
88 279 100       651 if (ref($v) eq 'CODE') {
89 185         254 *{"$into\::$k"} = $v;
  185         3372  
90             }
91             else {
92 94         255 my ($sub, @args) = @$v;
93 94         1890 $sub->(@args);
94             }
95             }
96             }
97              
98             sub args_to_subs {
99 25     25 0 51 my $class = shift;
100 25         74 my ($attr_list, $attr_subs, $args, $into) = @_;
101              
102 25         108 my $use_gen = $class->can('gen_accessor') ;
103              
104 25         45 my %out;
105              
106 25         68 while (@$args) {
107 53         106 my $x = shift @$args;
108 53         123 my $p = substr($x, 0, 1);
109              
110 53   100     125 my $spec = $class->spec->{$p} || {reader => 1, writer => 1};
111 53 100       167 substr($x, 0, 1) = '' if $spec->{strip};
112              
113 53         142 push @$attr_list => $x;
114 53         165 my ($sub, $attr) = (uc $x, $x);
115              
116 53     0   476 $attr_subs->{$sub} = sub() { $attr };
  0         0  
117 53         164 $out{$sub} = $attr_subs->{$sub};
118              
119 53         120 my $copy = "$attr";
120 53 100       138 if ($spec->{reader}) {
121 49 50       133 if ($use_gen) {
    50          
122 0         0 $out{$attr} = $class->gen_accessor(reader => $copy, $spec, $args);
123             }
124             elsif (CLASS_XS_ACCESSOR && !$spec->{no_xs}) {
125 49         319 $out{$attr} = [\&Class::XSAccessor::newxs_getter, "$into\::$attr", $copy];
126             }
127             else {
128 0     0   0 $out{$attr} = sub { $_[0]->{$attr} };
  0         0  
129             }
130             }
131              
132 53 100       171 if ($spec->{writer}) {
    100          
    100          
133 45 50       109 if ($use_gen) {
    50          
134 0         0 $out{"set_$attr"} = $class->gen_accessor(writer => $copy, $spec, $args);
135             }
136             elsif(CLASS_XS_ACCESSOR && !$spec->{no_xs}) {
137 45         213 $out{"set_$attr"} = [\&Class::XSAccessor::newxs_setter, "$into\::set_$attr", $copy, 0];
138             }
139             else {
140 0     0   0 $out{"set_$attr"} = sub { $_[0]->{$attr} = $_[1] };
  0         0  
141             }
142             }
143             elsif($spec->{read_only}) {
144 2 50   1   25 $out{"set_$attr"} = $use_gen ? $class->gen_accessor(read_only => $copy, $spec, $args) : sub { Carp::croak("'$attr' is read-only") };
  1         271  
145             }
146             elsif($spec->{dep_writer}) {
147 2 50   1   14 $out{"set_$attr"} = $use_gen ? $class->gen_accessor(dep_writer => $copy, $spec, $args) : sub { Carp::carp("set_$attr() is deprecated"); $_[0]->{$attr} = $_[1] };
  1         168  
  1         8  
148             }
149              
150 53 50       261 if ($spec->{custom}) {
151 0         0 my %add = $class->gen_accessor(custom => $copy, $spec, $args);
152 0         0 $out{$_} = $add{$_} for keys %add;
153             }
154             }
155              
156 25         265 return %out;
157             }
158              
159             sub attr_list {
160 3     3 1 5 my $class = shift;
161              
162 3         14 my $isa = _isa($class);
163              
164 3         7 my %seen;
165 15         36 my @list = grep { !$seen{$_}++ } map {
166 3         8 my @out;
  6         8  
167              
168 6 50 50     27 if (0.004 > ($Object::HashBase::VERSION{$_} || 0)) {
169 0         0 Carp::carp("$_ uses an inlined version of Object::HashBase too old to support attr_list()");
170             }
171             else {
172 6         26 my $list = $Object::HashBase::ATTR_LIST{$_};
173 6 50       23 @out = $list ? @$list : ()
174             }
175              
176 6         15 @out;
177             } reverse @$isa;
178              
179 3         19 return @list;
180             }
181              
182             sub _build_new {
183 22     22   69 my $class = shift;
184 22         54 my ($into, $pre_init, $post_init) = @_;
185              
186 22     3   153 my $add_pre_init = sub(&) { push @$pre_init => $_[-1] };
  3         9  
187 22     4   152 my $add_post_init = sub(&) { push @$post_init => $_[-1] };
  4         12  
188              
189 22         130 my $__pre_init = $into->can('_pre_init');
190 22 100   12   114 my $_pre_init = $__pre_init ? sub { ($__pre_init->(), @$pre_init) } : sub { @$pre_init };
  5         12  
  12         40  
191              
192 22         127 my $__post_init = $into->can('_post_init');
193 22 100   12   131 my $_post_init = $__post_init ? sub { ($__post_init->(), @$post_init) } : sub { @$post_init };
  5         12  
  12         30  
194              
195             my $new = sub {
196 13     13   29 my $class = shift;
197              
198 13         19 my $self;
199              
200 13 100       38 if (@_ == 1) {
201 3         4 my $arg = shift;
202 3         5 my $type = ref($arg);
203              
204 3 100       6 if ($type eq 'HASH') {
205 1         4 $self = bless({%$arg}, $class);
206             }
207             else {
208 2 50       6 Carp::croak("Not sure what to do with '$type' in $class constructor")
209             unless $type eq 'ARRAY';
210              
211 2         3 my %proto;
212 2         5 my @attributes = attr_list($class);
213 2         5 while (@$arg) {
214 9         11 my $val = shift @$arg;
215 9 100       135 my $key = shift @attributes or Carp::croak("Too many arguments for $class constructor");
216 8         14 $proto{$key} = $val;
217             }
218              
219 1         3 $self = bless(\%proto, $class);
220             }
221             }
222             else {
223 10         34 $self = bless({@_}, $class);
224             }
225              
226             $Object::HashBase::CAN_CACHE{$class} = $self->can('init')
227 12 100       88 unless exists $Object::HashBase::CAN_CACHE{$class};
228              
229 12         28 $self->$_() for $_pre_init->();
230 12 100       41 $self->init() if $Object::HashBase::CAN_CACHE{$class};
231 12         25 $self->$_() for reverse $_post_init->();
232              
233 12         40 $self;
234 22         186 };
235              
236 22   100     75 my $new_lookup = $Object::HashBase::NEW_LOOKUP //= {};
237 22         110 $new_lookup->{$new} = 1;
238              
239 22         39 my %out;
240              
241             {
242 2     2   19 no strict 'refs';
  2         4  
  2         502  
  22         39  
243 22 50       32 $out{new} = $new unless defined(&{"${into}\::new"});
  22         135  
244 22 50       38 $out{add_pre_init} = $add_pre_init unless defined(&{"${into}\::add_pre_init"});
  22         182  
245 22 50       33 $out{add_post_init} = $add_post_init unless defined(&{"${into}\::add_post_init"});
  22         125  
246 22 50       36 $out{_pre_init} = $_pre_init unless defined(&{"${into}\::_pre_init"});
  22         78  
247 22 50       35 $out{_post_init} = $_post_init unless defined(&{"${into}\::_post_init"});
  22         105  
248             }
249              
250 22         167 return %out;
251             }
252              
253             1;
254              
255             __END__