Skip to content

Commit

Permalink
[Finder] content(), notContent() methods
Browse files Browse the repository at this point in the history
  • Loading branch information
gajdaw committed Apr 19, 2012
1 parent 0cd6385 commit 082d86e
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 26 deletions.
53 changes: 53 additions & 0 deletions src/Symfony/Component/Finder/Finder.php
Expand Up @@ -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');

Expand Down Expand Up @@ -188,6 +190,53 @@ public function notName($pattern)
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.
*
Expand Down Expand Up @@ -551,6 +600,10 @@ private function searchInDirectory($dir)
$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);
}
Expand Down
@@ -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, '/'));
}

}
38 changes: 12 additions & 26 deletions src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php
Expand Up @@ -18,32 +18,8 @@
*
* @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.
Expand Down Expand Up @@ -81,7 +57,17 @@ public function accept()
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;
Expand Down
@@ -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);

}
42 changes: 42 additions & 0 deletions src/Symfony/Component/Finder/Tests/FinderTest.php
Expand Up @@ -330,4 +330,46 @@ protected function toAbsolute($files)

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')),
);
}

}
2 changes: 2 additions & 0 deletions src/Symfony/Component/Finder/Tests/Fixtures/dolor.txt
@@ -0,0 +1,2 @@
dolor sit amet
DOLOR SIT AMET
2 changes: 2 additions & 0 deletions src/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt
@@ -0,0 +1,2 @@
ipsum dolor sit amet
IPSUM DOLOR SIT AMET
2 changes: 2 additions & 0 deletions src/Symfony/Component/Finder/Tests/Fixtures/lorem.txt
@@ -0,0 +1,2 @@
lorem ipsum dolor sit amet
LOREM IPSUM DOLOR SIT AMET
@@ -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();
}
}

0 comments on commit 082d86e

Please sign in to comment.