File Coverage

blib/lib/Object/ArrayType/New.pm
Criterion Covered Total %
statement 86 99 86.8
branch 33 58 56.9
condition 12 36 33.3
subroutine 16 16 100.0
pod n/a
total 147 209 70.3


line stmt bran cond sub pod time code
1             package Object::ArrayType::New;
2             $Object::ArrayType::New::VERSION = '1.001001';
3 2     2   66583 use strict; use warnings;
  2     2   6  
  2         65  
  2         10  
  2         4  
  2         48  
4              
5 2     2   10 use Carp;
  2         4  
  2         172  
6 2     2   11 use B ();
  2         5  
  2         37  
7 2     2   10 use Scalar::Util 'blessed', 'reftype';
  2         4  
  2         2617  
8              
9             sub import {
10 5     5   240 my ($class, $params) = @_;
11 5 100       16 $params = [] unless defined $params;
12 5 50 66     56 croak "Expected an ARRAY or HASH but got $params"
      66        
13             unless ref $params
14             and reftype $params eq 'ARRAY'
15             or reftype $params eq 'HASH';
16              
17 5         9 my $target = caller;
18 5         16 $class->_validate_and_install($target => $params)
19             }
20              
21             sub _inject_code {
22 15     15   25 my ($class, $target, $code) = @_;
23 15 50 33     66 confess "Expected a target package and string to inject"
24             unless defined $target and defined $code;
25 15         116 my $run = "package $target; $code; 1;";
26 15 50       46 warn "(eval ->) $run\n" if $ENV{OBJECT_ARRAYTYPE_DEBUG};
27 15         16 local $@;
28 15 50 33 3   2492 eval $run and not $@ or confess "eval: $@";
  3 50 33 1   1909  
  3 100 66 1   6  
  3 50 0 1   20  
  1 100 33     13  
  1 100 0     3  
  1 50 33     5  
  0 0 0     0  
  2 50 33     9  
  3 50       53  
  3 0       13  
  1 50       1245  
  1 50       3  
  1 50       7  
  0 0       0  
  0 50       0  
  0 50       0  
  0 50       0  
  1 50       2  
  1         14  
  1         4  
  1         859  
  1         2  
  1         9  
  0         0  
  0         0  
  0         0  
  0         0  
  1         3  
  1         14  
  1         4  
  1         792  
  1         3  
  1         8  
  0         0  
  0         0  
  0         0  
  0         0  
  1         5  
  1         18  
  1         4  
29 15         5095 1
30             }
31              
32             sub _inject_constant {
33 10     10   20 my ($class, $target, $name, $val) = @_;
34 10 50       26 my $code = ref $val ? "sub $name () { \$val }"
35 10         88 : "sub $name () { ${\ B::perlstring($val) } }";
36 10         38 $class->_inject_code($target => $code)
37             }
38              
39             sub _install_constants {
40 5     5   9 my ($class, $target, $items) = @_;
41 5         6 my $idx = 0;
42 5         12 for my $item (@$items) {
43 10         21 my $constant = $item->{constant};
44 10         28 $class->_inject_constant($target => $constant => $idx++);
45             }
46             1
47 5         11 }
48              
49             sub _validate_and_install {
50 5     5   9 my ($class, $target, $params) = @_;
51 5 100       26 my @items = reftype $params eq 'HASH' ? %$params : @$params;
52              
53 5         7 my @install;
54 5         19 PARAM: while (my ($initarg, $def) = splice @items, 0, 2) {
55 10 50       37 $initarg = '' unless defined $initarg;
56 10 100       23 my $store = $def ? $def : uc $initarg;
57 10 50       21 confess "No init arg and no constant specified!" unless $store;
58 10         58 push @install, +{
59             name => $initarg,
60             constant => $store,
61             };
62             }
63              
64 5         45 $class->_install_constants($target => \@install);
65 5         18 $class->_install_constructor($target => \@install);
66             }
67              
68             sub _generate_storage {
69 5     5   9 my (undef, undef, $items) = @_;
70 5         7 my $code = " my \$self = bless [\n";
71 5         10 for my $item (@$items) {
72 10         16 my $attr = $item->{name};
73 10 100       39 $code .= $attr ?
74             qq[ (defined \$args{$attr} ? \$args{$attr} : undef),\n]
75             : qq[ undef,\n]
76             }
77 5         15 $code .= ' ], (Scalar::Util::blessed($class) || $class);';
78 5         12 $code
79             }
80              
81             sub _install_constructor {
82 5     5   10 my ($class, $target, $items) = @_;
83              
84 5         7 my $code = <<'_EOC';
85             sub new {
86             my $class = shift; my %args;
87             if (@_ == 1) {
88             Carp::confess "Expected single param to be a HASH but got $_[0]"
89             unless ref $_[0] and Scalar::Util::reftype $_[0] eq 'HASH';
90             %args = %{ $_[0] }
91             } elsif (@_ % 2) {
92             Carp::confess "Expected either a HASH or a list of key/value pairs"
93             } else {
94             %args = @_
95             }
96              
97             _EOC
98            
99 5         14 $code .= $class->_generate_storage($target => $items);
100 5         9 $code .= "\n \$self\n}\n";
101 5         15 $class->_inject_code($target => $code)
102             }
103              
104             print
105             q[ also every time you @result = `curl blahblah`],
106             qq[ LeoNerd uses passive voice\n]
107             unless caller;
108             1;
109              
110             =pod
111              
112             =for Pod::Coverage import
113              
114             =head1 NAME
115              
116             Object::ArrayType::New - Inject constants and constructor for ARRAY-type objects
117              
118             =head1 SYNOPSIS
119              
120             package MyObject;
121             use strict; use warnings;
122             use Object::ArrayType::New
123             [ foo => 'FOO', bar => 'BAR' ];
124             sub foo { shift->[FOO] }
125             sub bar { shift->[BAR] ||= [] }
126              
127             package main;
128             my $obj = MyObject->new(foo => 'baz');
129             my $foo = $obj->foo; # baz
130             my $bar = $obj->bar; # []
131              
132             =head1 DESCRIPTION
133              
134             ARRAY-backed objects are light and fast, but obviously slightly more
135             complicated to cope with than just stuffing key/value pairs into a HASH.
136             The easiest way to keep track of where things live is to set up some named
137             constants to index into the ARRAY -- you can access your indexes by name,
138             and gain compile-time typo checking as an added bonus.
139              
140             A common thing I find myself doing looks something like:
141              
142             package MySimpleObject;
143             use strict; use warnings;
144              
145             sub TAG () { 0 }
146             sub BUF () { 1 }
147             # ...
148              
149             sub new {
150             my $class = shift;
151             my %params = @_ > 1 ? @_ : %{ $_[0] };
152             bless [
153             $params{tag}, # TAG
154             ($params{buffer} || []) # BUF
155             # ...
156             ], $class
157             }
158             sub tag { shift->[TAG] }
159             sub buffer { shift->[BUF] }
160             # ...
161              
162             ... when I'd rather be doing something more like the L.
163              
164             This tiny module takes, as arguments to C, an ARRAY of pairs mapping a
165             C parameter name to the name of a constant. The constant represents the
166             item's position in the object's backing ARRAY.
167              
168             If the B's name is boolean false, the uppercased parameter name is
169             used as the name of the constant:
170              
171             use Object::ArrayType::New
172             [ foo => '', bar => '' ];
173             # same as foo => 'FOO', bar => 'BAR'
174              
175             If the B's name is boolean false, there is no construction-time
176             parameter. The constant is installed and the appropriate position in the
177             backing ARRAY is set to C at construction time; this can be useful for
178             private attributes:
179              
180             use Object::ArrayType::New
181             [ foo => 'FOO', '' => 'BAR' ];
182             sub foo { shift->[FOO] ||= 'foo' }
183             sub _bar { shift->[BAR] ||= [] }
184              
185             An appropriate constructor is generated and installed, as well as constants
186             that can be used within the class to index into the C<$self> object.
187              
188             The generated constructor takes parameters as either a list of pairs or a
189             single HASH. Parameters not specified at construction time are C.
190              
191             That's it; no accessors, no defaults, no type-checks, no required attributes,
192             nothing fancy. L may be convenient there; the above
193             raw Perl example could be written something like:
194              
195             use Object::ArrayType::New [ tag => '', buffer => 'BUF' ];
196             sub tag { shift->[TAG] }
197             sub buffer { shift->[BUF] }
198             use Class::Method::Modifers;
199             around new => sub {
200             my ($orig, $class) = splice @_, 0, 2;
201             my $self = $class->$orig(@_);
202             $self->[BUF] = [] unless defined $self->[BUF];
203             $self
204             };
205              
206             if C<< $ENV{OBJECT_ARRAYTYPE_DEBUG} >> is true, generated code is printed to
207             STDERR before being evaluated.
208              
209             Constants aren't currently sanity-checked ahead of time; attempting to use
210             invalid identifiers will result in vague 'Illegal declaration ...' failures.
211              
212             =head1 AUTHOR
213              
214             Jon Portnoy
215              
216             =cut
217              
218             # vim: ts=2 sw=2 et sts=2 ft=perl