diff --git a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php index 8958571bdff7..be8c003437bb 100644 --- a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php @@ -31,6 +31,8 @@ abstract class AbstractAdapter implements AdapterInterface protected $dates = array(); protected $filters = array(); protected $sort = false; + protected $paths = array(); + protected $notPaths = array(); /** * {@inheritdoc} @@ -171,4 +173,25 @@ public function setSort($sort) return $this; } + + /** + * {@inheritdoc} + */ + public function setPath(array $paths) + { + $this->paths = $paths; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotPath(array $notPaths) + { + $this->notPaths = $notPaths; + + return $this; + } + } diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php index 2dbae76a565f..3607165f9bd6 100644 --- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -73,6 +73,10 @@ public function searchInDirectory($dir) $iterator = $iteratorAggregate->getIterator(); } + if ($this->paths || $this->notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); + } + return $iterator; } diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index b6d15176ff68..b6b85d5239d9 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -52,6 +52,8 @@ class Finder implements \IteratorAggregate, \Countable private $contains = array(); private $notContains = array(); private $adapters = array(); + private $paths = array(); + private $notPaths = array(); private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); @@ -283,6 +285,52 @@ public function notContains($pattern) return $this; } + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + */ + public function path($pattern) + { + $this->paths[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + */ + public function notPath($pattern) + { + $this->notPaths[] = $pattern; + + return $this; + } + /** * Adds tests for file sizes. * @@ -682,6 +730,8 @@ private function buildAdapter(AdapterInterface $adapter) ->setSizes($this->sizes) ->setDates($this->dates) ->setFilters($this->filters) - ->setSort($this->sort); + ->setSort($this->sort) + ->setPath($this->paths) + ->setNotPath($this->notPaths); } } diff --git a/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php b/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php new file mode 100644 index 000000000000..c736f0b31038 --- /dev/null +++ b/src/Symfony/Component/Finder/Iterator/PathFilterIterator.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $filename = strtr($filename, '\\', '/'); + } + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return true; + } + } + } + + return $match; + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname. + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index e1b1779f9928..046cfed4afe0 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -646,4 +646,65 @@ private function buildTestData(array $tests) return $data; } + + /** + * @dataProvider getTestPathData + */ + public function testPath($matchPatterns, $noMatchPatterns, $expected) + { + $finder = new Finder(); + $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures') + ->path($matchPatterns) + ->notPath($noMatchPatterns); + + $this->assertIterator($this->toAbsoluteFixtures($expected), $finder); + } + + public function getTestPathData() + { + return array( + array('', '', array()), + + array('/^A\/B\/C/', '/C$/', + array('A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat') + ), + + array('/^A\/B/', 'foobar', + array( + 'A'.DIRECTORY_SEPARATOR.'B', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + ) + ), + + array('A/B/C', 'foobar', + array( + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy', + ) + ), + + array('A/B', 'foobar', + array( + //dirs + 'A'.DIRECTORY_SEPARATOR.'B', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + + //files + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat.copy', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy', + ) + ), + + + ); + } + } diff --git a/src/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat b/src/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat b/src/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Finder/Tests/Fixtures/A/a.dat b/src/Symfony/Component/Finder/Tests/Fixtures/A/a.dat new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy b/src/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy b/src/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy b/src/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php index 5f0625f9f5e6..5f2b39823941 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Finder\Tests\Iterator; use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Tests\Iterator\MockSplFileInfo; +use Symfony\Component\Finder\Tests\Iterator\MockFileListIterator; class FilecontentFilterIteratorTest extends IteratorTestCase { @@ -85,107 +87,3 @@ public function getTestFilterData() ); } } - -class MockSplFileInfo extends \SplFileInfo -{ - const TYPE_DIRECTORY = 1; - const TYPE_FILE = 2; - const TYPE_UNKNOWN = 3; - - private $contents = null; - private $mode = null; - private $type = null; - - public function __construct($param) - { - if (is_string($param)) { - parent::__construct($param); - } elseif (is_array($param)) { - - $defaults = array( - 'name' => 'file.txt', - 'contents' => null, - 'mode' => null, - 'type' => null - ); - $defaults = array_merge($defaults, $param); - parent::__construct($defaults['name']); - $this->setContents($defaults['contents']); - $this->setMode($defaults['mode']); - $this->setType($defaults['type']); - } else { - throw new \RuntimeException(sprintf('Incorrect parameter "%s"', $param)); - } - } - - public function isFile() - { - if ($this->type === null) { - return preg_match('/file/', $this->getFilename()); - }; - - return self::TYPE_FILE === $this->type; - } - - public function isDir() - { - if ($this->type === null) { - return preg_match('/directory/', $this->getFilename()); - } - - return self::TYPE_DIRECTORY === $this->type; - } - - public function isReadable() - { - if ($this->mode === null) { - return preg_match('/r\+/', $this->getFilename()); - } - - return preg_match('/r\+/', $this->mode); - } - - public function getContents() - { - return $this->contents; - } - - public function setContents($contents) - { - $this->contents = $contents; - } - - public function setMode($mode) - { - $this->mode = $mode; - } - - public function setType($type) - { - if (is_string($type)) { - switch ($type) { - case 'directory': - case 'd': - $this->type = self::TYPE_DIRECTORY; - break; - case 'file': - case 'f': - $this->type = self::TYPE_FILE; - break; - default: - $this->type = self::TYPE_UNKNOWN; - } - } else { - $this->type = $type; - } - } -} - -class MockFileListIterator extends \ArrayIterator -{ - public function __construct(array $filesArray = array()) - { - $files = array_map(function($file){ return new MockSplFileInfo($file); }, $filesArray); - parent::__construct($files); - } -} diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php b/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php new file mode 100644 index 000000000000..6d8ae39561b1 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php @@ -0,0 +1,21 @@ + + * + * 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; + +class MockFileListIterator extends \ArrayIterator +{ + public function __construct(array $filesArray = array()) + { + $files = array_map(function($file){ return new MockSplFileInfo($file); }, $filesArray); + parent::__construct($files); + } +} diff --git a/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php b/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php new file mode 100644 index 000000000000..50312860bd4b --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php @@ -0,0 +1,136 @@ + + * + * 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; + +class MockSplFileInfo extends \SplFileInfo +{ + const TYPE_DIRECTORY = 1; + const TYPE_FILE = 2; + const TYPE_UNKNOWN = 3; + + private $contents = null; + private $mode = null; + private $type = null; + private $relativePath = null; + private $relativePathname = null; + + public function __construct($param) + { + if (is_string($param)) { + parent::__construct($param); + } elseif (is_array($param)) { + + $defaults = array( + 'name' => 'file.txt', + 'contents' => null, + 'mode' => null, + 'type' => null, + 'relativePath' => null, + 'relativePathname' => null, + ); + $defaults = array_merge($defaults, $param); + parent::__construct($defaults['name']); + $this->setContents($defaults['contents']); + $this->setMode($defaults['mode']); + $this->setType($defaults['type']); + $this->setRelativePath($defaults['relativePath']); + $this->setRelativePathname($defaults['relativePathname']); + } else { + throw new \RuntimeException(sprintf('Incorrect parameter "%s"', $param)); + } + } + + public function isFile() + { + if ($this->type === null) { + return preg_match('/file/', $this->getFilename()); + }; + + return self::TYPE_FILE === $this->type; + } + + public function isDir() + { + if ($this->type === null) { + return preg_match('/directory/', $this->getFilename()); + } + + return self::TYPE_DIRECTORY === $this->type; + } + + public function isReadable() + { + if ($this->mode === null) { + return preg_match('/r\+/', $this->getFilename()); + } + + return preg_match('/r\+/', $this->mode); + } + + public function getContents() + { + return $this->contents; + } + + public function setContents($contents) + { + $this->contents = $contents; + } + + public function setMode($mode) + { + $this->mode = $mode; + } + + public function setType($type) + { + if (is_string($type)) { + switch ($type) { + case 'directory': + $this->type = self::TYPE_DIRECTORY; + case 'd': + $this->type = self::TYPE_DIRECTORY; + break; + case 'file': + $this->type = self::TYPE_FILE; + case 'f': + $this->type = self::TYPE_FILE; + break; + default: + $this->type = self::TYPE_UNKNOWN; + } + } else { + $this->type = $type; + } + } + + public function setRelativePath($relativePath) + { + $this->relativePath = $relativePath; + } + + public function setRelativePathname($relativePathname) + { + $this->relativePathname = $relativePathname; + } + + public function getRelativePath() + { + return $this->relativePath; + } + + public function getRelativePathname() + { + return $this->relativePathname; + } + +} diff --git a/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php new file mode 100644 index 000000000000..6a155d4d7cf0 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php @@ -0,0 +1,86 @@ + + * + * 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\PathFilterIterator; + +class PathFilterIteratorTest extends IteratorTestCase +{ + + /** + * @dataProvider getTestFilterData + */ + public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray) + { + $iterator = new PathFilterIterator($inner, $matchPatterns, $noMatchPatterns); + $this->assertIterator($resultArray, $iterator); + } + + public function getTestFilterData() + { + $inner = new MockFileListIterator(); + + //PATH: A/B/C/abc.dat + $inner[] = new MockSplFileInfo(array( + 'name' => 'abc.dat', + 'relativePathname' => 'A' . DIRECTORY_SEPARATOR . 'B' . DIRECTORY_SEPARATOR . 'C' . DIRECTORY_SEPARATOR . 'abc.dat', + )); + + //PATH: A/B/ab.dat + $inner[] = new MockSplFileInfo(array( + 'name' => 'ab.dat', + 'relativePathname' => 'A' . DIRECTORY_SEPARATOR . 'B' . DIRECTORY_SEPARATOR . 'ab.dat', + )); + + //PATH: A/a.dat + $inner[] = new MockSplFileInfo(array( + 'name' => 'a.dat', + 'relativePathname' => 'A' . DIRECTORY_SEPARATOR . 'a.dat', + )); + + //PATH: copy/A/B/C/abc.dat.copy + $inner[] = new MockSplFileInfo(array( + 'name' => 'abc.dat.copy', + 'relativePathname' => 'copy' . DIRECTORY_SEPARATOR . 'A' . DIRECTORY_SEPARATOR . 'B' . DIRECTORY_SEPARATOR . 'C' . DIRECTORY_SEPARATOR . 'abc.dat', + )); + + //PATH: copy/A/B/ab.dat.copy + $inner[] = new MockSplFileInfo(array( + 'name' => 'ab.dat.copy', + 'relativePathname' => 'copy' . DIRECTORY_SEPARATOR . 'A' . DIRECTORY_SEPARATOR . 'B' . DIRECTORY_SEPARATOR . 'ab.dat', + )); + + //PATH: copy/A/a.dat.copy + $inner[] = new MockSplFileInfo(array( + 'name' => 'a.dat.copy', + 'relativePathname' => 'copy' . DIRECTORY_SEPARATOR . 'A' . DIRECTORY_SEPARATOR . 'a.dat', + )); + + return array( + array($inner, array('/^A/'), array(), array('abc.dat', 'ab.dat', 'a.dat')), + array($inner, array('/^A\/B/'), array(), array('abc.dat', 'ab.dat')), + array($inner, array('/^A\/B\/C/'), array(), array('abc.dat')), + array($inner, array('/A\/B\/C/'), array(), array('abc.dat', 'abc.dat.copy')), + + array($inner, array('A'), array(), array('abc.dat', 'ab.dat', 'a.dat', 'abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')), + array($inner, array('A/B'), array(), array('abc.dat', 'ab.dat', 'abc.dat.copy', 'ab.dat.copy')), + array($inner, array('A/B/C'), array(), array('abc.dat', 'abc.dat.copy')), + + array($inner, array('copy/A'), array(), array('abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')), + array($inner, array('copy/A/B'), array(), array('abc.dat.copy', 'ab.dat.copy')), + array($inner, array('copy/A/B/C'), array(), array('abc.dat.copy')), + + ); + } + +} +