File Coverage

blib/lib/Data/CSel/WrapStruct.pm
Criterion Covered Total %
statement 70 83 84.3
branch 16 30 53.3
condition 1 3 33.3
subroutine 16 21 76.1
pod 2 2 100.0
total 105 139 75.5


line stmt bran cond sub pod time code
1             package Data::CSel::WrapStruct;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2020-04-07'; # DATE
5             our $DIST = 'Data-CSel-WrapStruct'; # DIST
6             our $VERSION = '0.006'; # VERSION
7              
8 1     1   110337 use 5.010001;
  1         16  
9 1     1   6 use strict;
  1         2  
  1         22  
10 1     1   5 use warnings;
  1         2  
  1         28  
11              
12 1     1   5 use Exporter qw(import);
  1         2  
  1         56  
13             our @EXPORT_OK = qw(
14             wrap_struct
15             unwrap_tree
16             );
17              
18             # convenience
19 1     1   9 use Data::CSel ();
  1         2  
  1         1178  
20             unshift @Data::CSel::CLASS_PREFIXES, __PACKAGE__
21             unless grep { $_ eq __PACKAGE__ } @Data::CSel::CLASS_PREFIXES;
22              
23             sub _wrap {
24 44     44   73 my ($data, $parent, $key_in_parent) = @_;
25 44         69 my $ref = ref($data);
26 44 100       85 if (!$ref) {
    100          
    50          
    0          
    0          
27 26         68 return Data::CSel::WrapStruct::Scalar->new($data, $parent, $key_in_parent);
28             #} elsif (blessed $data) {
29             } elsif ($ref eq 'ARRAY') {
30 14         49 my $node = Data::CSel::WrapStruct::Array->new($data, $parent);
31 14         26 $node->children([ map { _wrap($data->[$_], $node, $_) } 0..$#{$data}]);
  40         77  
  14         27  
32 14         40 return $node;
33             } elsif ($ref eq 'HASH') {
34 4         18 my $node = Data::CSel::WrapStruct::Hash->new($data, $parent);
35 4         18 my @keys = sort keys %$data;
36 4         14 $node->_keys(\@keys);
37 4         12 $node->children([ map { _wrap($data->{$_}, $node, $_) } @keys]);
  2         5  
38 4         42 return $node;
39             } elsif ($ref eq 'SCALAR') {
40 0         0 return Data::CSel::WrapStruct::ScalarRef->new($data, $parent, undef);
41             } elsif ($ref eq 'JSON::PP::Boolean') {
42 0         0 return Data::CSel::WrapStruct::Scalar->new($$data, $parent, undef);
43             } else {
44 0         0 die "Sorry, currently can't handle ref=$ref";
45             }
46             }
47              
48             sub wrap_struct {
49 2     2 1 3914 my $data = shift;
50 2         9 _wrap($data, undef, undef);
51             }
52              
53             sub unwrap_tree {
54 7     7 1 7277 my $tree = shift;
55              
56 7         14 state $cleaner = do {
57 1         602 require Data::Clean;
58 1         4941 Data::Clean->new(
59             '!recurse_obj' => 1,
60             'Data::CSel::WrapStruct::Scalar' => [call_method=>'value'],
61             'Data::CSel::WrapStruct::ScalarRef' => [call_method=>'value'],
62             'Data::CSel::WrapStruct::Array' => [call_method=>'value'],
63             'Data::CSel::WrapStruct::Hash' => [call_method=>'value'],
64             );
65             };
66              
67 7         2518 $cleaner->clean_in_place($tree);
68             }
69              
70             package
71             Data::CSel::WrapStruct::Base;
72              
73             sub new {
74 44     44   77 my ($class, $data_ref, $parent, $key_in_parent) = @_;
75 44         133 bless [$data_ref, $parent, $key_in_parent], $class;
76             }
77              
78             sub value {
79 38     38   1386 my $self = shift;
80 38 100       78 if (@_) {
81 2         7 my ($parent, $key_in_parent) = ($self->[1], $self->[2]);
82 2         3 my $new_value = shift;
83 2         5 my $orig_value = $self->[0];
84 2         4 $self->[0] = $new_value;
85 2 50       6 if (defined $key_in_parent) {
86 2         5 my $ref_parent = ref $parent->[0];
87 2 50       6 if ($ref_parent eq 'ARRAY') {
    0          
88 2         4 $parent->[0][$key_in_parent] = $new_value;
89             } elsif ($ref_parent eq 'HASH') {
90 0         0 $parent->[0]{$key_in_parent} = $new_value;
91             } else {
92 0         0 warn "Cannot replace value in parent: not array/hash";
93             }
94             }
95 2         5 return $new_value;
96             }
97 36         231 $self->[0];
98             }
99              
100             sub remove {
101 2     2   52 my $self = shift;
102 2         6 my ($parent, $key_in_parent) = ($self->[1], $self->[2]);
103 2 50 33     13 if (defined $parent && defined $key_in_parent) {
104 2         6 my $ref_parent = ref $parent->[0];
105 2 50       6 if ($ref_parent eq 'ARRAY') {
    0          
106 2         3 splice @{ $parent->[0] }, $key_in_parent, 1;
  2         5  
107             } elsif ($ref_parent eq 'HASH') {
108 0         0 delete $parent->[0]{$key_in_parent};
109             } else {
110 0         0 warn "Cannot remove node from parent: not array/hash";
111             }
112             }
113 2         5 undef;
114             }
115              
116             sub parent {
117 0     0   0 $_[0][1];
118             }
119              
120             package
121             Data::CSel::WrapStruct::Scalar;
122              
123             our @ISA = qw(Data::CSel::WrapStruct::Base);
124              
125             sub children {
126 65     65   1139 [];
127             }
128              
129             package
130             Data::CSel::WrapStruct::ScalarRef;
131              
132             our @ISA = qw(Data::CSel::WrapStruct::Base);
133              
134             sub children {
135 0     0   0 [];
136             }
137              
138             package
139             Data::CSel::WrapStruct::Array;
140              
141             our @ISA = qw(Data::CSel::WrapStruct::Base);
142              
143             sub children {
144 49 100   49   9937 if (@_ > 1) {
145 14         30 $_[0][2] = $_[1];
146             }
147 49         90 $_[0][2];
148             }
149              
150             sub length {
151 0     0   0 scalar @{ $_[0][0] };
  0         0  
152             }
153              
154             package
155             Data::CSel::WrapStruct::Hash;
156              
157             our @ISA = qw(Data::CSel::WrapStruct::Base);
158              
159             sub _keys {
160 4 50   4   10 if (@_ > 1) {
161 4         9 $_[0][2] = $_[1];
162             }
163 4         6 $_[0][2];
164             }
165              
166             sub children {
167 14 100   14   181 if (@_ > 1) {
168 4         9 $_[0][3] = $_[1];
169             }
170 14         28 $_[0][3];
171             }
172              
173             sub length {
174 4     4   436 scalar @{ $_[0][2] };
  4         12  
175             }
176              
177             sub has_key {
178 0     0     exists $_[0][0]{$_[1]};
179             }
180              
181             sub key {
182 0     0     $_[0][0]{$_[1]};
183             }
184              
185             1;
186             # ABSTRACT: Wrap data structure into a tree of objects suitable for use with Data::CSel
187              
188             __END__
189              
190             =pod
191              
192             =encoding UTF-8
193              
194             =head1 NAME
195              
196             Data::CSel::WrapStruct - Wrap data structure into a tree of objects suitable for use with Data::CSel
197              
198             =head1 VERSION
199              
200             This document describes version 0.006 of Data::CSel::WrapStruct (from Perl distribution Data-CSel-WrapStruct), released on 2020-04-07.
201              
202             =head1 SYNOPSIS
203              
204             use Data::CSel qw(csel);
205             use Data::CSel::WrapStruct qw(wrap_struct unwrap_tree);
206              
207             my $data = [
208             0,
209             1,
210             [2, ["two","dua"], {url=>"http://example.com/two.jpg"}, ["even","prime"]],
211             3,
212             [4, ["four","empat"], {}, ["even"]],
213             ];
214              
215             my $tree = wrap_struct($data);
216             my @nodes = csel(":root > * > *:nth-child(4) > *", $tree);
217             my @tags = map { $_->value } @nodes; # -> ("even", "prime", "even")
218              
219             Scalars are wrapped using C<Data::CSel::WrapStruct::Scalar> class, scalarrefs
220             are wrapped using C<Data::CSel::WrapStruct::ScalarRef> class, arrays are wrapped
221             using C<Data::CSel::WrapStruct::Array> class, and hashes are wrapped using
222             C<Data::CSel::WrapStruct::Hash> class. For convenience, when you load
223             C<Data::CSel::WrapStruct>, it adds C<Data::CSel::WrapStruct> to
224             C<@Data::CSel::CLASS_PREFIXES> so you don't have to specify C<<
225             {class_prefixes=>["Data::CSel::WrapStruct"]} >> C<csel()> option everytime.
226              
227             my @hashes = map {$_->value} csel("Hash", $tree);
228             # -> ({url=>"http://example.com/two.jpg"}, {})
229              
230             The wrapper objects provide some methods, e.g.:
231              
232             my @empty_hashes = map {$_->value} csel("Hash[length=0]", $tree);
233             # -> ({})
234              
235             my @hashes_that_have_url_key = map {$_->value} csel("Hash[has_key('url')]", $tree);
236             # -> ({url=>"http://example.com/two.jpg"})
237              
238             my @larger_scalars = [map {$_->value} csel("Scalar[value >= 3]", $tree)]
239             # -> (3, 4)
240              
241             See L</NODE METHODS>, L</SCALAR NODE METHODS>, L</SCALARREF NODE METHODS>,
242             L</ARRAY NODE METHODS>, L</HASH NODE METHODS> for more details on the provided
243             methods.
244              
245             You can replace the value of nodes using L</value>:
246              
247             my @posint_scalar_nodes = csel("Scalar[value > 0]", $tree);
248             for (@posint_scalar_nodes) { $_->value( $_->value * 10 ) }
249             use Data::Dump;
250             dd unwrap_tree($data);
251             # => [
252             # 0,
253             # 10,
254             # [20, ["two","dua"], {url=>"http://example.com/two.jpg"}, ["even","prime"]],
255             # 30,
256             # [40, ["four","empat"], {}, ["even"]],
257             # ];
258              
259             =head1 DESCRIPTION
260              
261             This module provides C<wrap_struct()> which creates a tree of objects from a
262             generic data structure. You can then perform node selection using
263             L<Data::CSel>'s C<csel()>.
264              
265             You can retrieve the original value of data items by calling C<value()> method
266             on the tree nodes.
267              
268             =for Pod::Coverage ^(.+)$
269              
270             =head1 NODE METHODS
271              
272             =head2 parent
273              
274             =head2 children
275              
276             =head2 value
277              
278             Usage:
279              
280             my $val = $node->value; # get node value
281             $node->value(1); # set node value
282              
283             Get or set node value.
284              
285             Note that when setting node value, the new node value is not automatically
286             wrapped for you. If you want to set new node value and expect to select it or
287             part of it again with C<csel()>, you will have to wrap the new value first with
288             L</wrap_struct>.
289              
290             =head2 remove
291              
292             Usage:
293              
294             $node->remove;
295              
296             Remove node from parent.
297              
298             =head1 SCALAR NODE METHODS
299              
300             In addition to methods listed in L</NODE METHODS>, Scalar nodes also have the
301             following methods.
302              
303             =head1 SCALARREF NODE METHODS
304              
305             In addition to methods listed in L</NODE METHODS>, ScalarRef nodes also have the
306             following methods.
307              
308             =head1 ARRAY NODE METHODS
309              
310             In addition to methods listed in L</NODE METHODS>, Array nodes also have the
311             following methods.
312              
313             =head2 length
314              
315             Get array length. Can be used to select an array based on its length, e.g.:
316              
317             @nodes = csel('Array[length > 0]');
318              
319             =head1 HASH NODE METHODS
320              
321             In addition to methods listed in L</NODE METHODS>, Hash nodes also have the
322             following methods.
323              
324             =head2 length
325              
326             Get the number of keys. Can be used to select a hash based on its number of
327             keys, e.g.:
328              
329             @nodes = csel('Hash[length > 0]');
330              
331             =head2 has_key
332              
333             Usage:
334              
335             my $bool = $node->has_key("foo");
336              
337             Check whether hash has a certain key. Can be used to select a hash, e.g.:
338              
339             @nodes = csel('Hash[has_key("foo")]');
340              
341             =head2 key
342              
343             Usage:
344              
345             my $key_val = $node->key("foo");
346              
347             Get a hash key's value. Can be used to select a hash based on the value of one
348             of its keys, e.g.:
349              
350             @nodes = csel('Hash[key("name") = "lisa"]');
351              
352             =head1 FUNCTIONS
353              
354             None exported by default, but exportable.
355              
356             =head2 wrap_struct
357              
358             Usage:
359              
360             my $tree = wrap_struct($data);
361              
362             Wrap a data structure into a tree of objects.
363              
364             Currently cannot handle recursive structure.
365              
366             =head2 unwrap_tree
367              
368             Usage:
369              
370             my $data = unwrap_tree($wrapped_data);
371              
372             Unwrap a tree produced by L</wrap_tree> back into unwrapped data structure.
373              
374             =head1 FAQ
375              
376             =head2 Changing the node value doesn't work!
377              
378             my $data = [0, 1, 2];
379             my @nodes = csel("Scalar[value > 0]", wrap_struct($data));
380             for (@nodes) { $_->[0] = "x" }
381             use Data::Dump;
382             dd $data;
383              
384             still prints C<< [0,1,2] >> instead of C<< [0,'x','x'] >>. Why?
385              
386             To set node value, you have to use the C<value()> node method with an argument:
387              
388             ...
389             for (@nodes) { $->value("x") }
390             ...
391              
392             will then print the expected C<< [0,'x','x'] >>.
393              
394             =head1 HOMEPAGE
395              
396             Please visit the project's homepage at L<https://metacpan.org/release/Data-CSel-WrapStruct>.
397              
398             =head1 SOURCE
399              
400             Source repository is at L<https://github.com/perlancar/perl-Data-CSel-WrapStruct>.
401              
402             =head1 BUGS
403              
404             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-CSel-WrapStruct>
405              
406             When submitting a bug or request, please include a test-file or a
407             patch to an existing test-file that illustrates the bug or desired
408             feature.
409              
410             =head1 SEE ALSO
411              
412             L<Data::CSel>
413              
414             =head1 AUTHOR
415              
416             perlancar <perlancar@cpan.org>
417              
418             =head1 COPYRIGHT AND LICENSE
419              
420             This software is copyright (c) 2020, 2016 by perlancar@cpan.org.
421              
422             This is free software; you can redistribute it and/or modify it under
423             the same terms as the Perl 5 programming language system itself.
424              
425             =cut