File Coverage

blib/lib/Data/Seek/Search.pm
Criterion Covered Total %
statement 119 121 98.3
branch 34 38 89.4
condition 11 15 73.3
subroutine 11 11 100.0
pod 3 3 100.0
total 178 188 94.6


line stmt bran cond sub pod time code
1             # ABSTRACT: Data::Seek Search Execution Class
2             package Data::Seek::Search;
3              
4 3     3   1138 use Data::Seek::Exception;
  3         7  
  3         67  
5 3     3   1030 use Data::Seek::Exception::RootInvalid;
  3         4  
  3         67  
6 3     3   1001 use Data::Seek::Exception::RootUnknown;
  3         6  
  3         225  
7 3     3   1022 use Data::Seek::Exception::NodeInvalid;
  3         6  
  3         67  
8 3     3   1068 use Data::Seek::Exception::NodeUnknown;
  3         4  
  3         69  
9 3     3   698 use Data::Seek::Search::Result;
  3         4  
  3         60  
10              
11 3     3   12 use Data::Object::Class;
  3         2  
  3         9  
12              
13             our $VERSION = '0.06'; # VERSION
14              
15             has 'cache' => (
16             is => 'rw',
17             default => 0
18             );
19              
20             has 'criteria' => (
21             is => 'rw',
22             default => sub {{}}
23             );
24              
25             has 'data' => (
26             is => 'ro',
27             default => sub {{}}
28             );
29              
30             has 'data_cache' => (
31             is => 'ro',
32             default => sub { shift->data->encode },
33             lazy => 1
34             );
35              
36             has 'ignore' => (
37             is => 'rw',
38             default => 0
39             );
40              
41             sub criterion {
42 52     52 1 63 my $self = shift;
43 52         44 my $expr = shift;
44              
45             Data::Seek::Exception->throw(
46             message => 'INVALID CRITERION PROVIDED'
47             )
48 52 100 66     94 unless $expr && $expr =~ do {
49 52         46 my $name = '[\w\*]+';
50 52         44 my $indx = ':\d+';
51 52         46 my $iter = '\@';
52              
53 52         192 my $sect = join '|',
54             "(?:${indx}\\.|${indx}\$)",
55             "(?:${iter}\\.|${iter}\$)",
56             "(?:${name}\\.|${name}\$)",
57             "(?:${name}${indx}\\.|${name}${indx}\$)";
58              
59 52         3182 qr/^(?:${indx}|(${sect})+(?:${iter}|${name}(?:${indx})?)?)$/;
60             };
61              
62 51         128 $self->criteria->{$expr} = keys %{$self->criteria};
  51         170  
63 51         117 return $self;
64             }
65              
66             sub perform {
67 47     47 1 514 my $self = shift;
68              
69 47         54 my $criteria = $self->criteria;
70 47         114 $criteria = { reverse %$criteria };
71              
72 47 100       226 my $dataset = $self->cache ?
73             $self->data_cache : $self->data->encode;
74              
75 47         177981 my @orders = sort keys %$criteria;
76 47         83 my @criteria = @$criteria{@orders};
77              
78 47         46 my @results;
79              
80 47         59 for my $criterion (@criteria) {
81 48         688 my @nodes = keys %$dataset;
82 48         232 my @selectors = split /\./, $criterion;
83 48 100       55 @selectors = map { /([^\.]+):(\d+)/ ? ($1, ":$2") : $_ } @selectors;
  100         260  
84              
85 48         51 my @expressions;
86 48         118 for (my $i=0; $i<@selectors; $i++) {
87 107         93 my $first = $i == 0;
88 107         84 my $last = $i == $#selectors;
89              
90 107         90 my $selector = $selectors[$i];
91 107         94 my $query = quotemeta $selector;
92 107         82 my $keys = [];
93              
94 107         81 my $token;
95             my $regex;
96              
97 107         86 $token = quotemeta '\@';
98 107         67 $regex = ':\d+';
99 107         244 $query =~ s/^$token$/$regex/g;
100              
101 107         95 $token = quotemeta '\*';
102 107         172 $token = sprintf '(?:%s){2,}', $token;
103 107         80 $regex = '.*';
104 107         164 $query =~ s/$token/$regex/g;
105              
106 107         68 $token = quotemeta '\*';
107 107         105 $token = sprintf '(?:%s){1}', $token;
108 107         71 $regex = '[^\.]+';
109 107         120 $query =~ s/$token/$regex/g;
110              
111 107         268 push @expressions, $query;
112             }
113              
114 48         37 my $was_match = 0;
115 48         34 my $was_array = 0;
116 48         27 my $was_hash = 0;
117 48         35 my $was_ending = 0;
118              
119 48         73 for (my $i=0; $i<@expressions; $i++) {
120 97         78 my $first = $i == 0;
121 97         78 my $last = $i == $#expressions;
122 97         208 my @steps = grep defined, @selectors[0..$i];
123 97         117 my @query = @expressions[0..$i];
124              
125 97         88 my $query = shift @query;
126 97 100       100 $query .= join '', map { /^\\?:/ ? $_ : '\.' . $_ } @query;
  71         195  
127              
128 97         286 my $index = $steps[-1] =~ /^\@|[^\.]+:\d+$/;
129              
130 97         72 my $is_match = 0;
131 97         68 my $is_array = 0;
132 97         62 my $is_hash = 0;
133 97         61 my $is_ending = 0;
134              
135 97 100       427 $is_array = grep /^$query/, @nodes if $index;
136 97 100       3721 $is_array = grep /^$query:\d+/, @nodes if !$index;
137 97         3826 $is_hash = grep /^$query\./, @nodes;
138              
139 97         3681 $is_match = @nodes = grep /^$query/, @nodes;
140 97 100       1029 $is_ending = @nodes = grep /^$query$/, @nodes if $last;
141              
142 97 100       156 if (@nodes) {
143 74         58 $was_match = $is_match;
144 74         50 $was_array = $is_array;
145 74         39 $was_hash = $is_hash;
146 74         56 $was_ending = $is_ending;
147             }
148              
149 97 100       243 unless (@nodes) {
150 23 100       75 last if $self->ignore;
151              
152             my $format = sub {
153 36     36   30 my $expr = shift;
154 36 100       65 return "[0]" if $expr =~ /\@/;
155 28 100       43 return "[$1]" if $expr =~ /:(\d+)/;
156 26 50       35 return ($1, "[$2]") if $expr =~ /(.*):(\d+)/;
157 26 50       33 return "{...}" if $expr =~ /\*+/;
158 26         39 return $_;
159 11         36 };
160              
161 11         20 my $subject = [ map $format->($_), @steps ];
162 11         17 my $target = [ map $format->($_), @selectors ];
163              
164 11 100       18 if ($first) {
165 5 100 66     17 if (!$is_match) {
    50 66        
166 3         19 Data::Seek::Exception::RootUnknown->throw(
167             criterion => $criterion,
168             subject => $subject,
169             target => $target,
170             is_array => $is_array,
171             is_ending => $is_ending,
172             is_hash => $is_hash,
173             is_match => $is_match,
174             );
175             }
176             elsif ($is_match && !$is_hash && !$is_array) {
177 0         0 Data::Seek::Exception::RootUnknown->throw(
178             criterion => $criterion,
179             subject => $subject,
180             target => $target,
181             is_array => $is_array,
182             is_ending => $is_ending,
183             is_hash => $is_hash,
184             is_match => $is_match,
185             );
186             }
187             else {
188 2         15 Data::Seek::Exception::RootInvalid->throw(
189             criterion => $criterion,
190             subject => $subject,
191             target => $target,
192             is_array => $is_array,
193             is_ending => $is_ending,
194             is_hash => $is_hash,
195             is_match => $is_match,
196             );
197             }
198             }
199             else {
200 6 50 66     36 if (!$was_match) {
    100 100        
201 0         0 Data::Seek::Exception::NodeUnknown->throw(
202             criterion => $criterion,
203             subject => $subject,
204             target => $target,
205             was_array => $was_array,
206             was_ending => $was_ending,
207             was_hash => $was_hash,
208             was_match => $was_match,
209             );
210             }
211             elsif ($was_match && !$was_hash && !$was_array) {
212 3         17 Data::Seek::Exception::NodeUnknown->throw(
213             criterion => $criterion,
214             subject => $subject,
215             target => $target,
216             was_array => $was_array,
217             was_ending => $was_ending,
218             was_hash => $was_hash,
219             was_match => $was_match,
220             );
221             }
222             else {
223 3         19 Data::Seek::Exception::NodeInvalid->throw(
224             criterion => $criterion,
225             subject => $subject,
226             target => $target,
227             was_array => $was_array,
228             was_ending => $was_ending,
229             was_hash => $was_hash,
230             was_match => $was_match,
231             );
232             }
233             }
234             }
235             }
236 37         230 my $result = {nodes => [sort @nodes], criterion => $criterion};
237 37         93 push @results, $result;
238             }
239              
240 36         40 my $output = [];
241 36         40 for my $result (@results) {
242 37         28 $$result{dataset} = {map { $_ => $$dataset{$_} } @{$$result{nodes}}};
  460         515  
  37         57  
243 37         78 push @$output, $result;
244             }
245              
246 36         470 return $output;
247             }
248              
249             sub result {
250 47     47 1 738 return Data::Seek::Search::Result->new(
251             search => shift
252             );
253             }
254              
255             1;
256              
257             __END__