File Coverage

blib/lib/PPI/Find.pm
Criterion Covered Total %
statement 6 88 6.8
branch 0 40 0.0
condition n/a
subroutine 2 11 18.1
pod 7 7 100.0
total 15 146 10.2


line stmt bran cond sub pod time code
1             package PPI::Find;
2              
3             =pod
4              
5             =head1 NAME
6              
7             PPI::Find - Object version of the Element->find method
8              
9             =head1 SYNOPSIS
10              
11             # Create the Find object
12             my $Find = PPI::Find->new( \&wanted );
13            
14             # Return all matching Elements as a list
15             my @found = $Find->in( $Document );
16            
17             # Can we find any matching Elements
18             if ( $Find->any_matches($Document) ) {
19             print "Found at least one matching Element";
20             }
21            
22             # Use the object as an iterator
23             $Find->start($Document) or die "Failed to execute search";
24             while ( my $token = $Find->match ) {
25             ...
26             }
27              
28             =head1 DESCRIPTION
29              
30             PPI::Find is the primary PDOM searching class in the core PPI package.
31              
32             =head2 History
33              
34             It became quite obvious during the development of PPI that many of the
35             modules that would be built on top of it were going to need large numbers
36             of saved, storable or easily creatable search objects that could be
37             reused a number of times.
38              
39             Although the internal ->find method provides a basic ability to search,
40             it is by no means thorough. PPI::Find attempts to resolve this problem.
41              
42             =head2 Structure and Style
43              
44             PPI::Find provides a similar API to the popular L
45             module for file searching, but without the ability to assemble queries.
46              
47             The implementation of a separate PPI::Find::Rule sub-class that does
48             provide this ability is left as an exercise for the reader.
49              
50             =head2 The &wanted function
51              
52             At the core of each PPI::Find object is a "wanted" function that is
53             passed a number of arguments and returns a value which controls the
54             flow of the search.
55              
56             As the search executes, each Element will be passed to the wanted function
57             in depth-first order.
58              
59             It will be provided with two arguments. The current Element to test as $_[0],
60             and the top-level Element of the search as $_[1].
61              
62             The &wanted function is expected to return 1 (positive) if the Element
63             matches the condition, 0 (false) if it does not, and undef (undefined) if
64             the condition does not match, and the Find search should not descend to
65             any of the current Element's children.
66              
67             Errors should be reported from the &wanted function via die, which will be
68             caught by the Find object and returned as an error.
69              
70             =head1 METHODS
71              
72             =cut
73              
74 1     1   1008 use strict;
  1         3  
  1         33  
75 1     1   4 use Params::Util qw{_INSTANCE};
  1         3  
  1         927  
