PPI::Find - Object version of the Element->find method


PPI documentation Contained in the PPI distribution.

Index


Code Index:

NAME

Top

PPI::Find - Object version of the Element->find method

SYNOPSIS

Top

  # Create the Find object
  my $Find = PPI::Find->new( \&wanted );

  # Return all matching Elements as a list
  my @found = $Find->in( $Document );

  # Can we find any matching Elements
  if ( $Find->any_matches($Document) ) {
  	print "Found at least one matching Element";
  }

  # Use the object as an iterator
  $Find->start($Document) or die "Failed to execute search";
  while ( my $token = $Find->match ) {
  	...
  }

DESCRIPTION

Top

PPI::Find is the primary PDOM searching class in the core PPI package.

History

It became quite obvious during the development of PPI that many of the modules that would be built on top of it were going to need large numbers of saved, storable or easily creatable search objects that could be reused a number of times.

Although the internal ->find method provides a basic ability to search, it is by no means thorough. PPI::Find attempts to resolve this problem.

Structure and Style

PPI::Find provides a similar API to the popular File::Find::Rule module for file searching, but without the ability to assemble queries.

The implementation of a separate PPI::Find::Rule sub-class that does provide this ability is left as an exercise for the reader.

The &wanted function

At the core of each PPI::Find object is a "wanted" function that is passed a number of arguments and returns a value which controls the flow of the search.

As the search executes, each Element will be passed to the wanted function in depth-first order.

It will be provided with two arguments. The current Element to test as $_[0], and the top-level Element of the search as $_[1].

The &wanted function is expected to return 1 (positive) if the Element matches the condition, 0 (false) if it does not, and undef (undefined) if the condition does not match, and the Find search should not descend to any of the current Element's children.

Errors should be reported from the &wanted function via die, which will be caught by the Find object and returned as an error.

METHODS

Top

new &wanted

The new constructor takes a single argument of the &wanted function, as described above and creates a new search.

Returns a new PPI::Find object, or undef if not passed a CODE reference.

clone

The clone method creates another instance of the same Find object.

The cloning is done safely, so if your existing Find object is in the middle of an iteration, the cloned Find object will not also be in the iteration and can be safely used independently.

Returns a duplicate PPI::Find object.

in $Document [, array_ref => 1 ]

The in method starts and completes a full run of the search.

It takes as argument a single PPI::Element object which will serve as the top of the search process.

Returns a list of PPI::Element objects that match the condition described by the &wanted function, or the null list on error.

You should check the ->errstr method for any errors if you are returned the null list, which may also mean simply that no Elements were found that matched the condition.

Because of this need to explicitly check for errors, an alternative return value mechanism is provide. If you pass the array_ref = 1> parameter to the method, it will return the list of matched Elements as a reference to an ARRAY. The method will return false if no elements were matched, or undef on error.

The ->errstr method can still be used to get the error message as normal.

start $Element

The start method lets the Find object act as an iterator. The method is passed the parent PPI::Element object as for the in method, but does not accept any parameters.

To simplify error handling, the entire search is done at once, with the results cached and provided as-requested.

Returns true if the search completes, and false on error.

match

The match method returns the next matching Element in the iteration.

Returns a PPI::Element object, or undef if there are no remaining Elements to be returned.

finish

The finish method provides a mechanism to end iteration if you wish to stop the iteration prematurely. It resets the Find object and allows it to be safely reused.

A Find object will be automatically finished when match returns false. This means you should only need to call finnish when you stop iterating early.

You may safely call this method even when not iterating and it will return without failure.

Always returns true

errstr

The errstr method returns the error messages when a given PPI::Find object fails any action.

Returns a string, or undef if there is no error.

TO DO

Top

- Implement the PPI::Find::Rule class

SUPPORT

Top

See the support section in the main module.

AUTHOR

Top

Adam Kennedy <adamk@cpan.org>

COPYRIGHT

Top


PPI documentation Contained in the PPI distribution.
package PPI::Find;

