[Finder] content(), notContent() methods

This commit is contained in:
Włodzimierz Gajda 2012-04-19 17:34:50 +02:00
parent 0cd63852aa
commit 082d86e3a0
9 changed files with 294 additions and 26 deletions

View File

@ -44,6 +44,8 @@ class Finder implements \IteratorAggregate
private $dirs = array();
private $dates = array();
private $iterators = array();
private $contents = array();
private $notContents = array();
static private $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
@ -188,6 +190,53 @@ class Finder implements \IteratorAggregate
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->content('Lorem ipsum')
* $finder->content('/Lorem ipsum/i')
*
* @param string $pattern A pattern (string or regexp)
*
* @return Finder The current Finder instance
*
* @see Symfony\Component\Finder\Iterator\FilecontentFilterIterator
*
* @api
*/
public function content($pattern)
{
$this->contents[] = $pattern;
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContent('Lorem ipsum')
* $finder->notContent('/Lorem ipsum/i')
*
* @param string $pattern A pattern (string or regexp)
*
* @return Finder The current Finder instance
*
* @see Symfony\Component\Finder\Iterator\FilecontentFilterIterator
*
* @api
*/
public function notContent($pattern)
{
$this->notContents[] = $pattern;
return $this;
}
/**
* Adds tests for file sizes.
*
@ -551,6 +600,10 @@ class Finder implements \IteratorAggregate
$iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
}
if ($this->contents || $this->notContents) {
$iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contents, $this->notContents);
}
if ($this->sizes) {
$iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
}

View File

@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return Boolean true if the value should be kept, false otherwise
*/
public function accept()
{
// should at least match one rule
if ($this->matchRegexps) {
$match = false;
foreach ($this->matchRegexps as $regex) {
$content = file_get_contents($this->getRealpath());
if (false === $content) {
throw new \RuntimeException(sprintf('Error reading file "%s".', $this->getRealpath()));
}
if (preg_match($regex, $content)) {
$match = true;
break;
}
}
} else {
$match = true;
}
// should at least not match one rule to exclude
if ($this->noMatchRegexps) {
$exclude = false;
foreach ($this->noMatchRegexps as $regex) {
$content = file_get_contents($this->getRealpath());
if (false === $content) {
throw new \RuntimeException(sprintf('Error reading file "%s".', $this->getRealpath()));
}
if (preg_match($regex, $content)) {
$exclude = true;
break;
}
}
} else {
$exclude = false;
}
return $match && !$exclude;
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex($str)
{
if (preg_match('/^([^a-zA-Z0-9\\\\]).+?\\1[ims]?$/', $str)) {
return $str;
}
return sprintf('/%s/', preg_quote($str, '/'));
}
}

View File

@ -18,32 +18,8 @@ use Symfony\Component\Finder\Glob;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FilenameFilterIterator extends \FilterIterator
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
private $matchRegexps;
private $noMatchRegexps;
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param array $matchPatterns An array of patterns that need to match
* @param array $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
$this->matchRegexps = array();
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
$this->noMatchRegexps = array();
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
@ -81,7 +57,17 @@ class FilenameFilterIterator extends \FilterIterator
return $match && !$exclude;
}
private function toRegex($str)
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*
* @return string regexp corresponding to a given glob or regexp
*/
protected function toRegex($str)
{
if (preg_match('/^([^a-zA-Z0-9\\\\]).+?\\1[ims]?$/', $str)) {
return $str;

View File

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
protected $matchRegexps;
protected $noMatchRegexps;
/**
* Constructor.
*
* @param \Iterator $iterator The Iterator to filter
* @param array $matchPatterns An array of patterns that need to match
* @param array $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
$this->matchRegexps = array();
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
$this->noMatchRegexps = array();
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Converts string into regexp.
*
* @param string $str Pattern
*
* @return string regexp corresponding to a given string
*/
abstract protected function toRegex($str);
}

View File

@ -330,4 +330,46 @@ class FinderTest extends Iterator\RealIteratorTestCase
return $f;
}
protected function toAbsoluteFixtures($files)
{
$f = array();
foreach ($files as $file) {
$f[] = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.$file;
}
return $f;
}
/**
* @dataProvider getContentTestData
*/
public function testContent($matchPatterns, $noMatchPatterns, $expected)
{
$finder = new Finder();
$finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures')
->name('*.txt')->sortByName()
->content($matchPatterns)
->notContent($noMatchPatterns);
$this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
}
public function getContentTestData()
{
return array(
array('', '', array()),
array('foo', 'bar', array()),
array('', 'foobar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')),
array('lorem ipsum dolor sit amet', 'foobar', array('lorem.txt')),
array('sit', 'bar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')),
array('dolor sit amet', '@^L@m', array('dolor.txt', 'ipsum.txt')),
array('/^lorem ipsum dolor sit amet$/m', 'foobar', array('lorem.txt')),
array('lorem', 'foobar', array('lorem.txt')),
array('', 'lorem', array('dolor.txt', 'ipsum.txt')),
array('ipsum dolor sit amet', '/^IPSUM/m', array('lorem.txt')),
);
}
}

View File

@ -0,0 +1,2 @@
dolor sit amet
DOLOR SIT AMET

View File

@ -0,0 +1,2 @@
ipsum dolor sit amet
IPSUM DOLOR SIT AMET

View File

@ -0,0 +1,2 @@
lorem ipsum dolor sit amet
LOREM IPSUM DOLOR SIT AMET

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Iterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
class FilecontentFilterIteratorTest extends IteratorTestCase
{
public function testAccept()
{
$inner = new ContentInnerNameIterator(array('test.txt'));
$iterator = new FilecontentFilterIterator($inner, array(), array());
$this->assertIterator(array('test.txt'), $iterator);
}
}
class ContentInnerNameIterator extends \ArrayIterator
{
public function current()
{
return new \SplFileInfo(parent::current());
}
public function getFilename()
{
return parent::current();
}
}