76              
77             our $VERSION = '1.277';
78              
79              
80              
81              
82              
83             #####################################################################
84             # Constructor
85              
86             =pod
87              
88             =head2 new &wanted
89              
90             The C constructor takes a single argument of the &wanted function,
91             as described above and creates a new search.
92              
93             Returns a new PPI::Find object, or C if not passed a CODE reference.
94              
95             =cut
96              
97             sub new {
98 0 0   0 1   my $class = ref $_[0] ? ref shift : shift;
99 0 0         my $wanted = ref $_[0] eq 'CODE' ? shift : return undef;
100              
101             # Create the object
102 0           my $self = bless {
103             wanted => $wanted,
104             }, $class;
105              
106 0           $self;
107             }
108              
109             =pod
110              
111             =head2 clone
112              
113             The C method creates another instance of the same Find object.
114              
115             The cloning is done safely, so if your existing Find object is in the
116             middle of an iteration, the cloned Find object will not also be in the
117             iteration and can be safely used independently.
118              
119             Returns a duplicate PPI::Find object.
120              
121             =cut
122              
123             sub clone {
124 0 0   0 1   my $self = ref $_[0] ? shift
125             : die "->clone can only be called as an object method";
126 0           my $class = ref $self;
127              
128             # Create the object
129             my $clone = bless {
130             wanted => $self->{wanted},
131 0           }, $class;
132              
133 0           $clone;
134             }
135              
136              
137              
138              
139              
140             ####################################################################
141             # Search Execution Methods
142              
143             =pod
144              
145             =head2 in $Document [, array_ref => 1 ]
146              
147             The C method starts and completes a full run of the search.
148              
149             It takes as argument a single L object which will
150             serve as the top of the search process.
151              
152             Returns a list of PPI::Element objects that match the condition
153             described by the &wanted function, or the null list on error.
154              
155             You should check the ->errstr method for any errors if you are
156             returned the null list, which may also mean simply that no Elements
157             were found that matched the condition.
158              
159             Because of this need to explicitly check for errors, an alternative
160             return value mechanism is provide. If you pass the C<< array_ref => 1 >>
161             parameter to the method, it will return the list of matched Elements
162             as a reference to an ARRAY. The method will return false if no elements
163             were matched, or C on error.
164              
165             The ->errstr method can still be used to get the error message as normal.
166              
167             =cut
168              
169             sub in {
170 0     0 1   my $self = shift;
171 0           my $Element = shift;
172 0           my %params = @_;
173 0           delete $self->{errstr};
174            
175             # Are we already acting as an iterator
176 0 0         if ( $self->{in} ) {
177 0           return $self->_error('->in called while another search is in progress', %params);
178             }
179              
180             # Get the root element for the search
181 0 0         unless ( _INSTANCE($Element, 'PPI::Element') ) {
182 0           return $self->_error('->in was not passed a PPI::Element object', %params);
183             }
184              
185             # Prepare the search
186 0           $self->{in} = $Element;
187 0           $self->{matches} = [];
188              
189             # Execute the search
190 0 0         if ( !eval { $self->_execute; 1 } ) {
  0            
  0            
191 0           my $errstr = $@;
192 0           $errstr =~ s/\s+at\s+line\s+.+$//;
193 0           return $self->_error("Error while searching: $errstr", %params);
194             }
195              
196             # Clean up and return
197 0           delete $self->{in};
198 0 0         if ( $params{array_ref} ) {
199 0 0         if ( @{$self->{matches}} ) {
  0            
200 0           return delete $self->{matches};
201             }
202 0           delete $self->{matches};
203 0           return '';
204             }
205              
206             # Return as a list
207 0           my $matches = delete $self->{matches};
208 0           @$matches;
209             }
210              
211             =pod
212              
213             =head2 start $Element
214              
215             The C method lets the Find object act as an iterator. The method
216             is passed the parent PPI::Element object as for the C method, but does
217             not accept any parameters.
218              
219             To simplify error handling, the entire search is done at once, with the
220             results cached and provided as-requested.
221              
222             Returns true if the search completes, and false on error.
223              
224             =cut
225              
226             sub start {
227 0     0 1   my $self = shift;
228 0           my $Element = shift;
229 0           delete $self->{errstr};
230              
231             # Are we already acting as an iterator
232 0 0         if ( $self->{in} ) {
233 0           return $self->_error('->in called while another search is in progress');
234             }
235              
236             # Get the root element for the search
237 0 0         unless ( _INSTANCE($Element, 'PPI::Element') ) {
238 0           return $self->_error('->in was not passed a PPI::Element object');
239             }
240              
241             # Prepare the search
242 0           $self->{in} = $Element;
243 0           $self->{matches} = [];
244              
245             # Execute the search
246 0 0         if ( !eval { $self->_execute; 1 } ) {
  0            
  0            
247 0           my $errstr = $@;
248 0           $errstr =~ s/\s+at\s+line\s+.+$//;
249 0           $self->_error("Error while searching: $errstr");
250 0           return undef;
251             }
252              
253 0           1;
254             }
255              
256             =pod
257              
258             =head2 match
259              
260             The C method returns the next matching Element in the iteration.
261              
262             Returns a PPI::Element object, or C if there are no remaining
263             Elements to be returned.
264              
265             =cut
266              
267             sub match {
268 0     0 1   my $self = shift;
269 0 0         return undef unless $self->{matches};
270              
271             # Fetch and return the next match
272 0           my $match = shift @{$self->{matches}};
  0            
273 0 0         return $match if $match;
274              
275 0           $self->finish;
276 0           undef;
277             }
278              
279             =pod
280              
281             =head2 finish
282              
283             The C method provides a mechanism to end iteration if you wish to
284             stop the iteration prematurely. It resets the Find object and allows it to
285             be safely reused.
286              
287             A Find object will be automatically finished when C returns false.
288             This means you should only need to call C when you stop
289             iterating early.
290              
291             You may safely call this method even when not iterating and it will return
292             without failure.
293              
294             Always returns true
295              
296             =cut
297              
298             sub finish {
299 0     0 1   my $self = shift;
300 0           delete $self->{in};
301 0           delete $self->{matches};
302 0           delete $self->{errstr};
303 0           1;
304             }
305              
306              
307              
308              
309              
310             #####################################################################
311             # Support Methods and Error Handling
312              
313             sub _execute {
314 0     0     my $self = shift;
315 0           my $wanted = $self->{wanted};
316 0           my @queue = ( $self->{in} );
317              
318             # Pull entries off the queue and hand them off to the wanted function
319 0           while ( my $Element = shift @queue ) {
320 0           my $rv = &$wanted( $Element, $self->{in} );
321              
322             # Add to the matches if returns true
323 0 0         push @{$self->{matches}}, $Element if $rv;
  0            
324              
325             # Continue and don't descend if it returned undef
326             # or if it doesn't have children
327 0 0         next unless defined $rv;
328 0 0         next unless $Element->isa('PPI::Node');
329              
330             # Add the children to the head of the queue
331 0 0         if ( $Element->isa('PPI::Structure') ) {
332 0 0         unshift @queue, $Element->finish if $Element->finish;
333 0           unshift @queue, $Element->children;
334 0 0         unshift @queue, $Element->start if $Element->start;
335             } else {
336 0           unshift @queue, $Element->children;
337             }
338             }
339              
340 0           1;
341             }
342              
343             =pod
344              
345             =head2 errstr
346              
347             The C method returns the error messages when a given PPI::Find
348             object fails any action.
349              
350             Returns a string, or C if there is no error.
351              
352             =cut
353              
354             sub errstr {
355 0     0 1   shift->{errstr};
356             }
357              
358             sub _error {
359 0     0     my $self = shift;
360 0           $self->{errstr} = shift;
361 0           my %params = @_;
362 0 0         $params{array_ref} ? undef : ();
363             }
364              
365             1;
366              
367             =pod
368              
369             =head1 TO DO
370              
371             - Implement the L class
372              
373             =head1 SUPPORT
374              
375             See the L in the main module.
376              
377             =head1 AUTHOR
378              
379             Adam Kennedy Eadamk@cpan.orgE
380              
381             =head1 COPYRIGHT
382              
383             Copyright 2001 - 2011 Adam Kennedy.
384              
385             This program is free software; you can redistribute
386             it and/or modify it under the same terms as Perl itself.
387              
388             The full text of the license can be found in the
389             LICENSE file included with this module.
390              
391             =cut