File Coverage

blib/lib/YAML/Active.pm
Criterion Covered Total %
statement 82 118 69.4
branch 23 54 42.5
condition 2 12 16.6
subroutine 24 31 77.4
pod 15 15 100.0
total 146 230 63.4


line stmt bran cond sub pod time code
1 2     2   1075135 use 5.008;
  2         13  
  2         198  
2 2     2   18 use strict;
  2         6  
  2         193  
3 2     2   14 use warnings;
  2         5  
  2         170  
4              
5             package YAML::Active;
6             our $VERSION = '1.100810';
7             # ABSTRACT: Combine data and logic in YAML
8 2     2   2464 use YAML::XS (); # no imports, we'll define our own Load() and LoadFile()
  2         9214  
  2         58  
9 2     2   19 use Exporter qw(import);
  2         5  
  2         242  
10             our %EXPORT_TAGS = (
11             load => [qw{Load Load_inactive Reload LoadFile}],
12             dump => [qw{Dump}],
13             active => [qw{node_activate array_activate hash_activate}],
14             assert => [qw{assert_arrayref assert_hashref}],
15             null => [qw{yaml_NULL NULL}],
16             );
17             our @EXPORT_OK = @{ $EXPORT_TAGS{all} = [ map { @$_ } values %EXPORT_TAGS ] };
18 2     2   12 use constant NULL => 'YAML::Active::NULL';
  2         4  
  2         5062  
