File Coverage

blib/lib/Jmespath/Functions.pm
Criterion Covered Total %
statement 312 331 94.2
branch 220 254 86.6
condition 102 135 75.5
subroutine 47 50 94.0
pod 12 35 34.2
total 693 805 86.0


line stmt bran cond sub pod time code
1             package Jmespath::Functions;
2 2     2   7 use strict;
  2         1  
  2         42  
3 2     2   5 use warnings;
  2         3  
  2         34  
4 2     2   30 use parent 'Exporter';
  2         2  
  2         7  
5 2     2   67 use JSON;
  2         1  
  2         7  
6 2     2   145 use Try::Tiny;
  2         2  
  2         79  
7 2     2   485 use POSIX qw(ceil floor);
  2         5025  
  2         10  
8 2     2   852 use Jmespath::Expression;
  2         2  
  2         41  
9 2     2   6 use Jmespath::ValueException;
  2         2  
  2         38  
10 2     2   592 use Jmespath::JMESPathTypeException;
  2         3  
  2         53  
11 2     2   629 use Jmespath::String;
  2         4  
  2         45  
12 2     2   8 use Scalar::Util qw(looks_like_number isdual blessed);
  2         2  
  2         86  
13 2     2   14 use v5.12;
  2         4  
14              
15             our @EXPORT_OK = qw( jp_abs
16             jp_avg
17             jp_contains
18             jp_ceil
19             jp_ends_with
20             jp_eq
21             jp_floor
22             jp_gt
23             jp_gte
24             jp_join
25             jp_keys
26             jp_length
27             jp_lt
28             jp_lte
29             jp_map
30             jp_max
31             jp_max_by
32             jp_merge
33             jp_min
34             jp_min_by
35             jp_ne
36             jp_not_null
37             jp_reverse
38             jp_sort
39             jp_sort_by
40             jp_starts_with
41             jp_sum
42             jp_to_array
43             jp_to_string
44             jp_to_number
45             jp_type
46             jp_values );
47              
48             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
49              
50             # jp_abs
51             #
52             # Absolute value of provided value. Throws exception if value is not
53             # a signed integer.
54             sub jp_abs {
55 10     10 1 11 my @args = @_;
56 10 100 100     41 Jmespath::ValueException
57             ->new({ message => 'abs() requires one argument' })
58             ->throw
59             if scalar @args < 1 or scalar @args > 1;
60 8         8 my $arg = shift @args;
61 8 100       20 Jmespath::ValueException
62             ->new({ message => 'contains() illegal boolean value' })
63             ->throw
64             if JSON::is_bool($arg);
65              
66 7 100       59 Jmespath::ValueException
67             ->new({ message => 'Not a number: [' . $arg . ']'})
68             ->throw
69             if not looks_like_number($arg);
70 6         13 return abs($arg);
71             }
72              
73             sub jp_avg {
74 6     6 1 7 my ($values) = @_;
75 6 100       22 Jmespath::ValueException->new({ message => 'Required argument not array ref' })->throw
76             if ref $values ne 'ARRAY';
77            
78 3         4 foreach (@$values) {
79 10 100       28 Jmespath::ValueException->new({ message => 'Not a number: ' . $_ })->throw
80             if not looks_like_number($_);
81             }
82 1         4 return jp_sum($values) / scalar(@$values);
83             }
84              
85              
86             sub jp_contains {
87 6     6 1 6 my ( $subject, $search ) = @_;
88 6 100       12 Jmespath::ValueException
89             ->new({ message => 'contains() illegal boolean value' })
90             ->throw
91             if JSON::is_bool($subject);
92 5 100       29 if ( ref $subject eq 'ARRAY' ) {
    50          
93 3         5 foreach (@$subject) {
94 6 100       32 return JSON::true if ( $_ eq $search ); #must be exact string match
95             }
96 1         6 return JSON::false;
97             }
98             elsif ( ref $subject eq '' ) { # straight string
99 2 100       29 return JSON::true if $subject =~ /$search/;
100             }
101 1         4 return JSON::false;
102             }
103              
104              
105             sub jp_ceil {
106 5     5 1 5 my ($value) = @_;
107 5 50       11 Jmespath::ValueException
108             ->new({ message => 'ceil() requires one argument' })
109             ->throw
110             if not defined $value;
111 5 100       15 Jmespath::ValueException
112             ->new({ message => 'ceil() requires one number' })
113             ->throw
114             if not looks_like_number($value);
115 4         22 return ceil($value);
116             }
117              
118             sub jp_ends_with {
119 6     6 1 7 my ( $subject, $suffix ) = @_;
120 6 100       23 Jmespath::ValueException
121             ->new({ message => 'ends_with() allows strings only' })
122             ->throw
123             if looks_like_number($suffix);
124 5 100       64 return JSON::true if $subject =~ /$suffix$/;
125 2         5 return JSON::false;
126             }
127              
128             sub jp_eq {
129 288     288 1 245 my ($left, $right) = @_;
130 288 100 66     445 return JSON::true if not defined $left and not defined $right;
131 284 100 66     683 return JSON::false if not defined $left or not defined $right;
132              
133             # If $left or $right is a boolean, they both must be boolean
134 239 100 100     691 if (ref($left) eq 'JSON::PP::Boolean' or
135             ref($right) eq 'JSON::PP::Boolean') {
136 56 100 100     138 if (ref($left) eq 'JSON::PP::Boolean' and
137             ref($right) eq 'JSON::PP::Boolean' ) {
138 8 100       15 return JSON::true if $left == $right;
139 4         24 return JSON::false;
140             }
141 48         81 return JSON::false;
142             }
143              
144             #If $left or $right is a HASH or ARRAY reference, they need to be
145             #compared because comparison of json objects must be handled.
146 183 100 100     444 if (ref($left) eq 'HASH' or ref($right) eq 'HASH') {
147 43 100 100     104 if (ref($left) eq 'HASH' and ref($right) eq 'HASH') {
148 11 100       17 return JSON::true if hashes_equal($left, $right);
149             }
150 38         61 return JSON::false;
151             }
152              
153 140 100 100     355 if (ref($left) eq 'ARRAY' or ref($right) eq 'ARRAY') {
154 24 100 100     103 if (ref($left) eq 'ARRAY' and ref($right) eq 'ARRAY') {
155 8 100       12 return JSON::true if arrays_equal($left, $right);
156             }
157 20         34 return JSON::false;
158             }
159              
160 116 100 66     523 if (looks_like_number($left) and
    50 33        
161             looks_like_number($right)) {
162 87 100       178 return JSON::true if $left == $right;
163             }
164             elsif (not looks_like_number($left) and
165             not looks_like_number($right)) {
166 29 100       59 return JSON::true if $left eq $right;
167             }
168 55         106 return JSON::false;
169             }
170              
171             sub jp_floor {
172 5     5 1 6 my ($value) = @_;
173 5 50       12 Jmespath::ValueException
174             ->new({ message => 'floor() requires one argument' })
175             ->throw
176             if not defined $value;
177 5 100       20 Jmespath::ValueException
178             ->new({ message => 'floor() requires one number' })
179             ->throw
180             if not looks_like_number($value);
181 3         12 return floor($value);
182             }
183              
184              
185             sub jp_gt {
186 18     18 1 18 my ($left, $right) = @_;
187             # According to the JMESPath Specification, this function returns
188             # undef if the variants are not numbers.
189 18 50       40 return if not looks_like_number($left);
190 18 50       28 return if not looks_like_number($right);
191 18 100       36 return JSON::true if $left > $right;
192 11         21 return JSON::false;
193             }
194              
195             sub jp_gte {
196 4     4 1 4 my ($left, $right) = @_;
197             # According to the JMESPath Specification, this function returns
198             # undef if the variants are not numbers.
199 4 50       11 return if not looks_like_number($left);
200 4 50       9 return if not looks_like_number($right);
201 4 100       11 return JSON::true if $left >= $right;
202 2         5 return JSON::false;
203             }
204              
205             sub jp_lt {
206 22     22 1 24 my ($left, $right) = @_;
207             # According to the JMESPath Specification, this function returns
208             # undef if the variants are not numbers.
209 22 50       47 return if not looks_like_number($left);
210 22 50       40 return if not looks_like_number($right);
211 22 100       40 return JSON::true if $left < $right;
212 12         21 return JSON::false;
213             }
214              
215             sub jp_lte {
216 4     4 1 8 my ($left, $right) = @_;
217             # According to the JMESPath Specification, this function returns
218             # undef if the variants are not numbers.
219 4 50       10 return if not looks_like_number($left);
220 4 50       12 return if not looks_like_number($right);
221 4 100       10 return JSON::true if $left <= $right;
222 1         4 return JSON::false;
223             }
224              
225             sub jp_join {
226 10     10 0 11 my ( $glue, $array ) = @_;
227 10 100       25 Jmespath::ValueException
228             ->new({ message => 'Not an array: ' . $array })
229             ->throw
230             if ref $array ne 'ARRAY';
231 9 100       13 Jmespath::ValueException
232             ->new({ message => 'Glue not a string: ' . $glue })
233             ->throw
234             if jp_type($glue) ne 'string';
235              
236 8         10 foreach (@$array) {
237 17 100 66     20 Jmespath::ValueException
238             ->new({message => "Cannot join " . jp_type($_) . " $_"})
239             ->throw
240             if jp_type($_) ne 'string' and ref $_ ne 'Jmespath::String';
241             }
242 6         16 return join ( $glue, @$array );
243             }
244              
245              
246             sub jp_keys {
247 5     5 0 7 my ( $obj ) = @_;
248 5 100       22 Jmespath::ValueException
249             ->new({ message => 'array keys(object obj) argument illegal' })
250             ->throw
251             if ref $obj ne 'HASH';
252 2         4 my @objkeys = keys %$obj;
253 2         5 return \@objkeys;
254             }
255              
256             sub jp_length {
257 11     11 0 12 my ( $subject ) = @_;
258 11         9 my ( $length ) = 0;
259              
260 11 100 100     25 Jmespath::ValueException
261             ->new({ message => 'number length(string|array|object subject) argument illegal' })
262             ->throw
263             if JSON::is_bool($subject) or looks_like_number($subject);
264 9 100       78 return scalar @$subject if ref $subject eq 'ARRAY';
265 8 100       15 return scalar keys %$subject if ref $subject eq 'HASH';
266 6         15 return length $subject;
267             }
268              
269             sub jp_map {
270 8     8 1 11 my ($expr, $elements) = @_;
271             # return [] if ref $elements ne 'ARRAY';
272 8 100       22 Jmespath::ValueException
273             ->new({ message => 'array[any] map(expression->any->any expr, array[any] elements) undefined elements' })
274             ->throw
275             if not defined $elements;
276 7         8 my $result = [];
277 7         9 foreach my $element (@$elements) {
278 2     2   9 use Data::Dumper;
  2         2  
  2         3337  
279 29 50       48 Jmespath::ValueException
280             ->new({ message => 'array[any] map(expression->any->any expr, array[any] elements) undefined elements' })
281             ->throw
282             if not defined $element;
283 29         43 my $res = $expr->visit($expr->{expression}, $element);
284 29         44 push @$result, $res;
285             }
286 7         21 return $result;
287             }
288              
289             # must be all numbers or strings in order to work
290             # perhaps consider List::Util max()
291             sub jp_max {
292 10     10 0 13 my ( $collection ) = @_;
293 10 100       26 Jmespath::ValueException->new({message=>'max(string|number array) argument not an array'})->throw
294             if ref $collection ne 'ARRAY';
295 9         9 my ($current_type, $current_max);
296 9         13 foreach my $arg (@$collection) {
297              
298 33         40 my $type = jp_type($arg);
299 33 100       45 $current_type = $type if not defined $current_type;
300 33 100       38 $current_max = $arg if not defined $current_max;
301              
302 33 100       46 Jmespath::ValueException
303             ->new({message=>"max(string|number array) mixed types not allowed"})
304             ->throw
305             if $type ne $current_type;
306              
307 32 50 66     52 Jmespath::ValueException
308             ->new({message=>"max(string|number array) $type not allowed"})
309             ->throw
310             if $type ne 'number' and $type ne 'string';
311              
312 32 100 100     108 if (looks_like_number($arg) and $arg > $current_max) { $current_max = $arg; }
  11         10  
313 32 100 100     58 if (not looks_like_number($arg) and $arg gt $current_max) { $current_max = $arg; }
  2         2  
314 32         31 $current_type = $type;
315             }
316 8         47 return $current_max;
317             }
318              
319             sub jp_max_by {
320 5     5 0 5 my ($array, $expref) = @_;
321 5         6 my $values = {};
322 5         8 foreach my $item (@$array) {
323 19         34 my $result = $expref->visit($expref->{expression}, $item);
324 19 100 100     52 Jmespath::ValueException
325             ->new({message=>"min(string|number array) mixed types not allowed"})
326             ->throw
327             if not defined $result or JSON::is_bool($result);
328 17         90 $values->{ $result } = $item;
329             }
330 3         10 my @keyed_on = keys %$values;
331 3         7 return $values->{ jp_max( \@keyed_on ) };
332             }
333              
334             # this needs to be a comparison function based on the "type" that is
335             # being sorted so the correct min/max will be taken by type.
336              
337             sub jp_keyed_max {
338 0     0 0 0 my ($array, $keyfunc) = @_;
339 0         0 return $array;
340             }
341              
342             sub _create_key_func {
343 0     0   0 my ($expref, $allowed_types, $function_name) = @_;
344             my $keyfunc = sub {
345 0     0   0 my $result = $expref->visit($expref->{expression}, shift);
346 0         0 };
347 0         0 return $keyfunc;
348             }
349              
350             sub jp_merge {
351 5     5 0 8 my @objects = @_;
352 5         5 my $merged = {};
353 5         6 foreach my $object (@objects) {
354 10 50       16 Jmespath::ValueException
355             ->new({message=>"object merge([object *argument, [, object $...]])"})
356             ->throw
357             if ref $object ne 'HASH';
358 10         19 foreach my $key (keys %$object) {
359 9         13 $merged->{$key} = $object->{$key};
360             }
361             }
362 5         12 return $merged;
363             }
364              
365             sub jp_min {
366 10     10 0 11 my ($collection) = @_;
367 10 100       30 Jmespath::ValueException->new({message=>'min(string|number array) argument not an array'})->throw
368             if ref $collection ne 'ARRAY';
369 9         9 my ($current_type, $current_min);
370 9         13 foreach my $arg (@$collection) {
371              
372 33         36 my $type = jp_type($arg);
373 33 100       47 $current_type = $type if not defined $current_type;
374 33 100       38 $current_min = $arg if not defined $current_min;
375              
376 33 100       41 Jmespath::ValueException->new({message=>"min(string|number array) mixed types not allowed"})->throw
377             if $type ne $current_type;
378              
379 32 50 66     57 Jmespath::ValueException->new({message=>"min(string|number array) $type not allowed"})->throw
380             if $type ne 'number' and $type ne 'string';
381              
382 32 100 100     102 if (looks_like_number($arg) and $arg < $current_min) { $current_min = $arg; }
  9         9  
383 32 50 66     63 if (not looks_like_number($arg) and $arg lt $current_min) { $current_min = $arg; }
  0         0  
384 32         29 $current_type = $type;
385             }
386 8         25 return $current_min;
387             }
388              
389             sub jp_min_by {
390 5     5 0 4 my ($array, $expref) = @_;
391 5         6 my $values = {};
392 5         10 foreach my $item (@$array) {
393 19         28 my $result = $expref->visit($expref->{expression}, $item);
394 19 100 100     57 Jmespath::ValueException
395             ->new({message=>"min(string|number array) mixed types not allowed"})
396             ->throw
397             if not defined $result or JSON::is_bool($result);
398 17         85 $values->{ $result } = $item;
399             }
400 3         10 my @keyed_on = keys %$values;
401 3         19 return $values->{ jp_min( \@keyed_on ) };
402             }
403              
404             sub jp_ne {
405 130     130 0 115 my ($left, $right) = @_;
406 130 100 66     201 return JSON::false if not defined $left and not defined $right;
407 128 100 66     290 return JSON::true if not defined $left or not defined $right;
408              
409 100 100 100     284 if (ref($left) eq 'JSON::PP::Boolean' or
410             ref($right) eq 'JSON::PP::Boolean') {
411 48 100 100     129 if (ref($left) eq 'JSON::PP::Boolean' and
412             ref($right) eq 'JSON::PP::Boolean' ) {
413 8 100       21 return JSON::true if $left != $right;
414 4         26 return JSON::false;
415             }
416             # else {
417             # return JSON::false;
418             # }
419 40         65 return JSON::true;
420             }
421              
422 52 100 100     135 if (ref($left) eq 'HASH' or ref($right) eq 'HASH') {
423 24 100 100     76 if (ref($left) eq 'HASH' and ref($right) eq 'HASH') {
424 4 100       9 return JSON::false if hashes_equal($left, $right);
425             }
426 22         38 return JSON::true;
427             }
428              
429 28 100 100     82 if (ref($left) eq 'ARRAY' or ref($right) eq 'ARRAY') {
430 16 100 100     38 if (ref($left) eq 'ARRAY' and ref($right) eq 'ARRAY') {
431 4 100       9 return JSON::false if arrays_equal($left, $right);
432             }
433 14         27 return JSON::true;
434             }
435              
436            
437 12 50 33     53 if (looks_like_number($left) and
438             looks_like_number($right)) {
439 12 100       36 return JSON::true if $left != $right;
440 5         10 return JSON::false;
441             }
442              
443 0         0 return JSON::false;
444             }
445              
446             #
447             #
448             sub jp_not_null {
449 10     10 0 18 my @arguments = @_;
450 10 100       23 Jmespath::ValueException
451             ->new({ message => 'not_null() requires at least one argument' })
452             ->throw
453             if not @arguments;
454 9         8 my $result = [];
455 9         12 foreach my $argument (@arguments) {
456 26 100       37 next if not defined $argument;
457 8 50       28 return $argument if defined $argument;
458             }
459 1         3 return;
460             }
461              
462             sub jp_reverse {
463 5     5 0 6 my $arg = shift;
464            
465 5 100       15 if (ref $arg eq 'ARRAY') {
    50          
466 3         3 my $result = [];
467 3         10 for ( my $idx = scalar @$arg - 1; $idx >= 0; $idx--) {
468 10         17 push @$result, @$arg[$idx];
469             }
470 3         8 return $result;
471             }
472             elsif (ref $arg eq '') {
473 2         6 return reverse $arg;
474             }
475 0         0 return;
476             }
477              
478             sub jp_sort {
479 9     9 0 11 my $list = shift;
480 9 100 100     42 Jmespath::ValueException
481             ->new({ message=>'array sort(array[number]|array[string] $list) illegal argument' })
482             ->throw
483             if not defined $list or ref $list ne 'ARRAY';
484 7         4 my $current_type;
485 7         12 foreach (@$list) {
486 19 50       26 Jmespath::ValueException
487             ->new({ message=>'array sort(array[number]|array[string] $list) illegal argument' })
488             ->throw
489             if ref $_ ne '';
490 19 100       33 $current_type = jp_type($_) if not defined $current_type;
491 19 50 66     43 Jmespath::ValueException
492             ->new({ message=>'array sort(array[number]|array[string] $list) illegal argument' })
493             ->throw
494             if $current_type ne 'string' and $current_type ne 'number';
495 19 100       20 Jmespath::ValueException
496             ->new({ message=>'array sort(array[number]|array[string] $list) mixed types' })
497             ->throw
498             if $current_type ne jp_type($_);
499             }
500              
501 6         18 my @result = sort { $a cmp $b } @$list ;
  11         30  
502 6         17 return \@result;
503             }
504              
505             sub jp_sort_by {
506 11     11 0 13 my ($array, $expref) = @_;
507 11         12 my $values = {};
508 11         10 my $keyed = [];
509 11         6 my $current_type;
510             # create "symbol map" for items
511 11 100       24 Jmespath::ValueException
512             ->new({message=>"sort_by(array elements, expression->number|expression->string expr) undefined expr not allowed"})
513             ->throw
514             if not defined $expref;
515 10         22 for (my $idx = 0; $idx < scalar @$array; $idx++) {
516 45         30 $values->{$idx} = @{$array}[$idx];
  45         75  
517 45         38 my $evaled = $expref->visit($expref->{expression}, @{$array}[$idx]);
  45         141  
518 45 100       88 $current_type = jp_type($evaled) if not defined $current_type;
519            
520 45 100       57 Jmespath::ValueException
521             ->new({message=>"sort_by(array elements, expression->number|expression->string expr) undefined expr not allowed"})
522             ->throw
523             if jp_type($evaled) ne $current_type;
524 43 100 66     109 Jmespath::ValueException
525             ->new({message=>"min(string|number array) mixed types not allowed"})
526             ->throw
527             if not defined $evaled or JSON::is_bool($evaled);
528 42         232 push @$keyed, [ $evaled, $idx ];
529             }
530 7         23 my @sorted = sort { $a->[0] cmp $b->[0] } @$keyed;
  53         69  
531 7         7 my $res = [];
532 7         9 foreach my $item (@sorted) {
533 36         40 push @$res, $values->{$item->[1]};
534             }
535 7         35 return $res;
536             }
537              
538             sub jp_starts_with {
539 5     5 0 6 my ($subject, $prefix) = @_;
540 5 50 33     16 Jmespath::ValueException->new({message=>'starts_with(subject, prefix) requires two arguments'})->throw
541             if not defined $subject or not defined $prefix;
542 5 100       21 Jmespath::ValueException->new({message=>'starts_with(subject, prefix) not a string'})->throw
543             if looks_like_number($prefix);
544 4 100       59 return JSON::true if $subject =~ /^$prefix/;
545 1         4 return JSON::false;
546             }
547              
548             sub jp_sum {
549 6     6 0 6 my $data = shift;
550 6         6 my $result = 0;
551 6         7 foreach my $value (@$data) {
552 21 100       41 Jmespath::ValueException
553             ->new({message=>'sum(numbers) member not a number'})
554             ->throw
555             if not looks_like_number($value);
556 20         16 $result += $value;
557             }
558 5         12 return $result;
559             }
560              
561             sub jp_to_array {
562 5     5 0 7 my ($arg) = shift;
563 5 100       10 return [$arg] if JSON::is_bool($arg);
564 4 100       23 return [$arg] if ref $arg eq 'HASH';
565 3 100       9 return $arg if ref $arg eq 'ARRAY';
566 2         5 return [$arg];
567             }
568              
569             sub jp_to_string {
570 10     10 0 12 my ($arg) = @_;
571 10 100 66     13 $arg = JSON->new->pretty(0)->allow_nonref->encode( $arg )
572             if jp_type($arg) eq 'object'
573             or jp_type($arg) eq 'array';
574 10 100       14 $arg = do { bless \( my $dummy = $arg), "Jmespath::String" }
  8         18  
575             if jp_type($arg) eq 'number';
576 10         21 return $arg;
577             }
578              
579             sub jp_to_number {
580 35     35 0 32 my ($arg) = @_;
581              
582 35 100       88 return if not looks_like_number($arg);
583 29 100       48 return if JSON::is_bool($arg);
584 28         119 $arg += 0; # remove trailing 0's
585 28         49 return $arg;
586             }
587              
588             sub jp_type {
589 212     212 0 169 my ($item) = @_;
590 212 100       269 return 'null' if not defined $item;
591 210 100       296 if (JSON::is_bool($item)) { return 'boolean'; }
  4         26  
592 206 100       877 if (looks_like_number($item)) {
593 146 50       186 return 'string' if ref $item eq 'Jmespath::String';
594 146         252 return 'number';
595             }
596 60 100       79 if (ref($item) eq 'ARRAY') { return 'array';}
  3         22  
597 57 100       69 if (ref($item) eq 'HASH' ) {return 'object';}
  2         4  
598 55         102 return 'string';
599             }
600              
601             sub jp_values {
602 2     2 0 2 my $obj = shift;
603 2 100       16 Jmespath::ValueException
604             ->new({message =>'array values(object $obj): illegal argument'})
605             ->throw
606             if ref $obj ne 'HASH';
607 1         2 my $result = [];
608 1         4 foreach my $item (keys %$obj) {
609 2         4 push @$result, $obj->{$item};
610             }
611 1         3 return $result;
612             }
613              
614              
615             sub arrays_equal {
616 14     14 0 17 my ( $a, $b ) = @_;
617 14 50       25 if ( scalar @$a != scalar @$b ) {
618 0         0 return 0;
619             }
620 14         10 for my $i ( 0 .. $#{$a} ) {
  14         34  
621 14         14 my $va = $a->[$i];
622 14         10 my $vb = $b->[$i];
623 14 50 33     123 if ( ref $va ne ref $vb ) {
    100 66        
    50 33        
    50 33        
    50 33        
624 0         0 return 0;
625             }
626             elsif ( ref $va eq '' and ref $vb eq '' and $va ne $vb) {
627 6         16 return 0;
628             }
629             elsif ( ref $va eq 'SCALAR' && $va ne $vb ) {
630 0         0 return 0;
631             }
632             elsif ( ref $va eq 'ARRAY' && !arrays_equal( $va, $vb ) ) {
633 0         0 return 0;
634             }
635             elsif ( ref $va eq 'HASH' && !hashes_equal( $va, $vb ) ) {
636 0         0 return 0;
637             }
638             }
639 8         33 return 1;
640             }
641              
642             sub hashes_equal {
643 15     15 0 14 my ( $a, $b ) = @_;
644 15 50       35 if ( scalar( keys %$a ) != scalar( keys %$b ) ) {
645 0         0 return 0;
646             }
647 15         30 for my $k ( keys %$a ) {
648 17 100       26 if ( exists $b->{$k} ) {
649 11         13 my $va = $a->{$k};
650 11         10 my $vb = $b->{$k};
651 11 50 66     96 if ( ref $va ne ref $vb ) {
    100 100        
    50 33        
    50 66        
    50 33        
652 0         0 return 0;
653             }
654             elsif ( ref $va eq '' and ref $vb eq '' and $va ne $vb ) {
655 2         5 return 0;
656             }
657             elsif ( ref $va eq 'SCALAR' && $va ne $vb ) {
658 0         0 return 0;
659             }
660             elsif ( ref $va eq 'ARRAY' && !arrays_equal( $va, $vb ) ) {
661 0         0 return 0;
662             }
663             elsif ( ref $va eq 'HASH' && !hashes_equal( $va, $vb ) ) {
664 0         0 return 0;
665             }
666             }
667             else {
668 6         14 return 0;
669             }
670             }
671 7         24 return 1;
672             }
673              
674             1;
675              
676             __END__
677              
678             =head1 NAME
679              
680             Functions.pm : JMESPath Built-In Functions
681              
682             =head1 EXPORTED FUNCTIONS
683              
684             =head2 jp_abs(value)
685              
686             =head2 jp_avg(values)
687              
688             =head2 jp_contains(subject, search)
689              
690             =head2 jp_ceil(value)
691              
692             =head2 jp_ends_with(subject, suffix)
693              
694             =head2 jp_eq
695              
696             =head2 jp_floor(value)
697              
698             =head2 jp_gt(left, right)
699              
700             =head2 jp_gte(left, right)
701              
702             =head2 jp_lt(left, right)
703              
704             =head2 jp_lte(left, right)
705              
706             =head2 jp_map($expr, $elements)
707              
708             Implements the L<JMESPath Built-In
709             Function|http://jmespath.org/specification.html#built-in-functions>
710             L<map()|http://jmespath.org/specification.html#map>
711