File Coverage

blib/lib/Struct/Flatten/Template.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Struct::Flatten::Template;
2              
3 2     2   1663 use 5.008;
  2         6  
  2         62  
4              
5 2     2   909 use Moose;
  0            
  0            
6              
7             use version 0.77; our $VERSION = version->declare('v0.1.2');
8              
9             =head1 NAME
10              
11             Struct::Flatten::Template - flatten data structures using a template
12              
13             =head1 SYNOPSIS
14              
15             use Struct::Flatten::Template;
16              
17             my $tpl = {
18             docs => [
19             {
20             key => \ { column => 0 },
21             sum => {
22             value => \ { column => 1 },
23             }
24             ],
25             };
26              
27             my @data = ( );
28              
29             my $hnd = sub {
30             my ($obj, $val, $args) = @_;
31              
32             my $idx = $args->{_index};
33             my $col = $args->{column};
34              
35             $data[$idx] ||= [ ];
36             $data[$idx]->[$col] = $val;
37             };
38              
39             my $data = {
40             docs => [
41             { key => 'A', sum => { value => 10 } },
42             { key => 'B', sum => { value => 4 } },
43             { key => 'C', sum => { value => 18 } },
44             ],
45             };
46              
47             my $p = Struct::Flatten::Template->new(
48             template => $tpl,
49             handler => $hnd,
50             );
51              
52             =head1 DESCRIPTION
53              
54             This module is used for "flattening" complex, deeply-nested data
55             structures, such as those returned by an ElasticSearch aggregation
56             query.
57              
58             It is configured with a L</template> that mirrors the data structure,
59             where some parts of the template contain information how to process
60             the corresponding parts of the data structure.
61              
62             =for readme stop
63              
64             =head1 ATTRIBUTES
65              
66             =head2 C<template>
67              
68             This is a template of the data structure.
69              
70             This is basically a copy of the data structure, with the hash
71             reference keys and values that you care to extract information from,
72             using the L</handler>.
73              
74             To obtain a value, set it to a reference to a hash reference, e.g.
75              
76             key => \ { ... }
77              
78             The keys in the hash reference can be whatever youre application
79             needs, so long as they are not prefixed with an underscore.
80              
81             The following special keys are used:
82              
83             =over
84              
85             =item C<_index>
86              
87             This is either the array index of hash key or array item that the
88             value is associated with.
89              
90             Note that this is deprecated, and may be removed in future
91             versions. Use L</_path> instead.
92              
93             =item C<_sort>
94              
95             If set, this is a method used to sort hash keys, when the template
96             refers to a list of hash keys, e.g.
97              
98             key => \ {
99             _sort => sub { $_[0] cmp $_[1] },
100             ...
101             }
102              
103             =item C<_next>
104              
105             If your template is for hash keys instead of values, then this refers
106             to the value of that hash key in the template.
107              
108             It is useful if you want to have your handler fill-in intermediate
109             values (e.g. gaps in a list of dates) by calling the L</process>
110             method.
111              
112             =item C<_path>
113              
114             This contains an array reference of where in the data structure the
115             handler is being called.
116              
117             The array is of the form
118              
119             $key1 => $type1, $key2 => $type2, ...
120              
121             where the keys refer to hash keys or array indices, and the types are
122             either C<HASH> or C<ARRAY>.
123              
124             =back
125              
126             Note: to trigger a callback on hash keys instead of values, use
127             L<Tie::RefHash>.
128              
129             Also note that templates for array references assume the first element
130             applies to all elements of the data structure being processed.
131              
132             =cut
133              
134             has 'template' => (
135             is => 'ro',
136             isa => 'Ref',
137             required => 1,
138             );
139              
140             =head2 C<is_testing>
141              
142             This is true if the template is being processed using L</test>.
143              
144             This is useful to extract meta-information from your template,
145             e.g. field titles.
146              
147             It is intended to be used from within the L</handler>.
148              
149             =cut
150              
151             has 'is_testing' => (
152             is => 'ro',
153             isa => 'Bool',
154             traits => [qw/ Bool /],
155             default => 0,
156             init_arg => undef,
157             handles => {
158             '_set_is_testing' => 'set',
159             '_set_is_live' => 'unset',
160             },
161             );
162              
163             =head2 C<ignore_missing>
164              
165             If true, missing substructures will be ignored and the template will
166             be processed. This is useful for setting default values for missing
167             parts of the structure.
168              
169             This is true by default.
170              
171             =cut
172              
173             has 'ignore_missing' => (
174             is => 'ro',
175             isa => 'Bool',
176             default => 1,
177             );
178              
179             =head2 C<handler>
180              
181             The handler is a reference to a function, e.g.
182              
183             sub {
184             my ($obj, $value, $args) = @_;
185              
186             if ($obj->is_testing) {
187             ...
188             } else {
189             ...
190             }
191             }
192              
193             where C<$obj> is the C<Struct::Flatten::Template> object, C<$value> is
194             the value from the data structure being processed, and C<$args> is a
195             hash reference from the template.
196              
197             Note that C<$args> may have additional keys added to it. See L</template>.
198              
199             Your handler will need to use the information in C<$args> to determine
200             what to do with the data, e.g., where in a spreadsheet or what column
201             in a database to it.
202              
203             =cut
204              
205             has 'handler' => (
206             is => 'ro',
207             isa => 'Maybe[CodeRef]',
208             reader => '_get_handler',
209             writer => '_set_handler',
210             );
211              
212             around '_get_handler' => sub {
213             my ( $orig, $self, $template ) = @_;
214              
215             my $type = ref $template;
216             return unless $type;
217              
218             if ( ( $type eq 'REF' ) && ( ref( ${$template} ) eq 'HASH' ) ) {
219             return $self->$orig;
220             } else {
221             return;
222             }
223             };
224              
225             =head1 METHODS
226              
227             =head2 C<run>
228              
229             $obj->run( $struct );
230              
231             Process C<$struct> using the L</template>.
232              
233             =cut
234              
235             sub run {
236             my ( $self, $struct ) = @_;
237             $self->_set_is_live;
238             $self->process($struct);
239             }
240              
241             =head2 C<test>
242              
243             $obj->test();
244              
245             Test the template. Essentially, it processes the L</template> against
246             itself.
247              
248             =cut
249              
250             sub test {
251             my ( $self, $struct ) = @_;
252             $self->_set_is_testing;
253             $self->process( $self->template );
254             }
255              
256             =head2 C<process>
257              
258             =head2 C<process_HASH>
259              
260             =head2 C<process_ARRAY>
261              
262             $obj->process($struct, $template, $index);
263              
264             These are low-level methods for processing the template. In general,
265             you don't need to worry about them unless you are subclassing this.
266              
267             If you are inserting intermediate values from within your handler,
268             you should be calling the C<process> method.
269              
270             =cut
271              
272             sub process {
273             my ( $self, @args ) = @_;
274              
275             no warnings 'recursion';
276              
277             my $struct = $args[0];
278             my $template = $#args ? $args[1] : $self->template;
279             my $index = $args[2];
280             my @path = @{ $args[3] || [ ] };
281              
282             if ( my $type = ref($template) ) {
283              
284             if ( my $fn = $self->_get_handler($template) ) {
285              
286             my %args = %{ ${$template} };
287             $args{_index} = $index if defined $index;
288             $args{_path} = \@path;
289              
290             $fn->( $self, $struct, \%args );
291              
292             } else {
293              
294             return
295             if ( !$self->ignore_missing
296             && ( defined $struct )
297             && ( $type ne ref($struct) ) );
298              
299             my $method = "process_${type}";
300             $method =~ s/::/_/g;
301             if ( my $fn = $self->can($method) ) {
302             $self->$fn( $struct, $template, \@path );
303             }
304             }
305             }
306             }
307              
308             sub process_HASH {
309             my ( $self, $struct, $template, $path ) = @_;
310             foreach my $key ( keys %{$template} ) {
311              
312             if ( my $fn = $self->_get_handler($key) ) {
313              
314             my %args = %{ ${$key} };
315             $args{_index} = 0;
316             $args{_next} = $template->{$key}; # allow gap filling
317              
318             my $sort
319             = ( !$self->is_testing && $args{_sort} )
320             ? $args{_sort}
321             : sub {0};
322              
323             my @path = ( @{$path}, undef => 'HASH' );
324             $args{_path} = \@path;
325              
326             foreach my $skey ( sort { $sort->( $a, $b ) } keys %{$struct} ) {
327             $fn->( $self, $skey, \%args );
328             $path[-2] = $skey;
329             $self->process( $struct->{$skey}, $template->{$key}, $skey, \@path );
330             $args{_index}++;
331             }
332              
333             last;
334              
335             } else {
336              
337             my @path = ( @{$path}, $key => 'HASH' );
338              
339             $self->process( $struct->{$key}, $template->{$key}, $key, \@path )
340             if $self->ignore_missing || ( exists $struct->{$key} );
341              
342             }
343             }
344             }
345              
346             sub process_ARRAY {
347             my ( $self, $struct, $template, $path ) = @_;
348             my @path = ( @{$path}, 0 => 'ARRAY' );
349             foreach my $s (@{$struct}) {
350             $self->process( $s, $template->[0], $path[-2], \@path );
351             $path[-2]++;
352             }
353              
354             }
355              
356             use namespace::autoclean;
357              
358             __PACKAGE__->meta->make_immutable;
359              
360             1;
361              
362             =for readme continue
363              
364             =head1 SEE ALSO
365              
366             The following alternative modules can be used to flatten hashes:
367              
368             =over
369              
370             =item L<Data::Hash::Flatten>
371              
372             =item L<Hash::Flatten>
373              
374             =back
375              
376             =head1 AUTHOR
377              
378             Robert Rothenberg, C<< <rrwo at cpan.org> >>
379              
380             =head1 ACKNOWLEDGEMENTS
381              
382             =over
383              
384             =item Foxtons, Ltd.
385              
386             =back
387              
388             =head1 LICENSE AND COPYRIGHT
389              
390             Copyright 2014 Robert Rothenberg.
391              
392             This program is free software; you can redistribute it and/or modify it
393             under the terms of the the Artistic License (2.0). You may obtain a
394             copy of the full license at:
395              
396             L<http://www.perlfoundation.org/artistic_license_2_0>
397              
398             =for readme stop
399              
400             Any use, modification, and distribution of the Standard or Modified
401             Versions is governed by this Artistic License. By using, modifying or
402             distributing the Package, you accept this license. Do not use, modify,
403             or distribute the Package, if you do not accept this license.
404              
405             If your Modified Version has been derived from a Modified Version made
406             by someone other than you, you are nevertheless required to ensure that
407             your Modified Version complies with the requirements of this license.
408              
409             This license does not grant you the right to use any trademark, service
410             mark, tradename, or logo of the Copyright Holder.
411              
412             This license includes the non-exclusive, worldwide, free-of-charge
413             patent license to make, have made, use, offer to sell, sell, import and
414             otherwise transfer the Package with respect to any patent claims
415             licensable by the Copyright Holder that are necessarily infringed by the
416             Package. If you institute patent litigation (including a cross-claim or
417             counterclaim) against any party alleging that the Package constitutes
418             direct or contributory patent infringement, then this Artistic License
419             to you shall terminate on the date that such litigation is filed.
420              
421             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
422             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
423             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
424             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
425             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
426             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
427             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
428             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
429              
430             =for readme continue
431              
432             =cut