19              
20             sub should_process_node_in_phase {
21 17     17 1 29 my ($node, $phase) = @_;
22              
23 17 50       44 if (defined $phase) {
24 0 0 0     0 return 0
25             unless exists $node->{_phase} && $phase eq $node->{_phase};
26             } else {
27 17 50       60 return 0 if exists $node->{_phase};
28             }
29 17         55 return 1;
30             }
31              
32             sub array_activate ($$) {
33 14     14 1 748 my ($node, $phase) = @_;
34              
35             #my @result;
36             #my @node = @$node;
37             #for my $index (0..$#node) {
38             # my $activated = node_activate($node[$index], $phase);
39             # next if ref($activated) eq NULL;
40             # push @result, $activated;
41             #}
42             #\@result;
43 45         157 [ grep { ref ne NULL }
  45         95  
44 14         55 map { node_activate($_, $phase) } @$node
45             ];
46             }
47              
48             sub hash_activate ($$) {
49 17     17 1 26 my ($node, $phase) = @_;
50 17 50       45 return unless should_process_node_in_phase($node, $phase);
51             return {
52 36         99 map {
53 17         64 my $val = node_activate($node->{$_}, $phase);
54 35 100       233 ref $val eq NULL ? () : ($_ => $val)
55             } keys %$node
56             };
57             }
58              
59             sub node_activate ($$) {
60 101     101 1 234592 my ($node, $phase) = @_;
61 101 100       265 return array_activate($node, $phase) if ref $node eq 'ARRAY';
62 95 100       243 return hash_activate($node, $phase) if ref $node eq 'HASH';
63              
64             # FIXME:
65             # don't just do
66             #
67             # return array_activate($node, $phase)
68             #
69             # because of the following situation:
70             #
71             # x: &REF
72             # foo: 1
73             # y: *REF
74             #
75             # $data->{y} comes out of YAML itself as a proper reference, but when we
76             # just replace $data->{x}, the value of $data->{y} still points to the old
77             # "{ foo => 1 }" hash ref and so gets replaced independently as well. This
78             # means we end up not with a reference but with two reference, each
79             # pointing to the same cloned hash.
80             # if (ref $node eq 'ARRAY') {
81             # my $result = array_activate($node, $phase);
82             # if (UNIVERSAL::isa($result, 'ARRAY')) {
83             # @$node = @$result;
84             # return $node;
85             # } else {
86             # return $result;
87             # }
88             # } elsif (ref $node eq 'HASH') {
89             # my $result = hash_activate($node, $phase);
90             # if (UNIVERSAL::isa($result, 'HASH')) {
91             # %$node = %$result;
92             # return $node;
93             # } else {
94             # return $result;
95             # }
96             # }
97 80 100       176 if (my $class = ref $node) {
98 22 100 66     348 if (!$class->can('yaml_activate')
99             && index($class, 'YAML::Active') != -1) {
100 2         224 eval "require $class";
101 2 50       737 die $@ if $@;
102             }
103 22 50       96 if ($node->can('yaml_activate')) {
104 22         85 return $node->yaml_activate($phase);
105             } else {
106              
107             # it's a blessed reference, but it can't yaml_activate, so dig
108             # deeper
109 0 0       0 my $activated =
    0          
110             UNIVERSAL::isa($node, 'ARRAY') ? array_activate($node, $phase)
111             : UNIVERSAL::isa($node, 'HASH') ? hash_activate($node, $phase)
112             : $node;
113 0         0 return bless $activated, ref $node;
114              
115             # if (UNIVERSAL::isa($node, 'ARRAY')) {
116             # my $result = array_activate($node, $phase);
117             # if (UNIVERSAL::isa($result, 'ARRAY')) {
118             # # the blessing stays the same
119             # @$node = @$result;
120             # return $node;
121             # } else {
122             # return bless $result, ref $node;
123             # }
124             # } elsif (UNIVERSAL::isa($node, 'HASH')) {
125             # my $result = hash_activate($node, $phase);
126             # if (UNIVERSAL::isa($result, 'HASH')) {
127             # # the blessing stays the same
128             # %$node = %$result;
129             # return $node;
130             # } else {
131             # return bless $result, ref $node;
132             # }
133             # }
134             #
135             # return $node;
136             }
137             }
138 58         306 return $node;
139             }
140              
141             # pass through
142             sub Load_inactive {
143 0     0 1 0 my $node = shift;
144 0         0 YAML::XS::Load($node);
145             }
146              
147             sub Load {
148 13     13 1 12049 my ($node, $phase) = @_;
149 13         301056 node_activate(YAML::XS::Load($node), $phase)
150              
151             #my $x = node_activate(Load_inactive($node), $phase);
152             #use Data::Dumper; print Dumper $x;
153             #if (ref $x->{setup} eq 'HASH') {
154             # printf "foo [%s]\n", $x->{setup}{foo};
155             # printf "bar [%s]\n", $x->{setup}{bar};
156             #}
157             #$x;
158             }
159              
160             sub Reload {
161 0     0 1 0 my ($node, $phase) = @_;
162 0         0 Load(Dump($node), $phase);
163             }
164              
165             sub LoadFile {
166 2     2 1 3 my ($node, $phase) = @_;
167 2         12 node_activate(YAML::XS::LoadFile($node), $phase);
168             }
169              
170             sub assert_arrayref {
171 7 100   7 1 42 return if UNIVERSAL::isa($_[0], 'ARRAY');
172 1         16 die sprintf "%s expects an array ref", (caller)[0];
173             }
174              
175             sub assert_hashref {
176 3 50   3 1 14 return if UNIVERSAL::isa($_[0], 'HASH');
177 0         0 die sprintf "%s expects a hash ref", (caller)[0];
178             }
179 3     3 1 21 sub yaml_NULL { bless {}, NULL }
180              
181             # end of activation-related code
182             # start of dump-related code
183             sub Dump {
184 0     0 1 0 my ($node, %args) = @_;
185 0 0       0 local $YAML::XS::ForceBlock =
186             exists $args{ForceBlock} ? $args{ForceBlock} : 1;
187 0         0 my $dump = YAML::XS::Dump(node_dump($node));
188 0         0 our %prepare_dump;
189 0   0     0 $_->can('finish_dump') && $_->finish_dump for keys %prepare_dump;
190 0         0 $dump;
191             }
192              
193             sub node_dump ($) {
194 0     0 1 0 my $node = shift;
195 0 0       0 return array_dump($node) if ref $node eq 'ARRAY';
196 0 0       0 return hash_dump($node) if ref $node eq 'HASH';
197 0 0       0 if (my $class = ref $node) {
198 0 0       0 if (!$node->can('yaml_dump')) {
199 0         0 eval "require $class";
200 0 0       0 die $@ if $@;
201             }
202 0 0       0 if ($node->can('prepare_dump')) {
203 0         0 our %prepare_dump;
204 0   0     0 $prepare_dump{ ref $node } ||= $node->prepare_dump;
205             }
206 0 0       0 return $node->can('yaml_dump') ? $node->yaml_dump : $node;
207             }
208 0         0 return $node;
209             }
210              
211             sub array_dump ($) {
212 0     0 1 0 my $node = shift;
213 0         0 [ grep { ref ne NULL }
  0         0  
214 0         0 map { node_dump($_) } @$node
215             ];
216             }
217              
218             sub hash_dump ($) {
219 0     0 1 0 my $node = shift;
220             return {
221 0         0 map {
222 0         0 my $val = node_dump($node->{$_});
223 0 0       0 ref $val eq NULL ? () : ($_ => $val)
224             } keys %$node
225             };
226             }
227              
228             package YAML::Active::Concat;
229             our $VERSION = '1.100810';
230             YAML::Active->import(':all');
231              
232             sub yaml_activate {
233 4     4   7 my ($self, $phase) = @_;
234 4         50 assert_arrayref($self);
235 3         4 return join '' => @{ array_activate($self, $phase) };
  3         8  
236             }
237              
238             package YAML::Active::Eval;
239             our $VERSION = '1.100810';
240             YAML::Active->import(':all');
241              
242             sub yaml_activate {
243 1     1   2 my ($self, $phase) = @_;
244 1         3 assert_hashref($self);
245 1         7 my $code_ref = eval node_activate($self->{code}, $phase);
246 1         28 return $code_ref->();
247             }
248              
249             package YAML::Active::Include;
250             our $VERSION = '1.100810';
251             YAML::Active->import(':all');
252              
253             sub yaml_activate {
254 2     2   4 my ($self, $phase) = @_;
255 2         6 assert_hashref($self);
256 2         10 return LoadFile(node_activate($self->{filename}, $phase));
257             }
258 4     4   22 sub YAML::Active::PID::yaml_activate { $$ }
259              
260             package YAML::Active::Shuffle;
261             our $VERSION = '1.100810';
262             YAML::Active->import(':all');
263              
264             sub yaml_activate {
265 1     1   3 my ($self, $phase) = @_;
266 1         4 assert_arrayref($self);
267 1         2 return [ sort { 1 - int rand 3 } @{ array_activate($self, $phase) } ];
  18         82  
  1         4  
268             }
269              
270             # example of a side-effect-only plugin
271             package YAML::Active::Print;
272             our $VERSION = '1.100810';
273             YAML::Active->import(':all');
274              
275             sub yaml_activate {
276 1     1   4 my ($self, $phase) = @_;
277 1         5 assert_arrayref($self);
278 1         5 my $result = array_activate($self, $phase);
279 1         55 print @$result;
280 1         5 return yaml_NULL();
281             }
282              
283             package YAML::Active::ValueMutator;
284             our $VERSION = '1.100810';
285             YAML::Active->import(':all');
286 0     0   0 sub mutate_value { $_[1] }
287              
288             sub yaml_activate {
289 4     4   6 my ($self, $phase) = @_;
290 4 100       25 if (UNIVERSAL::isa($self, 'ARRAY')) {
    50          
291 8 100       24 return [ map { ref($_) ? $_ : $self->mutate_value($_) }
  2         5  
292 2         3 @{ array_activate($self, $phase) } ];
293             } elsif (UNIVERSAL::isa($self, 'HASH')) {
294 2         6 my $h = hash_activate($self, $phase);
295 2         8 $_ = $self->mutate_value($_) for grep { !ref } values %$h;
  7         18  
296 2         9 return $h;
297             }
298 0         0 return $self; # shouldn't get here
299             }
300              
301             package YAML::Active::uc;
302             our $VERSION = '1.100810';
303             our @ISA = 'YAML::Active::ValueMutator';
304 6     6   22 sub mutate_value { uc $_[1] }
305              
306             package YAML::Active::lc;
307             our $VERSION = '1.100810';
308             our @ISA = 'YAML::Active::ValueMutator';
309 7     7   24 sub mutate_value { lc $_[1] }
310             1;
311              
312              
313             __END__