File Coverage

blib/lib/Class/Build/Array/Glob.pm
Criterion Covered Total %
statement 102 118 86.4
branch 29 58 50.0
condition 6 9 66.6
subroutine 10 11 90.9
pod n/a
total 147 196 75.0


line stmt bran cond sub pod time code
1             package Class::Build::Array::Glob;
2              
3             our $DATE = '2016-03-27'; # DATE
4             our $VERSION = '0.01'; # VERSION
5              
6 1     1   17706 use 5.010001;
  1         4  
7 1     1   4 use strict 'subs', 'vars';
  1         1  
  1         24  
8 1     1   4 use warnings;
  1         0  
  1         22  
9              
10 1     1   371 use Hook::AfterRuntime;
  1         2617  
  1         227  
11              
12             our %all_attribute_specs; # key=class, value=[$attr, \%predicates, ...]
13              
14             sub _collect_attributes {
15 3     3   8 my ($target_class, $package, $attrs) = @_;
16              
17 3         1 for my $parent (@{"$package\::ISA"}) {
  3         10  
18 1         2 _collect_attributes($target_class, $parent, $attrs);
19             }
20 3   50     3 push @$attrs, @{ $all_attribute_specs{$package} // [] };
  3         9  
21             }
22              
23             sub import {
24 2     2   308 my $class0 = shift;
25              
26 2         4 my $caller = caller();
27 2         9 *{"$caller\::has"} = sub {
28 5     5   212 my ($attr_name, %predicates) = @_;
29 5         4 push @{ $all_attribute_specs{$caller} }, [$attr_name, \%predicates];
  5         11  
30              
31             # define the sub first, to allow things like Role::Tiny::With to check
32             # the existence of required methods
33 5   50     16 my $is = $predicates{is} // 'ro';
34 5 50       15 *{"$caller\::$attr_name"} = $is eq 'rw' ? sub(;$) {} : sub() {};
  5         20  
35 2         6 };
36             after_runtime {
37 2     2   16 my @attr_specs;
38              
39             # prepend the parent classes' attributes
40 2         4 _collect_attributes($caller, $caller, \@attr_specs);
41              
42 2         1 my $glob_attr;
43             my %attr_indexes;
44             # generate the accessor methods
45             {
46 1     1   5 no warnings 'redefine';
  1         2  
  1         318  
47 2         1 my $idx = 0;
48 2         2 for my $attr_spec (@attr_specs) {
49 8         12 my ($attr_name, $predicates) = @$attr_spec;
50 8 50       13 next if defined $attr_indexes{$attr_name};
51 8         10 $attr_indexes{$attr_name} = $idx;
52 8 50       12 die "Class $caller attribute $attr_name: can't declare ".
53             "another attribute after globbing attribute ($glob_attr)"
54             if defined $glob_attr;
55 8 100       13 if ($predicates->{glob}) {
56 1         1 $glob_attr = $attr_name;
57             }
58 8   50     11 my $is = $predicates->{is} // 'ro';
59 8 50       13 my $code_str = $is eq 'rw' ? 'sub (;$) { ' : 'sub () { ';
60 8 100       9 if (defined $glob_attr) {
61 1 50       4 $code_str .= "splice(\@{\$_[0]}, $idx, scalar(\@{\$_[0]}), \@{\$_[1]}) if \@_ > 1; "
62             if $is eq 'rw';
63 1         2 $code_str .= "[ \@{\$_[0]}[$idx .. \$#{\$_[0]}] ]; ";
64             } else {
65 7 50       14 $code_str .= "\$_[0][$idx] = \$_[1] if \@_ > 1; "
66             if $is eq 'rw';
67 7         9 $code_str .= "\$_[0][$idx]; ";
68             }
69 8         3 $code_str .= "}";
70             #say "D:accessor code for attr $attr_name: ", $code_str;
71 8 0   0   564 *{"$caller\::$attr_name"} = eval $code_str;
  8 50       32  
  0 50       0  
  0 50       0  
  1 50       23  
  1 0       1  
  1 0       23  
  1 0       2  
  1         26  
  1         2  
  1         23  
  1         2  
  1         2  
  1         2  
  1         4  
  1         3  
  1         2  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
72 8 50       17 die if $@;
73 8         12 $idx++;
74             }
75             }
76              
77             # generate constructor
78             {
79 2         3 my $code_str = 'sub { ';
  2         2  
  2         1  
80 2         4 $code_str .= 'my ($class, %args) = @_; ';
81 2 100       3 if (defined $glob_attr) {
82 1         4 $code_str .= 'my $obj = bless [(undef) x '.(scalar(keys %attr_indexes)-1).'], $class; ';
83             } else {
84 1         1 $code_str .= 'my $obj = bless [], $class; ';
85             }
86 2         10 for my $attr_name (sort keys %attr_indexes) {
87 8         8 my $idx = $attr_indexes{$attr_name};
88 8 100 100     21 if (defined($glob_attr) && $attr_name eq $glob_attr) {
89 1         3 $code_str .= "if (exists \$args{'$attr_name'}) { splice(\@\$obj, $idx, scalar(\@\$obj), \@{ \$args{'$attr_name'} }) } ";
90             } else {
91 7         14 $code_str .= "if (exists \$args{'$attr_name'}) { \$obj->[$idx] = \$args{'$attr_name'} } ";
92             }
93             }
94 2         2 $code_str .= '$obj; }';
95             #say "D:constructor code for class $caller: ", $code_str;
96 2 50       1 unless (*{"$caller\::new"}{CODE}) {
  2         11  
97 2 50   1   253 *{"$caller\::new"} = eval $code_str;
  2 50       6  
  1 50       2172  
  1 50       5  
  1 50       4  
  0 50       0  
  1 50       4  
  0 50       0  
  1         3  
  0         0  
  1         6  
  0         0  
  0         0  
  1         3  
  0         0  
  1         3  
  1         62  
  1         2  
  1         5  
  0         0  
  1         3  
  1         5  
  1         3  
  0         0  
  1         4  
98 2 50       7 die if $@;
99             };
100             }
101              
102             # cleanup, so user can't do $obj->has(...) etc later
103 2         1 undef *{"$caller\::has"};
  2         27  
104 2         10 };
105             }
106              
107             1;
108             # ABSTRACT: Generate Class accessors/constructor (array-based object, supports globbing attribute)
109              
110             __END__