File Coverage

blib/lib/Banal/Utils/Data.pm
Criterion Covered Total %
statement 30 155 19.3
branch 0 80 0.0
condition 0 38 0.0
subroutine 10 16 62.5
pod 3 3 100.0
total 43 292 14.7


line stmt bran cond sub pod time code
1             #===============================================
2             package Banal::Utils::Data;
3              
4 1     1   11369 use 5.006;
  1         9  
  1         109  
5 1     1   7 use utf8;
  1         3  
  1         25  
6 1     1   103 use strict;
  1         2  
  1         56  
7 1     1   6 use warnings;
  1         3  
  1         43  
8 1     1   4 no warnings qw(uninitialized);
  1         2  
  1         116  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw( banal_get_data
14             flatten_complex_data_to_list
15             flatten_complex_data_to_list_with_options
16             );
17 1     1   7 use Carp;
  1         3  
  1         115  
18 1     1   3483 use Data::Dumper;
  1         13060  
  1         88  
19 1     1   9 use Banal::Utils::String qw(trim);
  1         2  
  1         67  
20 1     1   6 use Banal::Utils::Array qw(array1_starts_with_array2);
  1         1  
  1         931  
21              
22              
23              
24              
25             ##############################################################################"
26             # PUBLIC (exportable) FUNCTIONS
27             ##############################################################################"
28              
29              
30             #----------------------------------
31             # Function, not a method!
32             #----------------------------------
33             sub banal_get_data {
34 0     0 1   my $args = {@_};
35 0   0       my $opts = $args->{options} || {};
36 0           my $search_upwards_while_not_defined = $opts->{search_upwards_while_not_defined};
37              
38            
39            
40             # This is where the MAGIC happens. For a full list of options, see the function "normalize_data_root_and_keys()".
41 0           my ($root, @keys) = _normalize_data_root_and_keys (@_);
42            
43             # The data root should have been defined by now.
44 0 0         return unless ($root);
45            
46             # All this for nothing?
47 0 0         return $root if (scalar(@keys) < 1);
48              
49             # The reason for the below loop is to allow outer level 'variables' to be used when the variable is not defined at the proper (inner) level.
50             # Very handy for CONFIGURATION handling scenarios.
51 0           my $key = pop @keys;
52 0           while (scalar(@keys) >= 0) {
53 0           my $value= _banal_basic_get_data_via_key_list(data=>$root, keys=>[@keys, $key]);
54 0 0         return $value if defined($value);
55            
56             # Continue searching upwards if we are allowed to do so. Return otherwise.
57 0 0         return unless $search_upwards_while_not_defined;
58            
59 0           pop @keys;
60             }
61 0           return;
62             }
63              
64              
65              
66             #-----------------------------------------------
67             # FUNCTION (not a method).
68             #-----------------------------------------------
69             sub flatten_complex_data_to_list {
70 0     0 1   return flatten_complex_data_to_list_with_options (data=>[@_], on_ArrayRef=>'flatten', on_HashRef=>'flatten', on_ScalarRef=>'flatten');
71             }
72              
73              
74             #-----------------------------------------------
75             # FUNCTION (not a method).
76             #-----------------------------------------------
77             sub flatten_complex_data_to_list_with_options {
78 0     0 1   my $opts = {@_};
79 0           my $data = $opts->{data};
80 0   0       my $on_ArrayRef = $opts->{on_ArrayRef} || 'flatten';
81 0   0       my $on_HashRef = $opts->{on_HashRef} || 'flatten';
82 0   0       my $on_ScalarRef = $opts->{on_ScalarRef} || 'flatten';
83 0           my @list = ();
84            
85 0           foreach my $datum (@$data) {
86 0 0 0       if ((reftype($datum) eq 'ARRAY') && ($on_ArrayRef =~ /^flatten|dereference$/io)){
    0 0        
    0 0        
87 0           push @list, flatten_complex_data_to_list_with_options(data=>$datum);
88 0           next;
89             }elsif ((reftype($datum) eq 'HASH') && ($on_HashRef =~ /^flatten|dereference$/io)){
90 0           push @list, flatten_complex_data_to_list_with_options(data=>[%$datum]);
91 0           next;
92             }elsif ((reftype($datum) eq 'SCALAR') && ($on_ScalarRef =~ /^flatten|dereference$/io)){
93 0           push @list, flatten_complex_data_to_list_with_options(data=>[$$datum]);;
94 0           next;
95              
96             }else {
97 0           push @list, $datum;
98 0           next;
99             }
100            
101             }
102 0           return @list;
103             }
104              
105              
106             #*******************************************************************
107             # PRIVATE (non-exported) FUNCTIONS
108             #*******************************************************************
109            
110             #----------------------------------
111             sub _is_absolute_data_key_reference {
112 0   0 0     return ((scalar(@_) > 0) && !$_[0]);
113             }
114              
115             #----------------------------------
116             sub _normalize_data_root_and_keys {
117 0     0     my $args = {@_};
118 0   0       my $keys = $args->{keys} || $args->{key} || $args->{path} || [];
119 0           my $data = $args->{data};
120 0   0       my $context = $args->{context} || [];
121 0   0       my $opts = $args->{options} || {};
122 0   0       my $separator = $opts->{path_separator} || $opts->{separator} || '/';
123 0 0         my $remove_extra_separators = defined($opts->{remove_extra_separators}) ? $opts->{remove_extra_separators} : 1;
124 0 0         my $remove_leading_separator = defined($opts->{remove_leading_separator}) ? $opts->{remove_leading_separator} : 0;
125 0 0         my $remove_trailing_separator = defined($opts->{remove_trailing_separator}) ? $opts->{remove_trailing_separator} : $remove_extra_separators;
126 0 0         my $remove_empty_segments = defined($opts->{remove_empty_segments}) ? $opts->{remove_empty_segments} : 0;
127 0 0         my $try_avoiding_repeated_segments = defined($opts->{try_avoiding_repeated_segments}) ? $opts->{try_avoiding_repeated_segments} : 0;
128 0   0       my $lc = $opts->{lower_case} || $opts->{lc} || 0;
129 0   0       my $trim = $opts->{trim} || 0;
130            
131 0           my $mroot = undef; # Yeah, 'undef' by default.
132 0           my $relevant_keys = [];
133 0           my @accumulated_segs = ();
134 0           my $use_path_semantics;
135            
136             {
137 1     1   7 no warnings;
  1         3  
  1         1129  
  0            
138 0 0 0       $use_path_semantics = (defined($opts->{path}) && (($keys eq $opts->{path}) || ($keys == $opts->{path}))) ? 1 : $opts->{use_path_semantics};
139             }
140            
141             # Flatten all context and key segments (which are potentially a mix of path segment strings)
142 0           $keys = flatten_complex_data_to_list_with_options(data=>[$data, $context, $keys], on_HashRef=>'keep');
143            
144             # If we've got a HASH reference given as a key (or context) segment, that's our root. Otherwise, build the relevant thingy (relative to the root).
145 0           foreach my $key (reverse @$keys) {
146 0 0         if (reftype($key) eq 'HASH') {
147 0           $mroot = $key;
148 0           last;
149             }else {
150 0           unshift @$relevant_keys, $key;
151             }
152             }
153            
154             # Flatten all context and key segments (which are potentially a mix of path segment strings)
155 0           while ( scalar(@$relevant_keys)) {
156 0           my $key = pop @$relevant_keys;
157 0           my @segs = flatten_complex_data_to_list_with_options(data=>[$key], on_HashRef=>'keep');
158            
159             # If it's an empty ARRAY, just ignore, and pass on to the next one.
160 0 0         next unless (scalar(@segs));
161            
162             # Do we have much to do, anyway?
163 0 0         if ($use_path_semantics) {
164 0           my $path = join($separator, @segs);
165            
166 0 0         $path =~ s/${separator}+/${separator}/ if ($remove_extra_separators);
167 0 0         $path =~ s/^${separator}// if ($remove_leading_separator); # If you ask for this, you won't be able to detect absolute paths.
168 0 0         $path =~ s/${separator}$// if ($remove_trailing_separator);
169            
170 0           @segs = split /$separator/, $path;
171             }
172            
173             # Lowercase and trim if required.
174 0 0         @segs = [map {lc($_)} @segs] if ($lc); # Segments are all automatically lowercased, if asked for it.
  0            
175 0 0         @segs = [map {trim($_)} @segs] if ($trim); # Segments are all automatically trimmed, if asked for.
  0            
176            
177 0           my @prepend_segs = @segs;
178 0 0         if($try_avoiding_repeated_segments) {
179 0           @prepend_segs = ();
180 0           while (scalar(@segs)) {
181 0 0         if (array1_starts_with_array2([@accumulated_segs], [@segs])) {
182 0           last;
183             }
184            
185 0           my $s = shift @segs;
186 0           push @prepend_segs, $s;
187             }
188             }
189            
190 0           @accumulated_segs = (@prepend_segs, @accumulated_segs);
191            
192 0 0         last if (_is_absolute_data_key_reference(@accumulated_segs));
193             }
194            
195            
196             # Remove empty segments if we are asked for it.
197             # If you ask for this, you won't be able to detect absolute paths later on (normally, we have already done the detection for you, though)
198 0 0         @accumulated_segs = grep (/^\s*$/i, @accumulated_segs) if ($remove_empty_segments);
199            
200             # Here's a little something: We insert the root to the begining of the array.
201 0           unshift @accumulated_segs, $mroot;
202            
203 0 0         return wantarray ? @accumulated_segs : [@accumulated_segs];
204             }
205              
206             #----------------------------------
207             # Function, not a method!
208             # Allows to get a data element within a deep structure composed of possibly complex data types (HASH, ARRAY, ...)
209             # Example:
210             # _banal_basic_get_data_via_key_list (data=>$h, keys=>["employee[23]", "department", "name"])
211             #
212             # In this example, we are assuming that the initial data ($h) is a HASH that has a key called 'employee' which refers to an ARRAY of hashes, ....
213             #----------------------------------
214             sub _banal_basic_get_data_via_key_list {
215 0     0     my $args = {@_};
216 0           my $data = $args->{data};
217 0           my $keys = $args->{keys};
218 0           my @segments = @$keys;
219            
220 0           foreach my $segment (@segments) {
221 0 0         next unless $segment;
222            
223 0           my $element = $segment;
224 0           my $index;
225            
226 0 0         if($element =~ /^([^\[]*)\[(\d+)\]$/) {
227 0           $element = $1;
228 0           $index = $2;
229             }
230             else {
231 0           $index = undef;
232             }
233            
234             # We're on a SCALAR. Fishy, since we have got a key segment, too.
235 0 0         unless(reftype($data)) {
236 0           return;
237             }
238            
239             # We're on a SCALAR Reference. Fishy, since we have got a key segment, too.
240 0 0         if(reftype($data) eq "SCALAR") {
241 0           return;
242             }
243            
244            
245             # We're on an ARRAY.
246 0 0         if(reftype($data) eq "ARRAY") {
247 0 0 0       if (defined ($index) && !defined($element)) {
    0          
248 0 0         if(exists $data->[$index]) {
249 0           $data = $data->[$index];
250 0           next;
251             }
252             else {
253 0           croak "No element with index $index!\n";
254             }
255             }elsif (!defined($element)) {
256 0           return $data
257             }
258 0           return;
259             }
260            
261            
262             # We're on a HASH.
263 0 0         if(reftype($data) eq "HASH") {
264            
265             # the entire segment (even if it matches the array indexing pattern!)
266 0 0         if (exists $data->{$segment}) {
267 0           $data = $data->{$segment}; # this way, we are able to retreive weird hash values with keys that actually match our array indexing.
268 0           next;
269             }
270            
271             # Now, we are on the normal route.
272 0 0         if (! exists $data->{$element}) {
273 0           return;
274             }
275 0 0         if(reftype($data->{$element}) eq "ARRAY") {
276 0 0         if(! defined($index) ) {
277             #croak "$element is an array but you didn't specify an index to access it!\n";
278 0           $data = $data->{$element};
279 0           next;
280             }
281             else {
282 0 0         if(exists $data->{$element}->[$index]) {
283 0           $data = $data->{$element}->[$index];
284 0           next;
285             }
286             else {
287 0           croak "$element doesn't have an element with index $index!\n";
288 0           return;
289             }
290             }
291             }
292             else {
293 0           $data = $data->{$element};
294             }
295             }
296             }
297            
298 0           return $data;
299             }
300              
301              
302             1;
303              
304              
305             __END__