use strict;
use Params::Util qw{_INSTANCE};

use vars qw{$VERSION};
BEGIN {
	$VERSION = '1.215';
}





#####################################################################
# Constructor

sub new {
	my $class  = ref $_[0] ? ref shift : shift;
	my $wanted = ref $_[0] eq 'CODE' ? shift : return undef;

	# Create the object
	my $self = bless {
		wanted => $wanted,
	}, $class;

	$self;
}

sub clone {
	my $self = ref $_[0] ? shift
		: die "->clone can only be called as an object method";
	my $class = ref $self;

	# Create the object
	my $clone = bless {
		wanted => $self->{wanted},
	}, $class;

	$clone;
}





####################################################################
# Search Execution Methods

sub in {
	my $self    = shift;
	my $Element = shift;
	my %params  = @_;
	delete $self->{errstr};
 
	# Are we already acting as an iterator
	if ( $self->{in} ) {
		return $self->_error('->in called while another search is in progress', %params);
	}

	# Get the root element for the search
	unless ( _INSTANCE($Element, 'PPI::Element') ) {
		return $self->_error('->in was not passed a PPI::Element object', %params);
	}

	# Prepare the search
	$self->{in}      = $Element;
	$self->{matches} = [];

	# Execute the search
	eval {
		$self->_execute;
	};
	if ( $@ ) {
		my $errstr = $@;
		$errstr =~ s/\s+at\s+line\s+.+$//;
		return $self->_error("Error while searching: $errstr", %params);
	}

	# Clean up and return
	delete $self->{in};
	if ( $params{array_ref} ) {
		if ( @{$self->{matches}} ) {
			return delete $self->{matches};
		}
		delete $self->{matches};
		return '';
	}

	# Return as a list
	my $matches = delete $self->{matches};
	@$matches;
}

sub start {
	my $self    = shift;
	my $Element = shift;
	delete $self->{errstr};

	# Are we already acting as an iterator
	if ( $self->{in} ) {
		return $self->_error('->in called while another search is in progress');
	}

	# Get the root element for the search
	unless ( _INSTANCE($Element, 'PPI::Element') ) {
		return $self->_error('->in was not passed a PPI::Element object');
	}

	# Prepare the search
	$self->{in}      = $Element;
	$self->{matches} = [];

	# Execute the search
	eval {
		$self->_execute;
	};
	if ( $@ ) {
		my $errstr = $@;
		$errstr =~ s/\s+at\s+line\s+.+$//;
		$self->_error("Error while searching: $errstr");
		return undef;
	}

	1;
}

sub match {
	my $self = shift;
	return undef unless $self->{matches};

	# Fetch and return the next match
	my $match = shift @{$self->{matches}};
	return $match if $match;

	$self->finish;
	undef;
}

sub finish {
	my $self = shift;
	delete $self->{in};
	delete $self->{matches};
	delete $self->{errstr};
	1;
}





#####################################################################
# Support Methods and Error Handling

sub _execute {
	my $self   = shift;
	my $wanted = $self->{wanted};
	my @queue  = ( $self->{in} );

	# Pull entries off the queue and hand them off to the wanted function
	while ( my $Element = shift @queue ) {
		my $rv = &$wanted( $Element, $self->{in} );

		# Add to the matches if returns true
		push @{$self->{matches}}, $Element if $rv;

		# Continue and don't descend if it returned undef
		# or if it doesn't have children
		next unless defined $rv;
		next unless $Element->isa('PPI::Node');

		# Add the children to the head of the queue
		if ( $Element->isa('PPI::Structure') ) {
			unshift @queue, $Element->finish if $Element->finish;
			unshift @queue, $Element->children;
			unshift @queue, $Element->start if $Element->start;
		} else {
			unshift @queue, $Element->children;
		}
	}

	1;
}

sub errstr {
	shift->{errstr};
}

sub _error {
	my $self = shift;
	$self->{errstr} = shift;
	my %params = @_;
	$params{array_ref} ? undef : ();
}

1;