From 331e3ad44923ef35092832e51e8d1050259e6987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sat, 21 Apr 2012 21:31:44 +0200 Subject: [PATCH 01/37] [Finder] Added adapter interface. --- .../Finder/Adapter/AdapterInterface.php | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/Symfony/Component/Finder/Adapter/AdapterInterface.php diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php new file mode 100644 index 0000000000000..47e64840a0567 --- /dev/null +++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +/** + * @author Jean-François Simon + */ +interface AdapterInterface +{ + /** + * @param bool $followLinks + * + * @return AdapterInterface Current instance + */ + function setFollowLinks($followLinks); + + /** + * @param int $mode + * + * @return AdapterInterface Current instance + */ + function setMode($mode); + + /** + * @param array $exclude + * + * @return AdapterInterface Current instance + */ + function setExclude(array $exclude); + + /** + * @param array $depths + * + * @return AdapterInterface Current instance + */ + function setDepths(array $depths); + + /** + * @param array $names + * + * @return AdapterInterface Current instance + */ + function setNames(array $names); + + /** + * @param array $notNames + * + * @return AdapterInterface Current instance + */ + function setNotNames(array $notNames); + + /** + * @param array $contains + * + * @return AdapterInterface Current instance + */ + function setContains(array $contains); + + /** + * @param array $notContains + * + * @return AdapterInterface Current instance + */ + function setNotContains(array $notContains); + + /** + * @param array $sizes + * + * @return AdapterInterface Current instance + */ + function setSizes(array $sizes); + + /** + * @param array $dates + * + * @return AdapterInterface Current instance + */ + function setDates(array $dates); + + /** + * @param array $filters + * + * @return AdapterInterface Current instance + */ + function setFilters(array $filters); + + /** + * @param \Closure|int $sort + * + * @return AdapterInterface Current instance + */ + function setSort($sort); + + /** + * @param string $dir + * + * @return \Iterator Result iterator + */ + function searchInDirectory($dir); +} From a05f449def3a5c9bdda0d1f2695910be7662624a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sat, 21 Apr 2012 21:32:06 +0200 Subject: [PATCH 02/37] [Finder] Added abstract adapter. --- .../Finder/Adapter/AbstractAdapter.php | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/Symfony/Component/Finder/Adapter/AbstractAdapter.php diff --git a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php new file mode 100644 index 0000000000000..3d3f4deda9fd5 --- /dev/null +++ b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +/** + * Interface for finder engine implementations. + * + * @author Jean-François Simon + */ +abstract class AbstractAdapter implements AdapterInterface +{ + protected $followLinks = false; + protected $mode = 0; + protected $depths = array(); + protected $exclude = array(); + protected $names = array(); + protected $notNames = array(); + protected $contains = array(); + protected $notContains = array(); + protected $sizes = array(); + protected $dates = array(); + protected $filters = array(); + protected $sort = array(); + + /** + * {@inheritdoc} + */ + public function setFollowLinks($followLinks) + { + $this->followLinks = $followLinks; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setMode($mode) + { + $this->mode = $mode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDepths(array $depths) + { + $this->depths = $depths; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setExclude(array $exclude) + { + $this->exclude = $exclude; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNames(array $names) + { + $this->names = $names; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotNames(array $notNames) + { + $this->notNames = $notNames; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setContains(array $contains) + { + $this->contains = $contains; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotContains(array $notContains) + { + $this->notContains = $notContains; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSizes(array $sizes) + { + $this->sizes = $sizes; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDates(array $dates) + { + $this->dates = $dates; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setFilters(array $filters) + { + $this->filters = $filters; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSort($sort) + { + $this->sort = $sort; + + return $this; + } +} From d340849953bbf6853ab45f6a1fa947fd2bca01df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sat, 21 Apr 2012 21:33:56 +0200 Subject: [PATCH 03/37] [Finder] Moved current implementation to php adapter. --- .../Component/Finder/Adapter/PhpAdapter.php | 78 +++++++++++++++++++ src/Symfony/Component/Finder/Finder.php | 74 ++++++------------ 2 files changed, 103 insertions(+), 49 deletions(-) create mode 100644 src/Symfony/Component/Finder/Adapter/PhpAdapter.php diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php new file mode 100644 index 0000000000000..ca4685ba72c5d --- /dev/null +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Iterator; + +/** + * PHP finder engine implementation. + * + * @author Jean-François Simon + */ +class PhpAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new \RecursiveIteratorIterator( + new Iterator\RecursiveDirectoryIterator($dir, $flags), + \RecursiveIteratorIterator::SELF_FIRST + ); + + if ($this->depths) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->depths); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } +} diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 489a6f8b9a988..d23e57dfeef05 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Finder; +use Symfony\Component\Finder\Adapter\PhpAdapter; + /** * Finder allows to build rules to find files and directories. * @@ -569,27 +571,16 @@ public function count() return iterator_count($this->getIterator()); } - private function searchInDirectory($dir) + /* + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + */ + private function getAdapter() { - $flags = \RecursiveDirectoryIterator::SKIP_DOTS; - - if ($this->followLinks) { - $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; - } - - $iterator = new \RecursiveIteratorIterator( - new Iterator\RecursiveDirectoryIterator($dir, $flags), - \RecursiveIteratorIterator::SELF_FIRST - ); - - if ($this->depths) { - $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->depths); - } - - if ($this->mode) { - $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); - } + return new PhpAdapter(); + } + private function searchInDirectory($dir) + { if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $this->exclude = array_merge($this->exclude, self::$vcsPatterns); } @@ -598,35 +589,20 @@ private function searchInDirectory($dir) $this->notNames[] = '/^\..+/'; } - if ($this->exclude) { - $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); - } - - if ($this->names || $this->notNames) { - $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); - } - - if ($this->contains || $this->notContains) { - $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); - } - - if ($this->sizes) { - $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); - } - - if ($this->dates) { - $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); - } - - if ($this->filters) { - $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); - } - - if ($this->sort) { - $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); - $iterator = $iteratorAggregate->getIterator(); - } - - return $iterator; + return $this + ->getAdapter() + ->setFollowLinks($this->followLinks) + ->setDepths($this->depths) + ->setMode($this->mode) + ->setExclude($this->exclude) + ->setNames($this->names) + ->setNotNames($this->notNames) + ->setContains($this->contains) + ->setNotContains($this->notContains) + ->setSizes($this->sizes) + ->setDates($this->dates) + ->setFilters($this->filters) + ->setSort($this->sort) + ->searchInDirectory($dir); } } From c3cdda32625d3c3e6e96a3ea744b1ea0863ba9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sat, 21 Apr 2012 22:34:34 +0200 Subject: [PATCH 04/37] [Finder] Moved min/max depth calculation to abstract adapter. --- .../Finder/Adapter/AbstractAdapter.php | 25 ++++++++++++++++-- .../Component/Finder/Adapter/PhpAdapter.php | 4 +-- .../Iterator/DepthRangeFilterIterator.php | 26 +++---------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php index 3d3f4deda9fd5..ca416c7ee81be 100644 --- a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php @@ -20,7 +20,8 @@ abstract class AbstractAdapter implements AdapterInterface { protected $followLinks = false; protected $mode = 0; - protected $depths = array(); + protected $minDepth = 0; + protected $maxDepth = INF; protected $exclude = array(); protected $names = array(); protected $notNames = array(); @@ -56,7 +57,27 @@ public function setMode($mode) */ public function setDepths(array $depths) { - $this->depths = $depths; + $this->minDepth = 0; + $this->maxDepth = INF; + + foreach ($depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $this->minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $this->minDepth = $comparator->getTarget(); + break; + case '<': + $this->maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $this->maxDepth = $comparator->getTarget(); + break; + default: + $this->minDepth = $this->maxDepth = $comparator->getTarget(); + } + } return $this; } diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php index ca4685ba72c5d..a93629728dd76 100644 --- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -36,8 +36,8 @@ public function searchInDirectory($dir) \RecursiveIteratorIterator::SELF_FIRST ); - if ($this->depths) { - $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->depths); + if ($this->minDepth > 0 || $this->maxDepth < INF) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->maxDepth); } if ($this->mode) { diff --git a/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php b/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php index 832125393f1ae..77a9f45f2dfa5 100644 --- a/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php @@ -24,31 +24,11 @@ class DepthRangeFilterIterator extends FilterIterator * Constructor. * * @param \RecursiveIteratorIterator $iterator The Iterator to filter - * @param array $comparators An array of \NumberComparator instances + * @param int $minDepth The min depth + * @param int $maxDepth The max depth */ - public function __construct(\RecursiveIteratorIterator $iterator, array $comparators) + public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = INF) { - $minDepth = 0; - $maxDepth = INF; - foreach ($comparators as $comparator) { - switch ($comparator->getOperator()) { - case '>': - $minDepth = $comparator->getTarget() + 1; - break; - case '>=': - $minDepth = $comparator->getTarget(); - break; - case '<': - $maxDepth = $comparator->getTarget() - 1; - break; - case '<=': - $maxDepth = $comparator->getTarget(); - break; - default: - $minDepth = $maxDepth = $comparator->getTarget(); - } - } - $this->minDepth = $minDepth; $iterator->setMaxDepth(INF === $maxDepth ? -1 : $maxDepth); From 411cb8bc817e3ddf8f1dc6a2070c01fc07e2d0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sat, 21 Apr 2012 22:34:53 +0200 Subject: [PATCH 05/37] [Finder] Fixed depth iterator tests. --- .../Tests/Iterator/DepthRangeIteratorTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeIteratorTest.php index 43ad4599496c5..68c81e1168a24 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeIteratorTest.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/DepthRangeIteratorTest.php @@ -19,11 +19,11 @@ class DepthRangeFilterIteratorTest extends RealIteratorTestCase /** * @dataProvider getAcceptData */ - public function testAccept($size, $expected) + public function testAccept($minDepth, $maxDepth, $expected) { $inner = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->getAbsolutePath(''), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST); - $iterator = new DepthRangeFilterIterator($inner, $size); + $iterator = new DepthRangeFilterIterator($inner, $minDepth, $maxDepth); $actual = array_keys(iterator_to_array($iterator)); sort($expected); @@ -34,11 +34,11 @@ public function testAccept($size, $expected) public function getAcceptData() { return array( - array(array(new NumberComparator('< 1')), array($this->getAbsolutePath('/.git'), $this->getAbsolutePath('/test.py'), $this->getAbsolutePath('/foo'), $this->getAbsolutePath('/test.php'), $this->getAbsolutePath('/toto'), $this->getAbsolutePath('/.foo'), $this->getAbsolutePath('/.bar'))), - array(array(new NumberComparator('<= 1')), array($this->getAbsolutePath('/.git'), $this->getAbsolutePath('/test.py'), $this->getAbsolutePath('/foo'), $this->getAbsolutePath('/foo/bar.tmp'), $this->getAbsolutePath('/test.php'), $this->getAbsolutePath('/toto'), $this->getAbsolutePath('/.foo'), $this->getAbsolutePath('/.foo/.bar'), $this->getAbsolutePath('/.bar'))), - array(array(new NumberComparator('> 1')), array()), - array(array(new NumberComparator('>= 1')), array($this->getAbsolutePath('/foo/bar.tmp'), $this->getAbsolutePath('/.foo/.bar'))), - array(array(new NumberComparator('1')), array($this->getAbsolutePath('/foo/bar.tmp'), $this->getAbsolutePath('/.foo/.bar'))), + array(0, 0, array($this->getAbsolutePath('/.git'), $this->getAbsolutePath('/test.py'), $this->getAbsolutePath('/foo'), $this->getAbsolutePath('/test.php'), $this->getAbsolutePath('/toto'), $this->getAbsolutePath('/.foo'), $this->getAbsolutePath('/.bar'))), + array(0, 1, array($this->getAbsolutePath('/.git'), $this->getAbsolutePath('/test.py'), $this->getAbsolutePath('/foo'), $this->getAbsolutePath('/foo/bar.tmp'), $this->getAbsolutePath('/test.php'), $this->getAbsolutePath('/toto'), $this->getAbsolutePath('/.foo'), $this->getAbsolutePath('/.foo/.bar'), $this->getAbsolutePath('/.bar'))), + array(2, INF, array()), + array(1, INF, array($this->getAbsolutePath('/foo/bar.tmp'), $this->getAbsolutePath('/.foo/.bar'))), + array(1, 1, array($this->getAbsolutePath('/foo/bar.tmp'), $this->getAbsolutePath('/.foo/.bar'))), ); } From c47c5de827064e737d93f2b140d994f9ca702c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 08:51:33 +0200 Subject: [PATCH 06/37] [Finder] Moved glob/regex methods to new Expr class. --- src/Symfony/Component/Finder/Expr.php | 210 ++++++++++++++++++ .../Iterator/FilenameFilterIterator.php | 6 +- .../Iterator/MultiplePcreFilterIterator.php | 17 +- .../Tests/{GlobTest.php => ExprTest.php} | 8 +- .../Component/Finder/Tests/FinderTest.php | 2 +- 5 files changed, 221 insertions(+), 22 deletions(-) create mode 100644 src/Symfony/Component/Finder/Expr.php rename src/Symfony/Component/Finder/Tests/{GlobTest.php => ExprTest.php} (79%) diff --git a/src/Symfony/Component/Finder/Expr.php b/src/Symfony/Component/Finder/Expr.php new file mode 100644 index 0000000000000..327d3ab10e7fc --- /dev/null +++ b/src/Symfony/Component/Finder/Expr.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * @author Jean-François Simon + */ +class Expr +{ + const TYPE_REGEX = 1; + const TYPE_GLOB = 2; + + /** + * @var string + */ + private $expr; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $flags; + + /** + * @var string + */ + private $body; + + /** + * @param string $expr + */ + public function __construct($expr) + { + $this->expr = $expr; + + if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $this->expr, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if (($start === $end && !preg_match('/[[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { + $this->type = self::TYPE_REGEX; + $this->body = $m[1]; + $this->flags = $m[2]; + + return; + } + } + + $this->flags = ''; + $this->body = $expr; + $this->type = self::TYPE_GLOB; + } + + /** + * @param string $expr + * + * @return Expr + */ + static public function create($expr) + { + return new self($expr); + } + + /** + * @return bool + */ + public function isCaseSensitive() + { + return self::TYPE_GLOB === $this->type + || false === strpos($this->flags, 'i'); + } + + /** + * @return bool + */ + public function isRegex() + { + return self::TYPE_REGEX === $this->type; + } + + /** + * @return bool + */ + public function isGlob() + { + return self::TYPE_GLOB === $this->type; + } + + /** + * @return mixed + */ + public function getBody() + { + return $this->body; + } + + /** + * @return mixed + */ + public function getExpr() + { + return $this->expr; + } + + /** + * @return string + */ + public function getFlags() + { + return $this->flags; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * + * @return string + */ + public function getRegex($strictLeadingDot = true, $strictWildcardSlash = true) + { + return self::TYPE_REGEX === $this->type + ? $this->expr + : $this->globToRegex($this->expr, $strictLeadingDot, $strictWildcardSlash); + } + + /** + * @param string $glob + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * + * @return string + */ + private function globToRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true) + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = strlen($glob); + for ($i = 0; $i < $sizeGlob; $i++) { + $car = $glob[$i]; + if ($firstByte) { + if ($strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = false; + } + + if ('/' === $car) { + $firstByte = true; + } + + if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return '#^'.$regex.'$#'; + } +} diff --git a/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php b/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php index 109b5f00294bc..b858446f7cd43 100644 --- a/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\Glob; +use Symfony\Component\Finder\Expr; /** * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). @@ -55,7 +55,7 @@ public function accept() * Converts glob to regexp. * * PCRE patterns are left unchanged. - * Glob strings are transformed with Glob::toRegex(). + * Glob strings are transformed with Expr::globToRegex(). * * @param string $str Pattern: glob or regexp * @@ -63,6 +63,6 @@ public function accept() */ protected function toRegex($str) { - return $this->isRegex($str) ? $str : Glob::toRegex($str); + return Expr::create($str)->getRegex(); } } diff --git a/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php b/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php index 12584b186e00b..8b008485cb873 100644 --- a/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\Expr; + /** * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). * @@ -52,20 +54,7 @@ public function __construct(\Iterator $iterator, array $matchPatterns, array $no */ protected function isRegex($str) { - if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { - $start = substr($m[1], 0, 1); - $end = substr($m[1], -1); - - if ($start === $end) { - return !preg_match('/[*?[:alnum:] \\\\]/', $start); - } - - if ($start === '{' && $end === '}') { - return true; - } - } - - return false; + return Expr::create($str)->isRegex(); } /** diff --git a/src/Symfony/Component/Finder/Tests/GlobTest.php b/src/Symfony/Component/Finder/Tests/ExprTest.php similarity index 79% rename from src/Symfony/Component/Finder/Tests/GlobTest.php rename to src/Symfony/Component/Finder/Tests/ExprTest.php index 56077a10687ce..ccd0fd8c355f5 100644 --- a/src/Symfony/Component/Finder/Tests/GlobTest.php +++ b/src/Symfony/Component/Finder/Tests/ExprTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Finder\Tests; -use Symfony\Component\Finder\Glob; +use Symfony\Component\Finder\Expr; -class GlobTest extends \PHPUnit_Framework_TestCase +class ExprTest extends \PHPUnit_Framework_TestCase { /** * @dataProvider getToRegexData @@ -21,11 +21,11 @@ class GlobTest extends \PHPUnit_Framework_TestCase public function testToRegex($glob, $match, $noMatch) { foreach ($match as $m) { - $this->assertRegExp(Glob::toRegex($glob), $m, '::toRegex() converts a glob to a regexp'); + $this->assertRegExp(Expr::create($glob)->getRegex(), $m, '::globToRegex() converts a glob to a regexp'); } foreach ($noMatch as $m) { - $this->assertNotRegExp(Glob::toRegex($glob), $m, '::toRegex() converts a glob to a regexp'); + $this->assertNotRegExp(Expr::create($glob)->getRegex(), $m, '::globToRegex() converts a glob to a regexp'); } } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index ec7db1ec06840..7005b7ea74aed 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -231,7 +231,7 @@ public function testIn() $finder = new Finder(); $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator(); - $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'bootstrap.php', __DIR__.DIRECTORY_SEPARATOR.'GlobTest.php'), $iterator); + $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'bootstrap.php', __DIR__.DIRECTORY_SEPARATOR.'ExprTest.php'), $iterator); } public function testGetIterator() From 432a3fe7e59ce88b69ba39e9bd5ad8305382d2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 09:03:36 +0200 Subject: [PATCH 07/37] [Finder] Added Expr class tests. --- src/Symfony/Component/Finder/Expr.php | 2 +- .../Component/Finder/Tests/ExprTest.php | 57 ++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Finder/Expr.php b/src/Symfony/Component/Finder/Expr.php index 327d3ab10e7fc..6fd8af808758e 100644 --- a/src/Symfony/Component/Finder/Expr.php +++ b/src/Symfony/Component/Finder/Expr.php @@ -52,7 +52,7 @@ public function __construct($expr) if (($start === $end && !preg_match('/[[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { $this->type = self::TYPE_REGEX; - $this->body = $m[1]; + $this->body = substr($m[1], 1, -1); $this->flags = $m[2]; return; diff --git a/src/Symfony/Component/Finder/Tests/ExprTest.php b/src/Symfony/Component/Finder/Tests/ExprTest.php index ccd0fd8c355f5..bc72d81ebab0c 100644 --- a/src/Symfony/Component/Finder/Tests/ExprTest.php +++ b/src/Symfony/Component/Finder/Tests/ExprTest.php @@ -16,9 +16,9 @@ class ExprTest extends \PHPUnit_Framework_TestCase { /** - * @dataProvider getToRegexData + * @dataProvider getGlobToRegexData */ - public function testToRegex($glob, $match, $noMatch) + public function testGlobToRegex($glob, $match, $noMatch) { foreach ($match as $m) { $this->assertRegExp(Expr::create($glob)->getRegex(), $m, '::globToRegex() converts a glob to a regexp'); @@ -29,7 +29,31 @@ public function testToRegex($glob, $match, $noMatch) } } - public function getToRegexData() + /** + * @dataProvider getTypeGuesserData + */ + public function testTypeGuesser($expr, $type) + { + $this->assertEquals($type, Expr::create($expr)->getType()); + } + + /** + * @dataProvider getCaseSensitiveData + */ + public function testCaseSensitive($expr, $isCaseSensitive) + { + $this->assertEquals($isCaseSensitive, Expr::create($expr)->isCaseSensitive()); + } + + /** + * @dataProvider getRegexBodyData + */ + public function testRegexBody($expr, $body) + { + $this->assertEquals($body, Expr::create($expr)->getBody()); + } + + public function getGlobToRegexData() { return array( array('', array(''), array('f', '/')), @@ -44,4 +68,31 @@ public function getToRegexData() array('/foo', array('/foo'), array('foo')), ); } + + public function getTypeGuesserData() + { + return array( + array('{foo}', Expr::TYPE_REGEX), + array('/foo/', Expr::TYPE_REGEX), + array('foo', Expr::TYPE_GLOB), + array('foo*', Expr::TYPE_GLOB), + ); + } + + public function getCaseSensitiveData() + { + return array( + array('{foo}m', true), + array('/foo/i', false), + array('foo*', true), + ); + } + + public function getRegexBodyData() + { + return array( + array('{foo}m', 'foo'), + array('/foo/i', 'foo'), + ); + } } From 524ebe9e51f0b882515dbf4bb7f97197d95d693f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 10:29:17 +0200 Subject: [PATCH 08/37] [Finder] Added file paths iterator and its tests. --- .../Finder/Iterator/FilePathsIterator.php | 123 ++++++++++++++++++ .../Tests/Iterator/FilePathsIteratorTest.php | 66 ++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/Symfony/Component/Finder/Iterator/FilePathsIterator.php create mode 100644 src/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php diff --git a/src/Symfony/Component/Finder/Iterator/FilePathsIterator.php b/src/Symfony/Component/Finder/Iterator/FilePathsIterator.php new file mode 100644 index 0000000000000..5153ed301aeac --- /dev/null +++ b/src/Symfony/Component/Finder/Iterator/FilePathsIterator.php @@ -0,0 +1,123 @@ + + * + * 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\SplFileInfo; + +/** + * Iterate over shell command result. + * + * @author Jean-François Simon + */ +class FilePathsIterator extends \ArrayIterator +{ + /** + * @var string + */ + private $baseDir; + + /** + * @var int + */ + private $baseDirLength; + + /** + * @var string + */ + private $subPath; + + /** + * @var string + */ + private $subPathname; + + /** + * @param array $paths List of paths returned by shell command + * @param string $baseDir Base dir for relative path building + */ + public function __construct(array $paths, $baseDir) + { + $this->baseDir = $baseDir; + $this->baseDirLength = strlen($baseDir); + + parent::__construct($paths); + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, array $arguments) + { + return call_user_func_array(array($this->current(), $name), $arguments); + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + public function current() + { + return new SplFileInfo(parent::current(), $this->subPath, $this->subPathname); + } + + public function next() + { + parent::next(); + + $this->buildSubPath(); + } + + public function rewind() + { + parent::rewind(); + + $this->buildSubPath(); + } + + /** + * @return string + */ + public function getSubPath() + { + return $this->subPath; + } + + /** + * @return string + */ + public function getSubPathname() + { + return $this->subPathname; + } + + /** + * @param string $absolutePath + * + * @return null|string + */ + private function buildSubPath() + { + $absolutePath = parent::current(); + + if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) { + $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\'); + $dir = dirname($this->subPathname); + $this->subPath = '.' === $dir ? '' : $dir; + } else { + $this->subPath = $this->subPathname = ''; + } + } +} diff --git a/src/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php new file mode 100644 index 0000000000000..c60a90656a550 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php @@ -0,0 +1,66 @@ + + * + * 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\FilePathsIterator; + +class FilePathsIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getSubPathData + */ + public function testSubPath($baseDir, array $paths, array $subPaths, array $subPathnames) + { + $iterator = new FilePathsIterator($paths, $baseDir); + + foreach ($iterator as $index => $file) { + $this->assertEquals($paths[$index], $file->getPathname()); + $this->assertEquals($subPaths[$index], $iterator->getSubPath()); + $this->assertEquals($subPathnames[$index], $iterator->getSubPathname()); + } + } + + public function getSubPathData() + { + $tmpDir = sys_get_temp_dir().'/symfony2_finder'; + + return array( + array( + $tmpDir, + array( // paths + $tmpDir.DIRECTORY_SEPARATOR.'.git', + $tmpDir.DIRECTORY_SEPARATOR.'test.py', + $tmpDir.DIRECTORY_SEPARATOR.'foo', + $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp', + $tmpDir.DIRECTORY_SEPARATOR.'test.php', + $tmpDir.DIRECTORY_SEPARATOR.'toto' + ), + array( // subPaths + '', + '', + '', + 'foo', + '', + '' + ), + array( // subPathnames + '.git', + 'test.py', + 'foo', + 'foo'.DIRECTORY_SEPARATOR.'bar.tmp', + 'test.php', + 'toto' + ), + ), + ); + } +} From f6acae06364d7764db20730aed47ac75cd1e9895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 11:11:48 +0200 Subject: [PATCH 09/37] [Finder] Added minimalist gnu find adapter. --- .../Finder/Adapter/GnuFindAdapter.php | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php new file mode 100644 index 0000000000000..8c947be2cd130 --- /dev/null +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\ShellTester; + +/** + * PHP finder engine implementation. + * + * @author Jean-François Simon + */ +class GnuFindAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + // -noleaf option is required for filesystems + // who doesn't follow '.' and '..' convention + // like MSDOS, CDROM or AFS mount points + $command = 'find '.$dir.' -noleaf'; + + if ($this->followLinks) { + $command.= ' -follow'; + } + + $command.= ' -mindepth '.($this->minDepth+1); + + // warning! INF < INF => true ; INF == INF => false ; INF === INF => true + // https://bugs.php.net/bug.php?id=9118 + if ($this->maxDepth !== INF) { + $command.= ' -maxdepth '.($this->maxDepth+1); + } + + if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { + $command.= ' -type d'; + } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { + $command.= ' -type f'; + } + + exec($command, $paths, $code); + + if ($code !== 0) { + throw new \RuntimeException(); + } + + $iterator = new Iterator\FilePathsIterator($paths, $dir); + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } +} From de2ae38f09ba5551582b37481f935044f2793343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 11:12:39 +0200 Subject: [PATCH 10/37] [Finder] Added adapter validity control. --- .../Finder/Adapter/AdapterInterface.php | 7 ++ .../Finder/Adapter/GnuFindAdapter.php | 11 +++ .../Component/Finder/Adapter/PhpAdapter.php | 8 ++ src/Symfony/Component/Finder/ShellTester.php | 80 +++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 src/Symfony/Component/Finder/ShellTester.php diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php index 47e64840a0567..ade2a01baa148 100644 --- a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php +++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -106,4 +106,11 @@ function setSort($sort); * @return \Iterator Result iterator */ function searchInDirectory($dir); + + /** + * Tests adapter validity for current plateform. + * + * @return bool + */ + function isValid(); } diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 8c947be2cd130..d440465bdc73a 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -88,4 +88,15 @@ public function searchInDirectory($dir) return $iterator; } + + /** + * {@inheritdoc} + */ + public function isValid() + { + $shell = new ShellTester(); + + return $shell->getType() !== ShellTester::TYPE_WINDOWS + && $shell->testCommand('find'); + } } diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php index a93629728dd76..4790c5432fb8a 100644 --- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -75,4 +75,12 @@ public function searchInDirectory($dir) return $iterator; } + + /** + * {@inheritdoc} + */ + public function isValid() + { + return true; + } } diff --git a/src/Symfony/Component/Finder/ShellTester.php b/src/Symfony/Component/Finder/ShellTester.php new file mode 100644 index 0000000000000..001e9a5798860 --- /dev/null +++ b/src/Symfony/Component/Finder/ShellTester.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * @author Jean-François Simon + */ +class ShellTester +{ + const TYPE_UNIX = 1; + const TYPE_MAC = 2; + const TYPE_CYGWIN = 3; + const TYPE_WINDOWS = 4; + + /** + * @var string|null + */ + private $type; + + /** + * Returns guessed OS type. + * + * @return int + */ + public function getType() + { + if (null === $this->type) { + $this->type = $this->guessType(); + } + + return $this->type; + } + + /** + * Tests if a command is available. + * + * @param string $command + * + * @return bool + */ + public function testCommand($command) + { + exec($command, $output, $code); + + return 0 === $code; + } + + /** + * Guesses OS type. + * + * @return int + */ + private function guessType() + { + $os = strtolower(PHP_OS); + + if (false !== strpos($os, 'cygwin')) { + return self::TYPE_CYGWIN; + } + + if (false !== strpos($os, 'darwin')) { + return self::TYPE_MAC; + } + + if (0 === strpos($os, 'win')) { + return self::TYPE_WINDOWS; + } + + return self::TYPE_UNIX; + } +} From dc4bf1f2d48b7009b2e341f2baecafaad352dce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 11:32:25 +0200 Subject: [PATCH 11/37] [Finder] Added adapter selection method. --- src/Symfony/Component/Finder/Finder.php | 33 ++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index d23e57dfeef05..7cc0d1f1ff21d 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Finder; -use Symfony\Component\Finder\Adapter\PhpAdapter; +use Symfony\Component\Finder\Adapter; /** * Finder allows to build rules to find files and directories. @@ -48,6 +48,7 @@ class Finder implements \IteratorAggregate, \Countable private $iterators = array(); private $contains = array(); private $notContains = array(); + private $adapter; private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); @@ -56,7 +57,8 @@ class Finder implements \IteratorAggregate, \Countable */ public function __construct() { - $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + $this->adapter = new Adapter\PhpAdapter(); } /** @@ -71,6 +73,22 @@ public static function create() return new static(); } + /** + * Changes engine implementation. + * + * @param Adapter\AdapterInterface $adapter An engine implementation + * + * @return \Symfony\Component\Finder\Finder The current Finder instance + */ + public function using(Adapter\AdapterInterface $adapter) + { + if ($adapter->isValid()) { + $this->adapter = $adapter; + } + + return $this; + } + /** * Restricts the matching to directories only. * @@ -572,13 +590,10 @@ public function count() } /* - * @return \Symfony\Component\Finder\Adapter\AdapterInterface + * @param $dir + * + * @return \Iterator */ - private function getAdapter() - { - return new PhpAdapter(); - } - private function searchInDirectory($dir) { if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { @@ -590,7 +605,7 @@ private function searchInDirectory($dir) } return $this - ->getAdapter() + ->adapter ->setFollowLinks($this->followLinks) ->setDepths($this->depths) ->setMode($this->mode) From c23ebf9eb8ad752b7427851184569eb5c43e4522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 11:32:48 +0200 Subject: [PATCH 12/37] [Finder] Updated tests to use all valid adapters. --- .../Component/Finder/Tests/FinderTest.php | 289 ++++++++++++------ .../Tests/Iterator/IteratorTestCase.php | 4 +- 2 files changed, 200 insertions(+), 93 deletions(-) diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 7005b7ea74aed..da69c1e9133ce 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Finder\Tests; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\Adapter; class FinderTest extends Iterator\RealIteratorTestCase { @@ -24,80 +25,98 @@ public static function setUpBeforeClass() self::$tmpDir = sys_get_temp_dir().'/symfony2_finder'; } - public function testCreate() + /** + * @dataProvider getAdaptersTestData + */ + public function testCreate(Adapter\AdapterInterface $adapter) { $this->assertInstanceOf('Symfony\Component\Finder\Finder', Finder::create()); } - public function testDirectories() + /** + * @dataProvider getAdaptersTestData + */ + public function testDirectories(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->directories()); $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->directories(); $finder->files(); $finder->directories(); $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } - public function testFiles() + /** + * @dataProvider getAdaptersTestData + */ + public function testFiles(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->files()); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->files(); $finder->directories(); $finder->files(); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); } - public function testDepth() + /** + * @dataProvider getAdaptersTestData + */ + public function testDepth(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->depth('< 1')); $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->depth('<= 0')); $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->depth('>= 1')); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->depth('< 1')->depth('>= 1'); $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); } - public function testName() + /** + * @dataProvider getAdaptersTestData + */ + public function testName(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->name('*.php')); $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->name('test.ph*'); $finder->name('test.py'); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); } - public function testNotName() + /** + * @dataProvider getAdaptersTestData + */ + public function testNotName(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->notName('*.php')); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->notName('*.php'); $finder->notName('*.py'); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->name('test.ph*'); $finder->name('test.py'); $finder->notName('*.php'); @@ -105,46 +124,60 @@ public function testNotName() $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); } - public function testSize() + /** + * @dataProvider getAdaptersTestData + */ + public function testSize(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500')); $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); } - public function testDate() + /** + * @dataProvider getAdaptersTestData + */ + public function testDate(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->files()->date('until last month')); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php')), $finder->in(self::$tmpDir)->getIterator()); } - public function testExclude() + /** + * @dataProvider getAdaptersTestData + */ + public function testExclude(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->exclude('foo')); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } - public function testIgnoreVCS() + /** + * @dataProvider getAdaptersTestData + */ + public function testIgnoreVCS(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false)); $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false); $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false)); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar')), $finder->in(self::$tmpDir)->getIterator()); - } - public function testIgnoreDotFiles() + /** + * @dataProvider getAdaptersTestData + */ + public function testIgnoreDotFiles(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false)); $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); @@ -152,75 +185,102 @@ public function testIgnoreDotFiles() $finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false); $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false)); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } - public function testSortByName() + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByName(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByName()); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } - public function testSortByType() + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByType(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByType()); $this->assertIterator($this->toAbsolute(array('foo', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); } - public function testSortByAccessedTime() + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByAccessedTime(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByAccessedTime()); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo')), $finder->in(self::$tmpDir)->getIterator()); } - public function testSortByChangedTime() + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByChangedTime(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByChangedTime()); $this->assertIterator($this->toAbsolute(array('toto', 'test.py', 'test.php', 'foo/bar.tmp', 'foo')), $finder->in(self::$tmpDir)->getIterator()); } - public function testSortByModifiedTime() + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByModifiedTime(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByModifiedTime()); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo')), $finder->in(self::$tmpDir)->getIterator()); } - public function testSort() + /** + * @dataProvider getAdaptersTestData + */ + public function testSort(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); })); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } - public function testFilter() + /** + * @dataProvider getAdaptersTestData + */ + public function testFilter(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return preg_match('/test/', $f) > 0; })); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); } - public function testFollowLinks() + /** + * @dataProvider getAdaptersTestData + */ + public function testFollowLinks(Adapter\AdapterInterface $adapter) { if ('\\' == DIRECTORY_SEPARATOR) { return; } - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->followLinks()); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } - public function testIn() + /** + * @dataProvider getAdaptersTestData + */ + public function testIn(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); try { $finder->in('foobar'); $this->fail('->in() throws a \InvalidArgumentException if the directory does not exist'); @@ -228,15 +288,18 @@ public function testIn() $this->assertInstanceOf('InvalidArgumentException', $e, '->in() throws a \InvalidArgumentException if the directory does not exist'); } - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator(); $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'bootstrap.php', __DIR__.DIRECTORY_SEPARATOR.'ExprTest.php'), $iterator); } - public function testGetIterator() + /** + * @dataProvider getAdaptersTestData + */ + public function testGetIterator(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); try { $finder->getIterator(); $this->fail('->getIterator() throws a \LogicException if the in() method has not been called'); @@ -244,7 +307,7 @@ public function testGetIterator() $this->assertInstanceOf('LogicException', $e, '->getIterator() throws a \LogicException if the in() method has not been called'); } - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $dirs = array(); foreach ($finder->directories()->in(self::$tmpDir) as $dir) { $dirs[] = (string) $dir; @@ -257,19 +320,22 @@ public function testGetIterator() $this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface'); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $this->assertEquals(2, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface'); - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $a = iterator_to_array($finder->directories()->in(self::$tmpDir)); $a = array_values(array_map(function ($a) { return (string) $a; }, $a)); sort($a); $this->assertEquals($expected, $a, 'implements the \IteratorAggregate interface'); } - public function testRelativePath() + /** + * @dataProvider getAdaptersTestData + */ + public function testRelativePath(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->in(self::$tmpDir); @@ -284,12 +350,15 @@ public function testRelativePath() sort($ref); sort($paths); - $this->assertEquals($paths, $ref); + $this->assertEquals($ref, $paths); } - public function testRelativePathname() + /** + * @dataProvider getAdaptersTestData + */ + public function testRelativePathname(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->in(self::$tmpDir)->sortByName(); @@ -304,15 +373,18 @@ public function testRelativePathname() sort($paths); sort($ref); - $this->assertEquals($paths, $ref); + $this->assertEquals($ref, $paths); } - public function testAppendWithAFinder() + /** + * @dataProvider getAdaptersTestData + */ + public function testAppendWithAFinder(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); - $finder1 = new Finder(); + $finder1 = Finder::create()->using($adapter); $finder1->directories()->in(self::$tmpDir); $finder->append($finder1); @@ -320,9 +392,12 @@ public function testAppendWithAFinder() $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator()); } - public function testAppendWithAnArray() + /** + * @dataProvider getAdaptersTestData + */ + public function testAppendWithAnArray(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); $finder->append($this->toAbsolute(array('foo', 'toto'))); @@ -392,9 +467,9 @@ protected function toAbsoluteFixtures($files) /** * @dataProvider getContainsTestData */ - public function testContains($matchPatterns, $noMatchPatterns, $expected) + public function testContains(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, $expected) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures') ->name('*.txt')->sortByName() ->contains($matchPatterns) @@ -403,26 +478,12 @@ public function testContains($matchPatterns, $noMatchPatterns, $expected) $this->assertIterator($this->toAbsoluteFixtures($expected), $finder); } - public function getContainsTestData() - { - 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')), - ); - } - - public function testContainsOnDirectory() + /** + * @dataProvider getAdaptersTestData + */ + public function testContainsOnDirectory(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->in(__DIR__) ->directories() ->name('Fixtures') @@ -430,9 +491,12 @@ public function testContainsOnDirectory() $this->assertIterator(array(), $finder); } - public function testNotContainsOnDirectory() + /** + * @dataProvider getAdaptersTestData + */ + public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter) { - $finder = new Finder(); + $finder = Finder::create()->using($adapter); $finder->in(__DIR__) ->directories() ->name('Fixtures') @@ -446,7 +510,7 @@ public function testNotContainsOnDirectory() * * @see https://bugs.php.net/bug.php?id=49104 */ - public function testMultipleLocations() + public function testMultipleLocations(Adapter\AdapterInterface $adapter) { $locations = array( self::$tmpDir.'/', @@ -459,4 +523,45 @@ public function testMultipleLocations() $this->assertEquals(1, count($finder)); } + + public function getAdaptersTestData() + { + return array_map(function (Adapter\AdapterInterface $adapter) { + return array($adapter); + }, $this->getValidAdapters()); + } + + public function getContainsTestData() + { + $tests = 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')), + ); + + $data = array(); + foreach ($this->getValidAdapters() as $adapter) { + foreach ($tests as $test) { + $data[] = array_merge(array($adapter), $test); + } + } + + return $data; + } + + private function getValidAdapters() + { + return array_filter( + array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()), + function (Adapter\AdapterInterface $adapter) { return $adapter->isValid(); } + ); + } } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php index 8810ce757d25a..226469b4e7a93 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php @@ -15,7 +15,9 @@ abstract class IteratorTestCase extends \PHPUnit_Framework_TestCase { protected function assertIterator($expected, \Traversable $iterator) { - $values = array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator)); + // set iterator_to_array $use_key to false to avoid values merge + // this made FinderTest::testAppendWithAnArray() failed with GnuFinderAdapter + $values = array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator, false)); sort($values); sort($expected); From bf74c89c3628c67ef21d18d368fe771bcc5bdee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 12:36:40 +0200 Subject: [PATCH 13/37] [Finder] Added native names/notNames support to gnu find adapter. --- .../Finder/Adapter/GnuFindAdapter.php | 33 ++++++++++++++++--- src/Symfony/Component/Finder/Expr.php | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index d440465bdc73a..8d82fe7ff98b7 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -13,6 +13,7 @@ use Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\ShellTester; +use Symfony\Component\Finder\Expr; /** * PHP finder engine implementation. @@ -49,6 +50,9 @@ public function searchInDirectory($dir) $command.= ' -type f'; } + $command.= $this->buildNamesOptions($this->names); + $command.= $this->buildNamesOptions($this->notNames, true); + exec($command, $paths, $code); if ($code !== 0) { @@ -61,10 +65,6 @@ public function searchInDirectory($dir) $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); } - if ($this->names || $this->notNames) { - $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); - } - if ($this->contains || $this->notContains) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } @@ -99,4 +99,29 @@ public function isValid() return $shell->getType() !== ShellTester::TYPE_WINDOWS && $shell->testCommand('find'); } + + private function buildNamesOptions(array $names, $not = false) + { + if (0 === count($names)) { + return ''; + } + + $options = array(); + + foreach ($names as $name) { + $expr = Expr::create($name); + + if ($expr->isRegex()) { + $option = $expr->isCaseSensitive() ? '-regex' : '-iregex'; + } elseif ($expr->isGlob()) { + $option = $expr->isCaseSensitive() ? '-name' : '-iname'; + } else { + continue; + } + + $options[] = $option.' '.escapeshellarg($expr->getBody()); + } + + return ' -regextype posix-extended'.($not ? ' -not ' : ' ').'\\( '.implode(' -or ', $options).' \\)'; + } } diff --git a/src/Symfony/Component/Finder/Expr.php b/src/Symfony/Component/Finder/Expr.php index 6fd8af808758e..7b96f54d9e553 100644 --- a/src/Symfony/Component/Finder/Expr.php +++ b/src/Symfony/Component/Finder/Expr.php @@ -79,7 +79,7 @@ static public function create($expr) */ public function isCaseSensitive() { - return self::TYPE_GLOB === $this->type + return !self::TYPE_GLOB === $this->type || false === strpos($this->flags, 'i'); } From 7b593ee62c3d0a34ad8ba51f74f32519ea1ae864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 13:58:39 +0200 Subject: [PATCH 14/37] [Finder] Added native sizes support to gnu find adapter. --- .../Finder/Adapter/GnuFindAdapter.php | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 8d82fe7ff98b7..bbda272ca8b41 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -52,6 +52,7 @@ public function searchInDirectory($dir) $command.= $this->buildNamesOptions($this->names); $command.= $this->buildNamesOptions($this->notNames, true); + $command.= $this->buildSizesOptions($this->sizes); exec($command, $paths, $code); @@ -69,10 +70,6 @@ public function searchInDirectory($dir) $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } - if ($this->sizes) { - $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); - } - if ($this->dates) { $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); } @@ -100,6 +97,12 @@ public function isValid() && $shell->testCommand('find'); } + /** + * @param string[] $names + * @param bool $not + * + * @return string + */ private function buildNamesOptions(array $names, $not = false) { if (0 === count($names)) { @@ -124,4 +127,50 @@ private function buildNamesOptions(array $names, $not = false) return ' -regextype posix-extended'.($not ? ' -not ' : ' ').'\\( '.implode(' -or ', $options).' \\)'; } + + /** + * @param \Symfony\Component\Finder\Comparator\NumberComparator[] $sizes + * + * @return string + */ + private function buildSizesOptions(array $sizes) + { + if (0 === count($sizes)) { + return ''; + } + + $options = array(); + + foreach ($sizes as $size) { + if ('<=' === $size->getOperator()) { + $options[] = '-size -'.($size->getTarget()+1).'c'; + continue; + } + + if ('<' === $size->getOperator()) { + $options[] = '-size -'.$size->getTarget().'c'; + continue; + } + + if ('>=' === $size->getOperator()) { + $options[] = '-size +'.($size->getTarget()-1).'c'; + continue; + } + + if ('>' === $size->getOperator()) { + $options[] = '-size +'.$size->getTarget().'c'; + continue; + } + + if ('!=' === $size->getOperator()) { + $options[] = '-size -'.$size->getTarget().'c'; + $options[] = '-size +'.$size->getTarget().'c'; + continue; + } + + $options[] = '-size '.$size->getTarget().'c'; + } + + return ' \\( '.implode(' -and ', $options).' \\)'; + } } From 3b3ee8f525c28209f242aec9e5041046d0eb2801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 14:33:21 +0200 Subject: [PATCH 15/37] [Finder] Added native dates support to gnu find adapter. --- .../Finder/Adapter/GnuFindAdapter.php | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index bbda272ca8b41..a5023b66b8024 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -53,6 +53,7 @@ public function searchInDirectory($dir) $command.= $this->buildNamesOptions($this->names); $command.= $this->buildNamesOptions($this->notNames, true); $command.= $this->buildSizesOptions($this->sizes); + $command.= $this->buildDatesOptions($this->dates); exec($command, $paths, $code); @@ -70,10 +71,6 @@ public function searchInDirectory($dir) $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } - if ($this->dates) { - $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); - } - if ($this->filters) { $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } @@ -173,4 +170,57 @@ private function buildSizesOptions(array $sizes) return ' \\( '.implode(' -and ', $options).' \\)'; } + + /** + * @param \Symfony\Component\Finder\Comparator\DateComparator[] $dates + * + * @return string + */ + private function buildDatesOptions(array $dates) + { + if (0 === count($dates)) { + return ''; + } + + $options = array(); + + foreach ($dates as $date) { + $mins = (int) round((time()-$date->getTarget())/60); + + if (0 > $mins) { + // mtime is in the future + // we will have no result + return ' -mmin -0'; + } + + if ('<=' === $date->getOperator()) { + $options[] = '-mmin +'.($mins-1); + continue; + } + + if ('<' === $date->getOperator()) { + $options[] = '-mmin +'.$mins; + continue; + } + + if ('>=' === $date->getOperator()) { + $options[] = '-mmin -'.($mins+1); + continue; + } + + if ('>' === $date->getOperator()) { + $options[] = '-mmin -'.$mins; + continue; + } + + if ('!=' === $date->getOperator()) { + $options[] = '-mmin +'.$mins.' -or -mmin -'.$mins; + continue; + } + + $options[] = '-mmin '.$mins; + } + + return ' \\( '.implode(' -and ', $options).' \\)'; + } } From 530f2294affb77a3d44b1cb29820746da2ced450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 15:56:56 +0200 Subject: [PATCH 16/37] [Finder] Performed some cleanup. --- .../Finder/Adapter/AdapterInterface.php | 4 +- .../Finder/Adapter/GnuFindAdapter.php | 112 +++++++++--------- .../Component/Finder/Adapter/PhpAdapter.php | 2 +- src/Symfony/Component/Finder/Command.php | 99 ++++++++++++++++ src/Symfony/Component/Finder/Expr.php | 2 +- src/Symfony/Component/Finder/Finder.php | 2 +- .../Finder/{ShellTester.php => Shell.php} | 2 +- .../Component/Finder/Tests/FinderTest.php | 2 +- 8 files changed, 160 insertions(+), 65 deletions(-) create mode 100644 src/Symfony/Component/Finder/Command.php rename src/Symfony/Component/Finder/{ShellTester.php => Shell.php} (98%) diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php index ade2a01baa148..b118e52e86fe3 100644 --- a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php +++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -108,9 +108,9 @@ function setSort($sort); function searchInDirectory($dir); /** - * Tests adapter validity for current plateform. + * Tests adapter support for current plateform. * * @return bool */ - function isValid(); + function isSupported(); } diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index a5023b66b8024..05497205b8419 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -12,8 +12,9 @@ namespace Symfony\Component\Finder\Adapter; use Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\ShellTester; +use Symfony\Component\Finder\Shell; use Symfony\Component\Finder\Expr; +use Symfony\Component\Finder\Command; /** * PHP finder engine implementation. @@ -30,38 +31,36 @@ public function searchInDirectory($dir) // -noleaf option is required for filesystems // who doesn't follow '.' and '..' convention // like MSDOS, CDROM or AFS mount points - $command = 'find '.$dir.' -noleaf'; + $command = Command::create('find ')->arg($dir)->add('-noleaf'); if ($this->followLinks) { - $command.= ' -follow'; + $command->add('-follow'); } - $command.= ' -mindepth '.($this->minDepth+1); + $command->add('-mindepth')->add($this->minDepth+1); // warning! INF < INF => true ; INF == INF => false ; INF === INF => true // https://bugs.php.net/bug.php?id=9118 - if ($this->maxDepth !== INF) { - $command.= ' -maxdepth '.($this->maxDepth+1); + if (INF !== $this->maxDepth) { + $command->add('-maxdepth')->add($this->maxDepth+1); } if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { - $command.= ' -type d'; + $command->add('-type d'); } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { - $command.= ' -type f'; + $command->add('-type f'); } - $command.= $this->buildNamesOptions($this->names); - $command.= $this->buildNamesOptions($this->notNames, true); - $command.= $this->buildSizesOptions($this->sizes); - $command.= $this->buildDatesOptions($this->dates); + $this->buildNamesOptions($command, $this->names); + $this->buildNamesOptions($command, $this->notNames, true); + $this->buildSizesOptions($command, $this->sizes); + $this->buildDatesOptions($command, $this->dates); - exec($command, $paths, $code); - - if ($code !== 0) { + if (0 !== $command->execute($output)) { throw new \RuntimeException(); } - $iterator = new Iterator\FilePathsIterator($paths, $dir); + $iterator = new Iterator\FilePathsIterator($output, $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); @@ -86,104 +85,101 @@ public function searchInDirectory($dir) /** * {@inheritdoc} */ - public function isValid() + public function isSupported() { - $shell = new ShellTester(); + $shell = new Shell(); - return $shell->getType() !== ShellTester::TYPE_WINDOWS + return $shell->getType() !== Shell::TYPE_WINDOWS && $shell->testCommand('find'); } /** - * @param string[] $names - * @param bool $not - * - * @return string + * @param \Symfony\Component\Finder\Command $command + * @param string[] $names + * @param bool $not */ - private function buildNamesOptions(array $names, $not = false) + private function buildNamesOptions(Command $command, array $names, $not = false) { if (0 === count($names)) { - return ''; + return; } - $options = array(); - + $bits = array(); foreach ($names as $name) { $expr = Expr::create($name); if ($expr->isRegex()) { - $option = $expr->isCaseSensitive() ? '-regex' : '-iregex'; + $bit = $expr->isCaseSensitive() ? '-regex' : '-iregex'; } elseif ($expr->isGlob()) { - $option = $expr->isCaseSensitive() ? '-name' : '-iname'; + $bit = $expr->isCaseSensitive() ? '-name' : '-iname'; } else { continue; } - $options[] = $option.' '.escapeshellarg($expr->getBody()); + $bits[] = $bit.' '.escapeshellarg($expr->getBody()); } - return ' -regextype posix-extended'.($not ? ' -not ' : ' ').'\\( '.implode(' -or ', $options).' \\)'; + $command + ->add('-regextype posix-extended') + ->add($not ? '-not' : '') + ->cmd('(')->add(implode(' -or ', $bits))->cmd(')'); } /** + * @param \Symfony\Component\Finder\Command $command * @param \Symfony\Component\Finder\Comparator\NumberComparator[] $sizes - * - * @return string */ - private function buildSizesOptions(array $sizes) + private function buildSizesOptions(Command $command, array $sizes) { if (0 === count($sizes)) { - return ''; + return; } - $options = array(); - + $bits = array(); foreach ($sizes as $size) { if ('<=' === $size->getOperator()) { - $options[] = '-size -'.($size->getTarget()+1).'c'; + $bits[] = '-size -'.($size->getTarget()+1).'c'; continue; } if ('<' === $size->getOperator()) { - $options[] = '-size -'.$size->getTarget().'c'; + $bits[] = '-size -'.$size->getTarget().'c'; continue; } if ('>=' === $size->getOperator()) { - $options[] = '-size +'.($size->getTarget()-1).'c'; + $bits[] = '-size +'.($size->getTarget()-1).'c'; continue; } if ('>' === $size->getOperator()) { - $options[] = '-size +'.$size->getTarget().'c'; + $bits[] = '-size +'.$size->getTarget().'c'; continue; } if ('!=' === $size->getOperator()) { - $options[] = '-size -'.$size->getTarget().'c'; - $options[] = '-size +'.$size->getTarget().'c'; + $bits[] = '-size -'.$size->getTarget().'c'; + $bits[] = '-size +'.$size->getTarget().'c'; continue; } - $options[] = '-size '.$size->getTarget().'c'; + $bits[] = '-size '.$size->getTarget().'c'; } - return ' \\( '.implode(' -and ', $options).' \\)'; + $command->cmd('(')->add(implode(' -and ', $bits))->cmd(')'); } /** + * @param \Symfony\Component\Finder\Command $command * @param \Symfony\Component\Finder\Comparator\DateComparator[] $dates - * - * @return string */ - private function buildDatesOptions(array $dates) + private function buildDatesOptions(Command $command, array $dates) { if (0 === count($dates)) { - return ''; + return; } - $options = array(); - + $bits = array(); foreach ($dates as $date) { $mins = (int) round((time()-$date->getTarget())/60); @@ -194,33 +190,33 @@ private function buildDatesOptions(array $dates) } if ('<=' === $date->getOperator()) { - $options[] = '-mmin +'.($mins-1); + $bits[] = '-mmin +'.($mins-1); continue; } if ('<' === $date->getOperator()) { - $options[] = '-mmin +'.$mins; + $bits[] = '-mmin +'.$mins; continue; } if ('>=' === $date->getOperator()) { - $options[] = '-mmin -'.($mins+1); + $bits[] = '-mmin -'.($mins+1); continue; } if ('>' === $date->getOperator()) { - $options[] = '-mmin -'.$mins; + $bits[] = '-mmin -'.$mins; continue; } if ('!=' === $date->getOperator()) { - $options[] = '-mmin +'.$mins.' -or -mmin -'.$mins; + $bits[] = '-mmin +'.$mins.' -or -mmin -'.$mins; continue; } - $options[] = '-mmin '.$mins; + $bits[] = '-mmin '.$mins; } - return ' \\( '.implode(' -and ', $options).' \\)'; + $command->cmd('(')->add(implode(' -and ', $bits))->cmd(')'); } } diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php index 4790c5432fb8a..77590b88c1fc9 100644 --- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -79,7 +79,7 @@ public function searchInDirectory($dir) /** * {@inheritdoc} */ - public function isValid() + public function isSupported() { return true; } diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Command.php new file mode 100644 index 0000000000000..3cec2ddb8a00e --- /dev/null +++ b/src/Symfony/Component/Finder/Command.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * @author Jean-François Simon + */ +class Command +{ + /** + * @var array + */ + private $bits; + + /** + * @param string $command + */ + public function __construct($bit = null) + { + if ($bit) { + $this->bits[] = $bit; + } + } + + /** + * @return string + */ + public function __toString() + { + return implode(' ', $this->bits); + } + + /** + * @param string $command + * + * @return \Symfony\Component\Finder\Command + */ + static public function create($command) + { + return new self($command); + } + + /** + * @param string $bit + * + * @return \Symfony\Component\Finder\Command The current Command instance + */ + public function add($bit) + { + $this->bits[] = (string) $bit; + + return $this; + } + + /** + * @param string $arg + * + * @return \Symfony\Component\Finder\Command The current Command instance + */ + public function arg($arg) + { + $this->bits[] = escapeshellarg($arg); + + return $this; + } + + /** + * @param string $esc + * + * @return \Symfony\Component\Finder\Command The current Command instance + */ + public function cmd($esc) + { + $this->bits[] = escapeshellcmd($esc); + + return $this; + } + + /** + * @param $output + * + * @return int + */ + public function execute(&$output) + { + exec(implode(' ', $this->bits), $output, $code); + + return $code; + } +} diff --git a/src/Symfony/Component/Finder/Expr.php b/src/Symfony/Component/Finder/Expr.php index 7b96f54d9e553..6fd8af808758e 100644 --- a/src/Symfony/Component/Finder/Expr.php +++ b/src/Symfony/Component/Finder/Expr.php @@ -79,7 +79,7 @@ static public function create($expr) */ public function isCaseSensitive() { - return !self::TYPE_GLOB === $this->type + return self::TYPE_GLOB === $this->type || false === strpos($this->flags, 'i'); } diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 7cc0d1f1ff21d..120be554449a2 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -82,7 +82,7 @@ public static function create() */ public function using(Adapter\AdapterInterface $adapter) { - if ($adapter->isValid()) { + if ($adapter->isSupported()) { $this->adapter = $adapter; } diff --git a/src/Symfony/Component/Finder/ShellTester.php b/src/Symfony/Component/Finder/Shell.php similarity index 98% rename from src/Symfony/Component/Finder/ShellTester.php rename to src/Symfony/Component/Finder/Shell.php index 001e9a5798860..9f9bef0d1a531 100644 --- a/src/Symfony/Component/Finder/ShellTester.php +++ b/src/Symfony/Component/Finder/Shell.php @@ -14,7 +14,7 @@ /** * @author Jean-François Simon */ -class ShellTester +class Shell { const TYPE_UNIX = 1; const TYPE_MAC = 2; diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index da69c1e9133ce..d6d794187b748 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -561,7 +561,7 @@ private function getValidAdapters() { return array_filter( array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()), - function (Adapter\AdapterInterface $adapter) { return $adapter->isValid(); } + function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); } ); } } From 05897a4ad247e62b2ca3df845ddacd8aa0851309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Sun, 22 Apr 2012 21:15:16 +0200 Subject: [PATCH 17/37] [Finder] Improved command class. --- src/Symfony/Component/Finder/Command.php | 122 +++++++++++++++++++---- 1 file changed, 105 insertions(+), 17 deletions(-) diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Command.php index 3cec2ddb8a00e..9b9774441d1c2 100644 --- a/src/Symfony/Component/Finder/Command.php +++ b/src/Symfony/Component/Finder/Command.php @@ -16,52 +16,72 @@ */ class Command { + /** + * @var Command|null + */ + private $parent; + /** * @var array */ private $bits; /** - * @param string $command + * @var array */ - public function __construct($bit = null) + private $labels; + + /** + * Constructor. + * + * @param Command $parent Parent command + */ + public function __construct(Command $parent = null) { - if ($bit) { - $this->bits[] = $bit; - } + $this->parent = null; + $this->bits = array(); + $this->labels = array(); } /** + * Returns command as string. + * * @return string */ public function __toString() { - return implode(' ', $this->bits); + return $this->join(); } /** - * @param string $command + * Creates a new Command instance. * - * @return \Symfony\Component\Finder\Command + * @param Command $parent Parent command + * + * @return \Symfony\Component\Finder\Command New Command instance */ - static public function create($command) + static public function create(Command $parent = null) { - return new self($command); + return new self($parent); } /** - * @param string $bit + * Appends a string or a Command instance. + * + * @param string|Command $bit * * @return \Symfony\Component\Finder\Command The current Command instance */ public function add($bit) { - $this->bits[] = (string) $bit; + $this->bits[] = $bit; return $this; } /** + * Appends an argument, will be quoted. + * * @param string $arg * * @return \Symfony\Component\Finder\Command The current Command instance @@ -74,6 +94,8 @@ public function arg($arg) } /** + * Appends escaped special command chars. + * * @param string $esc * * @return \Symfony\Component\Finder\Command The current Command instance @@ -86,14 +108,80 @@ public function cmd($esc) } /** - * @param $output + * Inserts a labeled command to feed later. + * + * @param string $label The unique label + * + * @return \Symfony\Component\Finder\Command The current Command instance + * + * @throws \RuntimeException If label already exists + */ + public function ins($label) + { + if (isset($this->labels[$label])) { + throw new \RuntimeException('Label "'.$label.'" already exists.'); + } + + $this->bits[] = self::create($this); + $this->labels[$label] = count($this->bits)-1; + + return $this; + } + + /** + * Retrieves a previously labeled command. + * + * @param string $label + * + * @return \Symfony\Component\Finder\Command The labeled command + */ + public function get($label) + { + return $this->labels[$label]; + } + + /** + * Returns parent command (if any). + * + * @return Command Parent command + * + * @throws \RuntimeException If command has no parent + */ + public function end() + { + if (null === $this->parent) { + throw new \RuntimeException('Calling end on root command dont makes sense.'); + } + + return $this->parent; + } + + /** + * Executes current command. * - * @return int + * @return array The command result */ - public function execute(&$output) + public function execute() { - exec(implode(' ', $this->bits), $output, $code); + exec($this->join(), $output, $code); - return $code; + if (0 !== $code) { + throw new \RuntimeException('Execution failed with return code: '.$code.'.'); + } + + return $output ?: array(); + } + + /** + * Joins bits. + * + * @return string + */ + private function join() + { + return implode(' ', array_filter( + array_map(function($bit) { return $bit instanceof Command ? $bit->join : ($bit ?: null); }, $this->bits), + function($bit) { return null !== $bit; } + )); } } From da79cc4bff1c3cc79c28591ca51794fe63c1d94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Mon, 23 Apr 2012 07:43:47 +0200 Subject: [PATCH 18/37] [Finder] Refactored command building. --- .../Finder/Adapter/GnuFindAdapter.php | 99 +++++++++---------- src/Symfony/Component/Finder/Command.php | 69 +++++++++++-- 2 files changed, 112 insertions(+), 56 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 05497205b8419..24aa093024d22 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -30,8 +30,7 @@ public function searchInDirectory($dir) { // -noleaf option is required for filesystems // who doesn't follow '.' and '..' convention - // like MSDOS, CDROM or AFS mount points - $command = Command::create('find ')->arg($dir)->add('-noleaf'); + $command = Command::create()->add('find ')->arg($dir)->add('-noleaf'); if ($this->followLinks) { $command->add('-follow'); @@ -51,16 +50,21 @@ public function searchInDirectory($dir) $command->add('-type f'); } - $this->buildNamesOptions($command, $this->names); - $this->buildNamesOptions($command, $this->notNames, true); - $this->buildSizesOptions($command, $this->sizes); - $this->buildDatesOptions($command, $this->dates); + $command->ins('names')->ins('notNames'); + $this->buildNamesCommand($command->get('names'), $this->names); + $this->buildNamesCommand($command->get('notNames'), $this->notNames, true); - if (0 !== $command->execute($output)) { - throw new \RuntimeException(); + if ($command->get('names')->length() || $command->get('notNames')->length()) { + $command->get('names')->top('-regextype posix-extended'); } - $iterator = new Iterator\FilePathsIterator($output, $dir); + $command->ins('sizes'); + $this->buildSizesCommand($command->get('sizes'), $this->sizes); + + $command->ins('dates'); + $this->buildDatesCommand($command->get('dates'), $this->dates); + + $iterator = new Iterator\FilePathsIterator($command->execute(), $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); @@ -98,125 +102,120 @@ public function isSupported() * @param string[] $names * @param bool $not */ - private function buildNamesOptions(Command $command, array $names, $not = false) + private function buildNamesCommand(Command $command, array $names, $not = false) { if (0 === count($names)) { return; } - $bits = array(); - foreach ($names as $name) { - $expr = Expr::create($name); + $command->add($not ? '-not' : null)->cmd('('); - if ($expr->isRegex()) { - $bit = $expr->isCaseSensitive() ? '-regex' : '-iregex'; - } elseif ($expr->isGlob()) { - $bit = $expr->isCaseSensitive() ? '-name' : '-iname'; - } else { - continue; - } + foreach ($names as $i => $name) { + $expr = Expr::create($name); - $bits[] = $bit.' '.escapeshellarg($expr->getBody()); + $command + ->add($i > 0 ? '-or' : null) + ->add($expr->isRegex() + ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') + : ($expr->isCaseSensitive() ? '-name' : '-iname') + ) + ->arg($expr->getBody()); } - $command - ->add('-regextype posix-extended') - ->add($not ? '-not' : '') - ->cmd('(')->add(implode(' -or ', $bits))->cmd(')'); + $command->cmd(')'); } /** * @param \Symfony\Component\Finder\Command $command * @param \Symfony\Component\Finder\Comparator\NumberComparator[] $sizes */ - private function buildSizesOptions(Command $command, array $sizes) + private function buildSizesCommand(Command $command, array $sizes) { if (0 === count($sizes)) { return; } - $bits = array(); - foreach ($sizes as $size) { + foreach ($sizes as $i => $size) { + $command->add($i > 0 ? '-and' : null); + if ('<=' === $size->getOperator()) { - $bits[] = '-size -'.($size->getTarget()+1).'c'; + $command->add('-size -'.($size->getTarget()+1).'c'); continue; } if ('<' === $size->getOperator()) { - $bits[] = '-size -'.$size->getTarget().'c'; + $command->add('-size -'.$size->getTarget().'c'); continue; } if ('>=' === $size->getOperator()) { - $bits[] = '-size +'.($size->getTarget()-1).'c'; + $command->add('-size +'.($size->getTarget()-1).'c'); continue; } if ('>' === $size->getOperator()) { - $bits[] = '-size +'.$size->getTarget().'c'; + $command->add('-size +'.$size->getTarget().'c'); continue; } if ('!=' === $size->getOperator()) { - $bits[] = '-size -'.$size->getTarget().'c'; - $bits[] = '-size +'.$size->getTarget().'c'; + $command->add('-size -'.$size->getTarget().'c'); + $command->add('-size +'.$size->getTarget().'c'); continue; } - $bits[] = '-size '.$size->getTarget().'c'; + $command->add('-size '.$size->getTarget().'c'); } - - $command->cmd('(')->add(implode(' -and ', $bits))->cmd(')'); } /** * @param \Symfony\Component\Finder\Command $command * @param \Symfony\Component\Finder\Comparator\DateComparator[] $dates */ - private function buildDatesOptions(Command $command, array $dates) + private function buildDatesCommand(Command $command, array $dates) { if (0 === count($dates)) { return; } - $bits = array(); - foreach ($dates as $date) { + foreach ($dates as $i => $date) { + $command->add($i > 0 ? '-and' : null); + $mins = (int) round((time()-$date->getTarget())/60); if (0 > $mins) { // mtime is in the future - // we will have no result - return ' -mmin -0'; + $command->add(' -mmin -0'); + // we will have no result so we dont need to continue + return; } if ('<=' === $date->getOperator()) { - $bits[] = '-mmin +'.($mins-1); + $command->add('-mmin +'.($mins-1)); continue; } if ('<' === $date->getOperator()) { - $bits[] = '-mmin +'.$mins; + $command->add('-mmin +'.$mins); continue; } if ('>=' === $date->getOperator()) { - $bits[] = '-mmin -'.($mins+1); + $command->add('-mmin -'.($mins+1)); continue; } if ('>' === $date->getOperator()) { - $bits[] = '-mmin -'.$mins; + $command->add('-mmin -'.$mins); continue; } if ('!=' === $date->getOperator()) { - $bits[] = '-mmin +'.$mins.' -or -mmin -'.$mins; + $command->add('-mmin +'.$mins.' -or -mmin -'.$mins); continue; } - $bits[] = '-mmin '.$mins; + $command->add('-mmin '.$mins); } - - $command->cmd('(')->add(implode(' -and ', $bits))->cmd(')'); } } diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Command.php index 9b9774441d1c2..0a14fcb327219 100644 --- a/src/Symfony/Component/Finder/Command.php +++ b/src/Symfony/Component/Finder/Command.php @@ -38,7 +38,7 @@ class Command */ public function __construct(Command $parent = null) { - $this->parent = null; + $this->parent = $parent; $this->bits = array(); $this->labels = array(); } @@ -65,6 +65,30 @@ static public function create(Command $parent = null) return new self($parent); } + /** + * Escapes special chars from input. + * + * @param string $input A string to escape + * + * @return string The escaped string + */ + static public function escape($input) + { + return escapeshellcmd($input); + } + + /** + * Quotes input. + * + * @param string $input An argument string + * + * @return string The quoted string + */ + static public function quote($input) + { + return escapeshellarg($input); + } + /** * Appends a string or a Command instance. * @@ -79,6 +103,24 @@ public function add($bit) return $this; } + /** + * Prepends a string or a command instance. + * + * @param string|Command $bit + * + * @return \Symfony\Component\Finder\Command The current Command instance + */ + public function top($bit) + { + array_unshift($this->bits, $bit); + + foreach ($this->labels as $label => $index) { + $this->labels[$label] += 1; + } + + return $this; + } + /** * Appends an argument, will be quoted. * @@ -88,7 +130,7 @@ public function add($bit) */ public function arg($arg) { - $this->bits[] = escapeshellarg($arg); + $this->bits[] = self::quote($arg); return $this; } @@ -102,7 +144,7 @@ public function arg($arg) */ public function cmd($esc) { - $this->bits[] = escapeshellcmd($esc); + $this->bits[] = self::escape($esc); return $this; } @@ -137,7 +179,11 @@ public function ins($label) */ public function get($label) { - return $this->labels[$label]; + if (!isset($this->labels[$label])) { + throw new \RuntimeException('Label "'.$label.'" does not exists.'); + } + + return $this->bits[$this->labels[$label]]; } /** @@ -150,12 +196,23 @@ public function get($label) public function end() { if (null === $this->parent) { + var_dump($this); throw new \RuntimeException('Calling end on root command dont makes sense.'); } return $this->parent; } + /** + * Counts bits stored in command. + * + * @return int The bits count + */ + public function length() + { + return count($this->bits); + } + /** * Executes current command. * @@ -177,10 +234,10 @@ public function execute() * * @return string */ - private function join() + public function join() { return implode(' ', array_filter( - array_map(function($bit) { return $bit instanceof Command ? $bit->join : ($bit ?: null); }, $this->bits), + array_map(function($bit) { return $bit instanceof Command ? $bit->join() : ($bit ?: null); }, $this->bits), function($bit) { return null !== $bit; } )); } From 19f055e2a00a190fd76cc00945cad6a50edbba61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Mon, 23 Apr 2012 08:29:39 +0200 Subject: [PATCH 19/37] [Finder] Updated shell command test. --- src/Symfony/Component/Finder/Shell.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Finder/Shell.php b/src/Symfony/Component/Finder/Shell.php index 9f9bef0d1a531..65b49e56609f0 100644 --- a/src/Symfony/Component/Finder/Shell.php +++ b/src/Symfony/Component/Finder/Shell.php @@ -49,9 +49,14 @@ public function getType() */ public function testCommand($command) { - exec($command, $output, $code); + if (self::TYPE_WINDOWS === $this->type) { + // todo: find a way to test if windows command exists + return true; + } + + exec('command -v '.$command, $output, $code); - return 0 === $code; + return 0 === $code && count($output) > 0; } /** From daa9b314bad1c75fe80b5f9d91d88fd2c81a79da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Mon, 23 Apr 2012 08:30:33 +0200 Subject: [PATCH 20/37] [Finder] Added adapter registration method. --- .../Finder/Adapter/AdapterInterface.php | 7 ++ .../Finder/Adapter/GnuFindAdapter.php | 8 ++ .../Component/Finder/Adapter/PhpAdapter.php | 8 ++ src/Symfony/Component/Finder/Finder.php | 76 ++++++++++++++++--- .../Component/Finder/Tests/FinderTest.php | 72 +++++++++--------- 5 files changed, 128 insertions(+), 43 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php index b118e52e86fe3..0ec1420522cbf 100644 --- a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php +++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -113,4 +113,11 @@ function searchInDirectory($dir); * @return bool */ function isSupported(); + + /** + * Returns adapter name. + * + * @return string + */ + function getName(); } diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 24aa093024d22..8eddf2823a3d1 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -97,6 +97,14 @@ public function isSupported() && $shell->testCommand('find'); } + /** + * {@inheritdoc} + */ + public function getName() + { + return 'gnu_find'; + } + /** * @param \Symfony\Component\Finder\Command $command * @param string[] $names diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php index 77590b88c1fc9..2dbae76a565f7 100644 --- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -83,4 +83,12 @@ public function isSupported() { return true; } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'php'; + } } diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 120be554449a2..cf96f5468ef65 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -48,7 +48,7 @@ class Finder implements \IteratorAggregate, \Countable private $iterators = array(); private $contains = array(); private $notContains = array(); - private $adapter; + private $adapters = array(); private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); @@ -57,8 +57,10 @@ class Finder implements \IteratorAggregate, \Countable */ public function __construct() { - $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; - $this->adapter = new Adapter\PhpAdapter(); + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + + $this->register(new Adapter\GnuFindAdapter()); + $this->register(new Adapter\PhpAdapter(), -50); } /** @@ -73,20 +75,44 @@ public static function create() return new static(); } + /** + * Registers a finder engine implementation. + * + * @param Adapter\AdapterInterface $adapter An adapter instance + * @param int $priority Highest is selected first + * + * @return \Symfony\Component\Finder\Finder The current Finder instance + */ + public function register(Adapter\AdapterInterface $adapter, $priority = 0) + { + $this->adapters[$adapter->getName()] = array( + 'adapter' => $adapter, + 'priority' => $priority, + 'forced' => false, + ); + + return $this->sortAdapters(); + } + /** * Changes engine implementation. * - * @param Adapter\AdapterInterface $adapter An engine implementation + * @param string $name A registred adapter name * * @return \Symfony\Component\Finder\Finder The current Finder instance + * + * @throws \LogicException If no adapter registered with given name */ - public function using(Adapter\AdapterInterface $adapter) + public function using($name) { - if ($adapter->isSupported()) { - $this->adapter = $adapter; + if (!isset($this->adapters[$name])) { + throw new \LogicException('No adapter registered with name "'.$name.'".'); } - return $this; + array_walk($this->adapters, function(array $adapter) { $adapter['forced'] = false; }); + $this->adapters[$name]['forced'] = true; + + return $this->sortAdapters(); } /** @@ -590,6 +616,38 @@ public function count() } /* + * @return Finder The current Finder instance + */ + private function sortAdapters() + { + uasort($this->adapters, function (array $a, array $b) { + if ($a['forced'] || $b['forced']) { + return $a['forced'] ? -1 : 1; + } + + return $a['priority'] - $b['priority']; + }); + + return $this; + } + + /** + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + * + * @throws \RuntimeException + */ + private function getAdapter() + { + foreach ($this->adapters as $adapter) { + if ($adapter['adapter']->isSupported()) { + return $adapter['adapter']; + } + } + + throw new \RuntimeException('No supported adapter found.'); + } + + /** * @param $dir * * @return \Iterator @@ -605,7 +663,7 @@ private function searchInDirectory($dir) } return $this - ->adapter + ->getAdapter() ->setFollowLinks($this->followLinks) ->setDepths($this->depths) ->setMode($this->mode) diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index d6d794187b748..e9283ae3a4fa7 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -28,7 +28,7 @@ public static function setUpBeforeClass() /** * @dataProvider getAdaptersTestData */ - public function testCreate(Adapter\AdapterInterface $adapter) + public function testCreate($adapter) { $this->assertInstanceOf('Symfony\Component\Finder\Finder', Finder::create()); } @@ -36,7 +36,7 @@ public function testCreate(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testDirectories(Adapter\AdapterInterface $adapter) + public function testDirectories($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->directories()); @@ -52,7 +52,7 @@ public function testDirectories(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testFiles(Adapter\AdapterInterface $adapter) + public function testFiles($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->files()); @@ -68,7 +68,7 @@ public function testFiles(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testDepth(Adapter\AdapterInterface $adapter) + public function testDepth($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->depth('< 1')); @@ -90,7 +90,7 @@ public function testDepth(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testName(Adapter\AdapterInterface $adapter) + public function testName($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->name('*.php')); @@ -105,7 +105,7 @@ public function testName(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testNotName(Adapter\AdapterInterface $adapter) + public function testNotName($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->notName('*.php')); @@ -127,7 +127,7 @@ public function testNotName(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSize(Adapter\AdapterInterface $adapter) + public function testSize($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500')); @@ -137,7 +137,7 @@ public function testSize(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testDate(Adapter\AdapterInterface $adapter) + public function testDate($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->files()->date('until last month')); @@ -147,7 +147,7 @@ public function testDate(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testExclude(Adapter\AdapterInterface $adapter) + public function testExclude($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->exclude('foo')); @@ -157,7 +157,7 @@ public function testExclude(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testIgnoreVCS(Adapter\AdapterInterface $adapter) + public function testIgnoreVCS($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false)); @@ -175,7 +175,7 @@ public function testIgnoreVCS(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testIgnoreDotFiles(Adapter\AdapterInterface $adapter) + public function testIgnoreDotFiles($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false)); @@ -194,7 +194,7 @@ public function testIgnoreDotFiles(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSortByName(Adapter\AdapterInterface $adapter) + public function testSortByName($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByName()); @@ -204,7 +204,7 @@ public function testSortByName(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSortByType(Adapter\AdapterInterface $adapter) + public function testSortByType($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByType()); @@ -214,7 +214,7 @@ public function testSortByType(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSortByAccessedTime(Adapter\AdapterInterface $adapter) + public function testSortByAccessedTime($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByAccessedTime()); @@ -224,7 +224,7 @@ public function testSortByAccessedTime(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSortByChangedTime(Adapter\AdapterInterface $adapter) + public function testSortByChangedTime($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByChangedTime()); @@ -234,7 +234,7 @@ public function testSortByChangedTime(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSortByModifiedTime(Adapter\AdapterInterface $adapter) + public function testSortByModifiedTime($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sortByModifiedTime()); @@ -244,7 +244,7 @@ public function testSortByModifiedTime(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testSort(Adapter\AdapterInterface $adapter) + public function testSort($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); })); @@ -254,7 +254,7 @@ public function testSort(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testFilter(Adapter\AdapterInterface $adapter) + public function testFilter($adapter) { $finder = Finder::create()->using($adapter); $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return preg_match('/test/', $f) > 0; })); @@ -264,7 +264,7 @@ public function testFilter(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testFollowLinks(Adapter\AdapterInterface $adapter) + public function testFollowLinks($adapter) { if ('\\' == DIRECTORY_SEPARATOR) { return; @@ -278,7 +278,7 @@ public function testFollowLinks(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testIn(Adapter\AdapterInterface $adapter) + public function testIn($adapter) { $finder = Finder::create()->using($adapter); try { @@ -297,7 +297,7 @@ public function testIn(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testGetIterator(Adapter\AdapterInterface $adapter) + public function testGetIterator($adapter) { $finder = Finder::create()->using($adapter); try { @@ -333,7 +333,7 @@ public function testGetIterator(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testRelativePath(Adapter\AdapterInterface $adapter) + public function testRelativePath($adapter) { $finder = Finder::create()->using($adapter); @@ -356,7 +356,7 @@ public function testRelativePath(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testRelativePathname(Adapter\AdapterInterface $adapter) + public function testRelativePathname($adapter) { $finder = Finder::create()->using($adapter); @@ -379,7 +379,7 @@ public function testRelativePathname(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testAppendWithAFinder(Adapter\AdapterInterface $adapter) + public function testAppendWithAFinder($adapter) { $finder = Finder::create()->using($adapter); $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); @@ -395,7 +395,7 @@ public function testAppendWithAFinder(Adapter\AdapterInterface $adapter) /** * @dataProvider getAdaptersTestData */ - public function testAppendWithAnArray(Adapter\AdapterInterface $adapter) + public function testAppendWithAnArray($adapter) { $finder = Finder::create()->using($adapter); $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); @@ -467,7 +467,7 @@ protected function toAbsoluteFixtures($files) /** * @dataProvider getContainsTestData */ - public function testContains(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, $expected) + public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expected) { $finder = Finder::create()->using($adapter); $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures') @@ -526,9 +526,10 @@ public function testMultipleLocations(Adapter\AdapterInterface $adapter) public function getAdaptersTestData() { - return array_map(function (Adapter\AdapterInterface $adapter) { - return array($adapter); - }, $this->getValidAdapters()); + return array_map( + function ($adapter) { return array($adapter); }, + $this->getValidAdapterNames() + ); } public function getContainsTestData() @@ -548,7 +549,7 @@ public function getContainsTestData() ); $data = array(); - foreach ($this->getValidAdapters() as $adapter) { + foreach ($this->getValidAdapterNames() as $adapter) { foreach ($tests as $test) { $data[] = array_merge(array($adapter), $test); } @@ -557,11 +558,14 @@ public function getContainsTestData() return $data; } - private function getValidAdapters() + private function getValidAdapterNames() { - return array_filter( - array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()), - function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); } + return array_map( + function (Adapter\AdapterInterface $adapter) { return $adapter->getName(); }, + array_filter( + array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()), + function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); } + ) ); } } From cba5bca68ce15436bd77378ffa4b520a47c43d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Tue, 24 Apr 2012 07:01:49 +0200 Subject: [PATCH 21/37] [Finder] Cleaned code. --- .../Finder/Adapter/GnuFindAdapter.php | 47 +++++++++---------- src/Symfony/Component/Finder/Command.php | 4 +- src/Symfony/Component/Finder/Expr.php | 3 +- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 8eddf2823a3d1..185ae451154cc 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -17,12 +17,25 @@ use Symfony\Component\Finder\Command; /** - * PHP finder engine implementation. + * Shell engine implementation using GNU find command. * * @author Jean-François Simon */ class GnuFindAdapter extends AbstractAdapter { + /** + * @var \Symfony\Component\Finder\Shell + */ + private $shell; + + /** + * Constructor. + */ + public function __construct() + { + $this->shell = new Shell(); + } + /** * {@inheritdoc} */ @@ -50,19 +63,11 @@ public function searchInDirectory($dir) $command->add('-type f'); } - $command->ins('names')->ins('notNames'); - $this->buildNamesCommand($command->get('names'), $this->names); - $this->buildNamesCommand($command->get('notNames'), $this->notNames, true); - - if ($command->get('names')->length() || $command->get('notNames')->length()) { - $command->get('names')->top('-regextype posix-extended'); - } - - $command->ins('sizes'); - $this->buildSizesCommand($command->get('sizes'), $this->sizes); - - $command->ins('dates'); - $this->buildDatesCommand($command->get('dates'), $this->dates); + $command->add('-regextype posix-extended'); + $this->buildNamesCommand($command, $this->names); + $this->buildNamesCommand($command, $this->notNames, true); + $this->buildSizesCommand($command, $this->sizes); + $this->buildDatesCommand($command, $this->dates); $iterator = new Iterator\FilePathsIterator($command->execute(), $dir); @@ -91,10 +96,8 @@ public function searchInDirectory($dir) */ public function isSupported() { - $shell = new Shell(); - - return $shell->getType() !== Shell::TYPE_WINDOWS - && $shell->testCommand('find'); + return $this->shell->getType() !== Shell::TYPE_WINDOWS + && $this->shell->testCommand('find'); } /** @@ -139,10 +142,6 @@ private function buildNamesCommand(Command $command, array $names, $not = false) */ private function buildSizesCommand(Command $command, array $sizes) { - if (0 === count($sizes)) { - return; - } - foreach ($sizes as $i => $size) { $command->add($i > 0 ? '-and' : null); @@ -182,10 +181,6 @@ private function buildSizesCommand(Command $command, array $sizes) */ private function buildDatesCommand(Command $command, array $dates) { - if (0 === count($dates)) { - return; - } - foreach ($dates as $i => $date) { $command->add($i > 0 ? '-and' : null); diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Command.php index 0a14fcb327219..942786d1a0521 100644 --- a/src/Symfony/Component/Finder/Command.php +++ b/src/Symfony/Component/Finder/Command.php @@ -237,7 +237,9 @@ public function execute() public function join() { return implode(' ', array_filter( - array_map(function($bit) { return $bit instanceof Command ? $bit->join() : ($bit ?: null); }, $this->bits), + array_map(function($bit) { + return $bit instanceof Command ? $bit->join() : ($bit ?: null); + }, $this->bits), function($bit) { return null !== $bit; } )); } diff --git a/src/Symfony/Component/Finder/Expr.php b/src/Symfony/Component/Finder/Expr.php index 6fd8af808758e..59cb9dd8b092f 100644 --- a/src/Symfony/Component/Finder/Expr.php +++ b/src/Symfony/Component/Finder/Expr.php @@ -79,8 +79,7 @@ static public function create($expr) */ public function isCaseSensitive() { - return self::TYPE_GLOB === $this->type - || false === strpos($this->flags, 'i'); + return self::TYPE_GLOB === $this->type || false === strpos($this->flags, 'i'); } /** From f131e2707b3dbf6fbb1868549b27b7ad0ac1e978 Mon Sep 17 00:00:00 2001 From: "jeanfrancois.simon" Date: Tue, 24 Apr 2012 13:32:06 +0200 Subject: [PATCH 22/37] [Finder] Added possibility for adapter to fail. --- .../Finder/Adapter/GnuFindAdapter.php | 3 +- .../Exception/AdapterFailureException.php | 46 +++++++++++ .../OperationNotPermitedException.php | 19 +++++ .../ShellCommandFailureException.php | 45 +++++++++++ src/Symfony/Component/Finder/Finder.php | 81 ++++++++++--------- 5 files changed, 154 insertions(+), 40 deletions(-) create mode 100644 src/Symfony/Component/Finder/Exception/AdapterFailureException.php create mode 100644 src/Symfony/Component/Finder/Exception/OperationNotPermitedException.php create mode 100644 src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 185ae451154cc..8ddd1368840ad 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -43,7 +43,7 @@ public function searchInDirectory($dir) { // -noleaf option is required for filesystems // who doesn't follow '.' and '..' convention - $command = Command::create()->add('find ')->arg($dir)->add('-noleaf'); + $command = Command::create()->add('find ')->arg($dir)->add('-noleaf')->add('-regextype posix-extended'); if ($this->followLinks) { $command->add('-follow'); @@ -63,7 +63,6 @@ public function searchInDirectory($dir) $command->add('-type f'); } - $command->add('-regextype posix-extended'); $this->buildNamesCommand($command, $this->names); $this->buildNamesCommand($command, $this->notNames, true); $this->buildSizesCommand($command, $this->sizes); diff --git a/src/Symfony/Component/Finder/Exception/AdapterFailureException.php b/src/Symfony/Component/Finder/Exception/AdapterFailureException.php new file mode 100644 index 0000000000000..4d483dc083a0d --- /dev/null +++ b/src/Symfony/Component/Finder/Exception/AdapterFailureException.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +use Symfony\Component\Finder\Adapter\AdapterInterface; + +/** + * Base exception for all adapter failures. + * + * @author Jean-François Simon + */ +class AdapterFailureException extends \RuntimeException +{ + /** + * @var \Symfony\Component\Finder\Adapter\AdapterInterface + */ + private $adapter; + + /** + * @param \Symfony\Component\Finder\Adapter\AdapterInterface $adapter + * @param string|null $message + * @param \Exception|null $previous + */ + public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null) + { + $this->adapter = $adapter; + parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous); + } + + /** + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } +} diff --git a/src/Symfony/Component/Finder/Exception/OperationNotPermitedException.php b/src/Symfony/Component/Finder/Exception/OperationNotPermitedException.php new file mode 100644 index 0000000000000..3663112259c4d --- /dev/null +++ b/src/Symfony/Component/Finder/Exception/OperationNotPermitedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class OperationNotPermitedException extends AdapterFailureException +{ +} diff --git a/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php b/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php new file mode 100644 index 0000000000000..0d42445bcb4aa --- /dev/null +++ b/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Command; + +/** + * @author Jean-François Simon + */ +class ShellCommandFailureException extends AdapterFailureException +{ + /** + * @var \Symfony\Component\Finder\Command + */ + private $command; + + /** + * @param \Symfony\Component\Finder\Adapter\AdapterInterface $adapter + * @param \Symfony\Component\Finder\Command $command + * @param \Exception|null $previous + */ + public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null) + { + $this->command = $command; + parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous); + } + + /** + * @return \Symfony\Component\Finder\Command + */ + public function getCommand() + { + return $this->command; + } +} diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index cf96f5468ef65..843102ea2b815 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Finder; use Symfony\Component\Finder\Adapter; +use Symfony\Component\Finder\Exception\AdapterFailureException; /** * Finder allows to build rules to find files and directories. @@ -88,31 +89,33 @@ public function register(Adapter\AdapterInterface $adapter, $priority = 0) $this->adapters[$adapter->getName()] = array( 'adapter' => $adapter, 'priority' => $priority, - 'forced' => false, ); return $this->sortAdapters(); } /** - * Changes engine implementation. - * - * @param string $name A registred adapter name + * Removes all adapters registered in the finder. * * @return \Symfony\Component\Finder\Finder The current Finder instance - * - * @throws \LogicException If no adapter registered with given name */ - public function using($name) + public function removeAdapters() { - if (!isset($this->adapters[$name])) { - throw new \LogicException('No adapter registered with name "'.$name.'".'); - } + $this->adapters = array(); - array_walk($this->adapters, function(array $adapter) { $adapter['forced'] = false; }); - $this->adapters[$name]['forced'] = true; + return $this; + } - return $this->sortAdapters(); + /** + * Returns reegistered adapters ordered by priority without extra informations. + * + * @return \Symfony\Component\Finder\Adapter\AdapterInterface[] + */ + public function getAdapters() + { + return array_values(array_map(function(array $adapter) { + return $adapter['adapter']; + }, $this->adapters)); } /** @@ -621,32 +624,12 @@ public function count() private function sortAdapters() { uasort($this->adapters, function (array $a, array $b) { - if ($a['forced'] || $b['forced']) { - return $a['forced'] ? -1 : 1; - } - - return $a['priority'] - $b['priority']; + return $a['priority'] > $b['priority'] ? -1 : 1; }); return $this; } - /** - * @return \Symfony\Component\Finder\Adapter\AdapterInterface - * - * @throws \RuntimeException - */ - private function getAdapter() - { - foreach ($this->adapters as $adapter) { - if ($adapter['adapter']->isSupported()) { - return $adapter['adapter']; - } - } - - throw new \RuntimeException('No supported adapter found.'); - } - /** * @param $dir * @@ -662,8 +645,31 @@ private function searchInDirectory($dir) $this->notNames[] = '/^\..+/'; } - return $this - ->getAdapter() + foreach ($this->adapters as $adapter) { + if (!$adapter['adapter']->isSupported()) { + continue; + } + + try { + return $this + ->buildAdapter($adapter['adapter']) + ->searchInDirectory($dir); + } catch(AdapterFailureException $e) { + continue; + } + } + + throw new \RuntimeException('No supported adapter found.'); + } + + /** + * @param Adapter\AdapterInterface $adapter + * + * @return Adapter\AdapterInterface + */ + private function buildAdapter(Adapter\AdapterInterface $adapter) + { + return $adapter ->setFollowLinks($this->followLinks) ->setDepths($this->depths) ->setMode($this->mode) @@ -675,7 +681,6 @@ private function searchInDirectory($dir) ->setSizes($this->sizes) ->setDates($this->dates) ->setFilters($this->filters) - ->setSort($this->sort) - ->searchInDirectory($dir); + ->setSort($this->sort); } } From 8a3b2de969fb7ac4b19321cd67bb5c175647a605 Mon Sep 17 00:00:00 2001 From: "jeanfrancois.simon" Date: Tue, 24 Apr 2012 13:32:45 +0200 Subject: [PATCH 23/37] [Finder] Added adapters registration tests. --- .../Finder/Tests/FakeAdapter/DummyAdapter.php | 58 ++++++++ .../Tests/FakeAdapter/FailingAdapter.php | 45 ++++++ .../Finder/Tests/FakeAdapter/NamedAdapter.php | 57 +++++++ .../Tests/FakeAdapter/UnsupportedAdapter.php | 44 ++++++ .../Component/Finder/Tests/FinderTest.php | 140 +++++++++++------- 5 files changed, 294 insertions(+), 50 deletions(-) create mode 100644 src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php create mode 100644 src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php create mode 100644 src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php create mode 100644 src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php new file mode 100644 index 0000000000000..f4b4a04ca2e36 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; +use Symfony\Component\Finder\Exception\AdapterFailureException; + +/** + * @author Jean-François Simon + */ +class DummyAdapter extends AbstractAdapter +{ + /** + * @var \Iterator + */ + private $iterator; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator) + { + $this->iterator = $iterator; + } + + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + return $this->iterator; + } + + /** + * {@inheritdoc} + */ + public function isSupported() + { + return true; + } + + /** + * {@inheritdoc} + */ + function getName() + { + return 'yes'; + } +} diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php new file mode 100644 index 0000000000000..00fc022fdf766 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; +use Symfony\Component\Finder\Exception\AdapterFailureException; + +/** + * @author Jean-François Simon + */ +class FailingAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + throw new AdapterFailureException($this); + } + + /** + * {@inheritdoc} + */ + public function isSupported() + { + return true; + } + + /** + * {@inheritdoc} + */ + function getName() + { + return 'failing'; + } +} diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php new file mode 100644 index 0000000000000..2321195e94b0c --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; + +/** + * @author Jean-François Simon + */ +class NamedAdapter extends AbstractAdapter +{ + /** + * @var string + */ + private $name; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + return new \ArrayIterator(array()); + } + + /** + * {@inheritdoc} + */ + public function isSupported() + { + return true; + } + + /** + * {@inheritdoc} + */ + function getName() + { + return $this->name; + } +} diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php new file mode 100644 index 0000000000000..d02a13e0328bd --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; + +/** + * @author Jean-François Simon + */ +class UnsupportedAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + return new \ArrayIterator(array()); + } + + /** + * {@inheritdoc} + */ + public function isSupported() + { + return false; + } + + /** + * {@inheritdoc} + */ + function getName() + { + return 'unsupported'; + } +} diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index e9283ae3a4fa7..2fc7d380ddcf1 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Adapter; +use Symfony\Component\Finder\Tests\FakeAdapter; class FinderTest extends Iterator\RealIteratorTestCase { @@ -38,11 +39,11 @@ public function testCreate($adapter) */ public function testDirectories($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->directories()); $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->directories(); $finder->files(); $finder->directories(); @@ -54,11 +55,11 @@ public function testDirectories($adapter) */ public function testFiles($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->files()); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->files(); $finder->directories(); $finder->files(); @@ -70,19 +71,19 @@ public function testFiles($adapter) */ public function testDepth($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->depth('< 1')); $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->depth('<= 0')); $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->depth('>= 1')); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->depth('< 1')->depth('>= 1'); $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); } @@ -92,11 +93,11 @@ public function testDepth($adapter) */ public function testName($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->name('*.php')); $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->name('test.ph*'); $finder->name('test.py'); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); @@ -107,16 +108,16 @@ public function testName($adapter) */ public function testNotName($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->notName('*.php')); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->notName('*.php'); $finder->notName('*.py'); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->name('test.ph*'); $finder->name('test.py'); $finder->notName('*.php'); @@ -129,7 +130,7 @@ public function testNotName($adapter) */ public function testSize($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500')); $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); } @@ -139,7 +140,7 @@ public function testSize($adapter) */ public function testDate($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->files()->date('until last month')); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php')), $finder->in(self::$tmpDir)->getIterator()); } @@ -149,7 +150,7 @@ public function testDate($adapter) */ public function testExclude($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->exclude('foo')); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } @@ -159,15 +160,15 @@ public function testExclude($adapter) */ public function testIgnoreVCS($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false)); $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false); $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false)); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar')), $finder->in(self::$tmpDir)->getIterator()); } @@ -177,7 +178,7 @@ public function testIgnoreVCS($adapter) */ public function testIgnoreDotFiles($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false)); $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); @@ -185,7 +186,7 @@ public function testIgnoreDotFiles($adapter) $finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false); $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false)); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); @@ -196,7 +197,7 @@ public function testIgnoreDotFiles($adapter) */ public function testSortByName($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->sortByName()); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } @@ -206,7 +207,7 @@ public function testSortByName($adapter) */ public function testSortByType($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->sortByType()); $this->assertIterator($this->toAbsolute(array('foo', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); } @@ -216,7 +217,7 @@ public function testSortByType($adapter) */ public function testSortByAccessedTime($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->sortByAccessedTime()); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo')), $finder->in(self::$tmpDir)->getIterator()); } @@ -226,7 +227,7 @@ public function testSortByAccessedTime($adapter) */ public function testSortByChangedTime($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->sortByChangedTime()); $this->assertIterator($this->toAbsolute(array('toto', 'test.py', 'test.php', 'foo/bar.tmp', 'foo')), $finder->in(self::$tmpDir)->getIterator()); } @@ -236,7 +237,7 @@ public function testSortByChangedTime($adapter) */ public function testSortByModifiedTime($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->sortByModifiedTime()); $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo')), $finder->in(self::$tmpDir)->getIterator()); } @@ -246,7 +247,7 @@ public function testSortByModifiedTime($adapter) */ public function testSort($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); })); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } @@ -256,7 +257,7 @@ public function testSort($adapter) */ public function testFilter($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return preg_match('/test/', $f) > 0; })); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); } @@ -270,7 +271,7 @@ public function testFollowLinks($adapter) return; } - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertSame($finder, $finder->followLinks()); $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); } @@ -280,7 +281,7 @@ public function testFollowLinks($adapter) */ public function testIn($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); try { $finder->in('foobar'); $this->fail('->in() throws a \InvalidArgumentException if the directory does not exist'); @@ -288,7 +289,7 @@ public function testIn($adapter) $this->assertInstanceOf('InvalidArgumentException', $e, '->in() throws a \InvalidArgumentException if the directory does not exist'); } - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator(); $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'bootstrap.php', __DIR__.DIRECTORY_SEPARATOR.'ExprTest.php'), $iterator); @@ -299,7 +300,7 @@ public function testIn($adapter) */ public function testGetIterator($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); try { $finder->getIterator(); $this->fail('->getIterator() throws a \LogicException if the in() method has not been called'); @@ -307,7 +308,7 @@ public function testGetIterator($adapter) $this->assertInstanceOf('LogicException', $e, '->getIterator() throws a \LogicException if the in() method has not been called'); } - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $dirs = array(); foreach ($finder->directories()->in(self::$tmpDir) as $dir) { $dirs[] = (string) $dir; @@ -320,10 +321,10 @@ public function testGetIterator($adapter) $this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface'); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $this->assertEquals(2, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface'); - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $a = iterator_to_array($finder->directories()->in(self::$tmpDir)); $a = array_values(array_map(function ($a) { return (string) $a; }, $a)); sort($a); @@ -335,7 +336,7 @@ public function testGetIterator($adapter) */ public function testRelativePath($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->in(self::$tmpDir); @@ -358,7 +359,7 @@ public function testRelativePath($adapter) */ public function testRelativePathname($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->in(self::$tmpDir)->sortByName(); @@ -381,10 +382,10 @@ public function testRelativePathname($adapter) */ public function testAppendWithAFinder($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); - $finder1 = Finder::create()->using($adapter); + $finder1 = $this->buildFinder($adapter); $finder1->directories()->in(self::$tmpDir); $finder->append($finder1); @@ -397,7 +398,7 @@ public function testAppendWithAFinder($adapter) */ public function testAppendWithAnArray($adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); $finder->append($this->toAbsolute(array('foo', 'toto'))); @@ -469,7 +470,7 @@ protected function toAbsoluteFixtures($files) */ public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expected) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures') ->name('*.txt')->sortByName() ->contains($matchPatterns) @@ -524,11 +525,46 @@ public function testMultipleLocations(Adapter\AdapterInterface $adapter) $this->assertEquals(1, count($finder)); } + public function testAdaptersOrdering() + { + $finder = Finder::create() + ->removeAdapters() + ->register(new FakeAdapter\NamedAdapter('a'), 0) + ->register(new FakeAdapter\NamedAdapter('b'), -50) + ->register(new FakeAdapter\NamedAdapter('c'), 50) + ->register(new FakeAdapter\NamedAdapter('d'), -25) + ->register(new FakeAdapter\NamedAdapter('e'), 25); + + $this->assertEquals( + array('c', 'e', 'a', 'd', 'b'), + array_map(function(Adapter\AdapterInterface $adapter) { + return $adapter->getName(); + }, $finder->getAdapters()) + ); + } + + public function testAdaptersChaining() + { + $iterator = new \ArrayIterator(array()); + $filenames = $this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')); + foreach ($filenames as $file) { + $iterator->append(new \Symfony\Component\Finder\SplFileInfo($file, null, null)); + } + + $finder = Finder::create() + ->removeAdapters() + ->register(new FakeAdapter\UnsupportedAdapter(), 3) + ->register(new FakeAdapter\FailingAdapter(), 2) + ->register(new FakeAdapter\DummyAdapter($iterator), 1); + + $this->assertIterator($filenames, $finder->in(sys_get_temp_dir())->getIterator()); + } + public function getAdaptersTestData() { return array_map( function ($adapter) { return array($adapter); }, - $this->getValidAdapterNames() + $this->getValidAdapters() ); } @@ -549,7 +585,7 @@ public function getContainsTestData() ); $data = array(); - foreach ($this->getValidAdapterNames() as $adapter) { + foreach ($this->getValidAdapters() as $adapter) { foreach ($tests as $test) { $data[] = array_merge(array($adapter), $test); } @@ -558,14 +594,18 @@ public function getContainsTestData() return $data; } - private function getValidAdapterNames() + private function buildFinder(Adapter\AdapterInterface $adapter) { - return array_map( - function (Adapter\AdapterInterface $adapter) { return $adapter->getName(); }, - array_filter( - array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()), - function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); } - ) + return Finder::create() + ->removeAdapters() + ->register($adapter); + } + + private function getValidAdapters() + { + return array_filter( + array(new Adapter\GnuFindAdapter(), new Adapter\PhpAdapter()), + function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); } ); } } From eefba16f2388118b0327fa18e217e7bc800a7c2e Mon Sep 17 00:00:00 2001 From: "jeanfrancois.simon" Date: Thu, 10 May 2012 11:48:48 +0200 Subject: [PATCH 24/37] [Finder] Added grep support for gnu find adapter. --- .../Finder/Adapter/GnuFindAdapter.php | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 8ddd1368840ad..a2f51e4245eb9 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -44,13 +44,11 @@ public function searchInDirectory($dir) // -noleaf option is required for filesystems // who doesn't follow '.' and '..' convention $command = Command::create()->add('find ')->arg($dir)->add('-noleaf')->add('-regextype posix-extended'); - if ($this->followLinks) { $command->add('-follow'); } $command->add('-mindepth')->add($this->minDepth+1); - // warning! INF < INF => true ; INF == INF => false ; INF === INF => true // https://bugs.php.net/bug.php?id=9118 if (INF !== $this->maxDepth) { @@ -68,13 +66,26 @@ public function searchInDirectory($dir) $this->buildSizesCommand($command, $this->sizes); $this->buildDatesCommand($command, $this->dates); - $iterator = new Iterator\FilePathsIterator($command->execute(), $dir); + if ($useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs')) { + $this->buildContainsOptions($command, $this->contains); + $this->buildContainsOptions($command, $this->notContains, true); + + echo $command; + } + + if ($this->shell->testCommand('uniq')) { + $paths = $command->add('| uniq')->execute(); + } else { + $paths = array_unique($command->execute()); + } + + $iterator = new Iterator\FilePathsIterator($paths, $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); } - if ($this->contains || $this->notContains) { + if (!$useGrep && ($this->contains || $this->notContains)) { $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } @@ -220,4 +231,25 @@ private function buildDatesCommand(Command $command, array $dates) $command->add('-mmin '.$mins); } } + + /** + * @param \Symfony\Component\Finder\Command $command + * @param array $contains + * @param bool $not + */ + private function buildContainsOptions(Command $command, array $contains, $not = false) + { + foreach ($contains as $contain) { + $expr = Expr::create($contain); + $regex = $expr->isRegex() + ? $expr->getBody() + : trim(Expr::create($expr->getRegex(false, false))->getBody(), '^$'); + + $command + ->add('| xargs -r grep -I') + ->add($expr->isCaseSensitive() ? null : '-i') + ->add($not ? '-L' : '-l') + ->add('-Ee')->arg($regex); + } + } } From 924561cf7cb405903a4e9465190c744313586630 Mon Sep 17 00:00:00 2001 From: jfsimon Date: Wed, 4 Jul 2012 16:52:38 +0200 Subject: [PATCH 25/37] [Finder] Fixed typo & removed debug output. --- src/Symfony/Component/Finder/Adapter/AdapterInterface.php | 2 +- src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php | 2 -- src/Symfony/Component/Finder/Command.php | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php index 0ec1420522cbf..246a26cb06ad0 100644 --- a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php +++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -108,7 +108,7 @@ function setSort($sort); function searchInDirectory($dir); /** - * Tests adapter support for current plateform. + * Tests adapter support for current platform. * * @return bool */ diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index a2f51e4245eb9..0f56818bbebe9 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -69,8 +69,6 @@ public function searchInDirectory($dir) if ($useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs')) { $this->buildContainsOptions($command, $this->contains); $this->buildContainsOptions($command, $this->notContains, true); - - echo $command; } if ($this->shell->testCommand('uniq')) { diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Command.php index 942786d1a0521..317f9c3caf8f3 100644 --- a/src/Symfony/Component/Finder/Command.php +++ b/src/Symfony/Component/Finder/Command.php @@ -196,7 +196,6 @@ public function get($label) public function end() { if (null === $this->parent) { - var_dump($this); throw new \RuntimeException('Calling end on root command dont makes sense.'); } From f924d85398f5ea3daa5d278aacb780435e0e8c94 Mon Sep 17 00:00:00 2001 From: jfsimon Date: Tue, 10 Jul 2012 10:16:09 +0200 Subject: [PATCH 26/37] [Finder] Added native sorting support to gnu find adapter. --- .../Finder/Adapter/AbstractAdapter.php | 2 +- .../Finder/Adapter/GnuFindAdapter.php | 64 +++++++++++++------ src/Symfony/Component/Finder/Command.php | 2 +- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php index ca416c7ee81be..8958571bdff7b 100644 --- a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php @@ -30,7 +30,7 @@ abstract class AbstractAdapter implements AdapterInterface protected $sizes = array(); protected $dates = array(); protected $filters = array(); - protected $sort = array(); + protected $sort = false; /** * {@inheritdoc} diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 0f56818bbebe9..81aaa4282d20f 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -15,6 +15,7 @@ use Symfony\Component\Finder\Shell; use Symfony\Component\Finder\Expr; use Symfony\Component\Finder\Command; +use Symfony\Component\Finder\Iterator\SortableIterator; /** * Shell engine implementation using GNU find command. @@ -41,34 +42,45 @@ public function __construct() */ public function searchInDirectory($dir) { - // -noleaf option is required for filesystems - // who doesn't follow '.' and '..' convention - $command = Command::create()->add('find ')->arg($dir)->add('-noleaf')->add('-regextype posix-extended'); + $command = Command::create(); + + $find = $command + ->ins('find') + ->add('find ') + ->arg($dir) + ->add('-noleaf') // -noleaf option is required for filesystems who doesn't follow '.' and '..' convention + ->add('-regextype posix-extended'); + if ($this->followLinks) { - $command->add('-follow'); + $find->add('-follow'); } - $command->add('-mindepth')->add($this->minDepth+1); + $find->add('-mindepth')->add($this->minDepth+1); // warning! INF < INF => true ; INF == INF => false ; INF === INF => true // https://bugs.php.net/bug.php?id=9118 if (INF !== $this->maxDepth) { - $command->add('-maxdepth')->add($this->maxDepth+1); + $find->add('-maxdepth')->add($this->maxDepth+1); } if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { - $command->add('-type d'); + $find->add('-type d'); } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { - $command->add('-type f'); + $find->add('-type f'); } - $this->buildNamesCommand($command, $this->names); - $this->buildNamesCommand($command, $this->notNames, true); - $this->buildSizesCommand($command, $this->sizes); - $this->buildDatesCommand($command, $this->dates); + $this->buildNamesCommand($find, $this->names); + $this->buildNamesCommand($find, $this->notNames, true); + $this->buildSizesCommand($find, $this->sizes); + $this->buildDatesCommand($find, $this->dates); - if ($useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs')) { - $this->buildContainsOptions($command, $this->contains); - $this->buildContainsOptions($command, $this->notContains, true); + if (($useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs')) && ($this->contains || $this->notContains)) { + $grep = $command->ins('grep'); + $this->buildContainsCommand($grep, $this->contains); + $this->buildContainsCommand($grep, $this->notContains, true); + } + + if ($useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('awk')) { + $this->buildSortCommand($command, $this->sort); } if ($this->shell->testCommand('uniq')) { @@ -77,7 +89,7 @@ public function searchInDirectory($dir) $paths = array_unique($command->execute()); } - $iterator = new Iterator\FilePathsIterator($paths, $dir); + $iterator = new Iterator\FilePathsIterator($command->execute(), $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); @@ -91,7 +103,7 @@ public function searchInDirectory($dir) $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); } - if ($this->sort) { + if (!$useSort && $this->sort) { $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); $iterator = $iteratorAggregate->getIterator(); } @@ -235,7 +247,7 @@ private function buildDatesCommand(Command $command, array $dates) * @param array $contains * @param bool $not */ - private function buildContainsOptions(Command $command, array $contains, $not = false) + private function buildContainsCommand(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { $expr = Expr::create($contain); @@ -250,4 +262,20 @@ private function buildContainsOptions(Command $command, array $contains, $not = ->add('-Ee')->arg($regex); } } + + private function buildSortCommand(Command $command, $sort) + { + switch ($sort) { + case SortableIterator::SORT_BY_NAME: $format = null; break; + case SortableIterator::SORT_BY_TYPE: $format = '%y'; break; + case SortableIterator::SORT_BY_ACCESSED_TIME: $format = '%A@'; break; + case SortableIterator::SORT_BY_CHANGED_TIME: $format = '%C@'; break; + case SortableIterator::SORT_BY_MODIFIED_TIME: $format = '%T@'; break; + default: throw new \InvalidArgumentException('Unknown sort options: '.$sort.'.'); + } + + $command->get('find')->add('-printf')->arg($format.' %h/%f\\n'); + $command->ins('sort')->add('| sort'); + $command->ins('awk')->add('| awk')->arg('{ print $'.(null === $format ? '1' : '2').' }'); + } } diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Command.php index 317f9c3caf8f3..9d8d8d4c4d0c5 100644 --- a/src/Symfony/Component/Finder/Command.php +++ b/src/Symfony/Component/Finder/Command.php @@ -167,7 +167,7 @@ public function ins($label) $this->bits[] = self::create($this); $this->labels[$label] = count($this->bits)-1; - return $this; + return $this->bits[$this->labels[$label]]; } /** From 4229962eb621922c7792408143529b28af9ed44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Tue, 31 Jul 2012 09:18:54 +0200 Subject: [PATCH 27/37] [Finder] Refactored expression (regex/glob) handling. --- .../Finder/Adapter/GnuFindAdapter.php | 14 +- src/Symfony/Component/Finder/Expr.php | 209 -------------- .../Finder/Expression/Expression.php | 123 ++++++++ .../Component/Finder/Expression/Glob.php | 126 ++++++++ .../Component/Finder/Expression/Regex.php | 271 ++++++++++++++++++ .../Finder/Expression/ValueInterface.php | 46 +++ .../Iterator/FilenameFilterIterator.php | 6 +- .../Iterator/MultiplePcreFilterIterator.php | 4 +- .../Component/Finder/Tests/ExprTest.php | 98 ------- .../Tests/Expression/ExpressionTest.php | 68 +++++ .../Component/Finder/Tests/FinderTest.php | 2 +- 11 files changed, 646 insertions(+), 321 deletions(-) delete mode 100644 src/Symfony/Component/Finder/Expr.php create mode 100644 src/Symfony/Component/Finder/Expression/Expression.php create mode 100644 src/Symfony/Component/Finder/Expression/Glob.php create mode 100644 src/Symfony/Component/Finder/Expression/Regex.php create mode 100644 src/Symfony/Component/Finder/Expression/ValueInterface.php delete mode 100644 src/Symfony/Component/Finder/Tests/ExprTest.php create mode 100644 src/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 81aaa4282d20f..59ba3cb12ef3c 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -13,7 +13,7 @@ use Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Shell; -use Symfony\Component\Finder\Expr; +use Symfony\Component\Finder\Expression\Expression; use Symfony\Component\Finder\Command; use Symfony\Component\Finder\Iterator\SortableIterator; @@ -142,7 +142,7 @@ private function buildNamesCommand(Command $command, array $names, $not = false) $command->add($not ? '-not' : null)->cmd('('); foreach ($names as $i => $name) { - $expr = Expr::create($name); + $expr = Expression::create($name); $command ->add($i > 0 ? '-or' : null) @@ -150,7 +150,7 @@ private function buildNamesCommand(Command $command, array $names, $not = false) ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') : ($expr->isCaseSensitive() ? '-name' : '-iname') ) - ->arg($expr->getBody()); + ->arg($expr->render()); } $command->cmd(')'); @@ -250,16 +250,14 @@ private function buildDatesCommand(Command $command, array $dates) private function buildContainsCommand(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { - $expr = Expr::create($contain); - $regex = $expr->isRegex() - ? $expr->getBody() - : trim(Expr::create($expr->getRegex(false, false))->getBody(), '^$'); + $expr = Expression::create($contain); + $pattern = $expr->isRegex() ? $expr->setStartFlag(false)->setEndFlag(false)->renderPattern() : $expr->render(); $command ->add('| xargs -r grep -I') ->add($expr->isCaseSensitive() ? null : '-i') ->add($not ? '-L' : '-l') - ->add('-Ee')->arg($regex); + ->add('-Ee')->arg($pattern); } } diff --git a/src/Symfony/Component/Finder/Expr.php b/src/Symfony/Component/Finder/Expr.php deleted file mode 100644 index 59cb9dd8b092f..0000000000000 --- a/src/Symfony/Component/Finder/Expr.php +++ /dev/null @@ -1,209 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder; - -/** - * @author Jean-François Simon - */ -class Expr -{ - const TYPE_REGEX = 1; - const TYPE_GLOB = 2; - - /** - * @var string - */ - private $expr; - - /** - * @var int - */ - private $type; - - /** - * @var string - */ - private $flags; - - /** - * @var string - */ - private $body; - - /** - * @param string $expr - */ - public function __construct($expr) - { - $this->expr = $expr; - - if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $this->expr, $m)) { - $start = substr($m[1], 0, 1); - $end = substr($m[1], -1); - - if (($start === $end && !preg_match('/[[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { - $this->type = self::TYPE_REGEX; - $this->body = substr($m[1], 1, -1); - $this->flags = $m[2]; - - return; - } - } - - $this->flags = ''; - $this->body = $expr; - $this->type = self::TYPE_GLOB; - } - - /** - * @param string $expr - * - * @return Expr - */ - static public function create($expr) - { - return new self($expr); - } - - /** - * @return bool - */ - public function isCaseSensitive() - { - return self::TYPE_GLOB === $this->type || false === strpos($this->flags, 'i'); - } - - /** - * @return bool - */ - public function isRegex() - { - return self::TYPE_REGEX === $this->type; - } - - /** - * @return bool - */ - public function isGlob() - { - return self::TYPE_GLOB === $this->type; - } - - /** - * @return mixed - */ - public function getBody() - { - return $this->body; - } - - /** - * @return mixed - */ - public function getExpr() - { - return $this->expr; - } - - /** - * @return string - */ - public function getFlags() - { - return $this->flags; - } - - /** - * @return int - */ - public function getType() - { - return $this->type; - } - - /** - * @param bool $strictLeadingDot - * @param bool $strictWildcardSlash - * - * @return string - */ - public function getRegex($strictLeadingDot = true, $strictWildcardSlash = true) - { - return self::TYPE_REGEX === $this->type - ? $this->expr - : $this->globToRegex($this->expr, $strictLeadingDot, $strictWildcardSlash); - } - - /** - * @param string $glob - * @param bool $strictLeadingDot - * @param bool $strictWildcardSlash - * - * @return string - */ - private function globToRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true) - { - $firstByte = true; - $escaping = false; - $inCurlies = 0; - $regex = ''; - $sizeGlob = strlen($glob); - for ($i = 0; $i < $sizeGlob; $i++) { - $car = $glob[$i]; - if ($firstByte) { - if ($strictLeadingDot && '.' !== $car) { - $regex .= '(?=[^\.])'; - } - - $firstByte = false; - } - - if ('/' === $car) { - $firstByte = true; - } - - if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { - $regex .= "\\$car"; - } elseif ('*' === $car) { - $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); - } elseif ('?' === $car) { - $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); - } elseif ('{' === $car) { - $regex .= $escaping ? '\\{' : '('; - if (!$escaping) { - ++$inCurlies; - } - } elseif ('}' === $car && $inCurlies) { - $regex .= $escaping ? '}' : ')'; - if (!$escaping) { - --$inCurlies; - } - } elseif (',' === $car && $inCurlies) { - $regex .= $escaping ? ',' : '|'; - } elseif ('\\' === $car) { - if ($escaping) { - $regex .= '\\\\'; - $escaping = false; - } else { - $escaping = true; - } - - continue; - } else { - $regex .= $car; - } - $escaping = false; - } - - return '#^'.$regex.'$#'; - } -} diff --git a/src/Symfony/Component/Finder/Expression/Expression.php b/src/Symfony/Component/Finder/Expression/Expression.php new file mode 100644 index 0000000000000..5fa014d5b9459 --- /dev/null +++ b/src/Symfony/Component/Finder/Expression/Expression.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Expression implements ValueInterface +{ + const TYPE_REGEX = 1; + const TYPE_GLOB = 2; + + /** + * @var ValueInterface + */ + private $value; + + /** + * @param string $expr + * + * @return Expression + */ + public static function create($expr) + { + return new self($expr); + } + + /** + * @param string $expr + */ + public function __construct($expr) + { + try { + $this->value = Regex::create($expr); + } catch(\InvalidArgumentException $e) { + $this->value = new Glob($expr); + } + } + + /** + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, array $arguments) + { + return call_user_func_array(array($this->value, $method), $arguments); + } + + /** + * {@inheritdoc} + */ + public function render() + { + return $this->value->render(); + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return $this->value->renderPattern(); + } + + /** + * @return bool + */ + public function isCaseSensitive() + { + return $this->value->isCaseSensitive(); + } + + /** + * @return int + */ + public function getType() + { + return $this->value->getType(); + } + + /** + * @return bool + */ + public function isRegex() + { + return self::TYPE_REGEX === $this->value->getType(); + } + + /** + * @return bool + */ + public function isGlob() + { + return self::TYPE_GLOB === $this->value->getType(); + } + + /** + * @return Regex + */ + public function getRegex() + { + return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex(); + } +} diff --git a/src/Symfony/Component/Finder/Expression/Glob.php b/src/Symfony/Component/Finder/Expression/Glob.php new file mode 100644 index 0000000000000..5e7b2d01127fa --- /dev/null +++ b/src/Symfony/Component/Finder/Expression/Glob.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Glob implements ValueInterface +{ + /** + * @var string + */ + private $pattern; + + /** + * @param string $pattern + */ + public function __construct($pattern) + { + $this->pattern = $pattern; + } + + /** + * {@inheritdoc} + */ + public function render() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return Expression::TYPE_GLOB; + } + + /** + * {@inheritdoc} + */ + public function isCaseSensitive() + { + return true; + } + + /** + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * + * @return Regex + */ + public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true) + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = strlen($this->pattern); + for ($i = 0; $i < $sizeGlob; $i++) { + $car = $this->pattern[$i]; + if ($firstByte) { + if ($strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = false; + } + + if ('/' === $car) { + $firstByte = true; + } + + if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return new Regex('^'.$regex.'$'); + } +} diff --git a/src/Symfony/Component/Finder/Expression/Regex.php b/src/Symfony/Component/Finder/Expression/Regex.php new file mode 100644 index 0000000000000..6394c7a57c2a2 --- /dev/null +++ b/src/Symfony/Component/Finder/Expression/Regex.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Regex implements ValueInterface +{ + const START_FLAG = '^'; + const END_FLAG = '$'; + const BOUNDARY = '~'; + const JOKER = '.*'; + + /** + * @var string + */ + private $pattern; + + /** + * @var array + */ + private $options; + + /** + * @var bool + */ + private $startFlag; + + /** + * @var bool + */ + private $endFlag; + + /** + * @var bool + */ + private $startJoker; + + /** + * @var bool + */ + private $endJoker; + + /** + * @param string $expr + * + * @return Regex + * + * @throws \InvalidArgumentException + */ + public static function create($expr) + { + if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if (($start === $end && !preg_match('/[[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { + return new self(substr($m[1], 1, -1), $m[2]); + } + } + + throw new \InvalidArgumentException('Given expression is not a regex.'); + } + + /** + * @param string $pattern + * @param string $options + */ + public function __construct($pattern, $options = '') + { + $this->parsePattern($pattern); + $this->options = $options; + } + + /** + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * {@inheritdoc} + */ + public function render() + { + return self::BOUNDARY + .$this->renderPattern() + .self::BOUNDARY + .$this->options; + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return ($this->startFlag ? self::START_FLAG : '') + .($this->startFlag ? self::JOKER : '') + .$this->pattern + .($this->endJoker ? self::JOKER : '') + .($this->endFlag ? self::END_FLAG : ''); + } + + /** + * {@inheritdoc} + */ + public function isCaseSensitive() + { + return !$this->hasOption('i'); + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return Expression::TYPE_REGEX; + } + + /** + * @param string $option + * + * @return bool + */ + public function hasOption($option) + { + return false !== strpos($this->options, $option); + } + + /** + * @param string $option + * + * @return Regex + */ + public function addOption($option) + { + if (!$this->hasOption($option)) { + $this->options.= $option; + } + + return $this; + } + + /** + * @param string $option + * + * @return Regex + */ + public function removeOption($option) + { + $this->options = str_replace($option, '', $this->options); + + return $this; + } + + /** + * @param bool $startFlag + * + * @return Regex + */ + public function setStartFlag($startFlag) + { + $this->startFlag = $startFlag; + + return $this; + } + + /** + * @return bool + */ + public function getStartFlag() + { + return $this->startFlag; + } + + /** + * @param bool $endFlag + * + * @return Regex + */ + public function setEndFlag($endFlag) + { + $this->endFlag = (bool) $endFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasEndFlag() + { + return $this->endFlag; + } + + /** + * @param bool $startJoker + * + * @return Regex + */ + public function setStartJoker($startJoker) + { + $this->startJoker = $startJoker; + + return $this; + } + + /** + * @return bool + */ + public function getStartJoker() + { + return $this->startJoker; + } + + /** + * @param bool $endJoker + * + * @return Regex + */ + public function setEndJoker($endJoker) + { + $this->endJoker = (bool) $endJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasEndJoker() + { + return $this->endJoker; + } + + /** + * @param string $pattern + */ + private function parsePattern($pattern) + { + if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) { + $pattern = substr($pattern, 1); + } + + if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) { + $pattern = substr($pattern, 2); + } + + if ($this->endFlag = self::END_FLAG === substr($pattern, 0, -1)) { + $pattern = substr($pattern, 0, -1); + } + + if ($this->endJoker = self::JOKER === substr($pattern, 0, -2)) { + $pattern = substr($pattern, 2); + } + + $this->pattern = $pattern; + } +} diff --git a/src/Symfony/Component/Finder/Expression/ValueInterface.php b/src/Symfony/Component/Finder/Expression/ValueInterface.php new file mode 100644 index 0000000000000..aca28f4bad894 --- /dev/null +++ b/src/Symfony/Component/Finder/Expression/ValueInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +interface ValueInterface +{ + /** + * Renders string representation of expression. + * + * @return string + */ + function render(); + + /** + * Renders string representation of pattern. + * + * @return string + */ + function renderPattern(); + + /** + * Returns value case sensitivity. + * + * @return bool + */ + function isCaseSensitive(); + + /** + * Returns expression type. + * + * @return int + */ + function getType(); +} diff --git a/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php b/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php index b858446f7cd43..3c0f3aa70ef78 100644 --- a/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\Expr; +use Symfony\Component\Finder\Expression\Expression; /** * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). @@ -55,7 +55,7 @@ public function accept() * Converts glob to regexp. * * PCRE patterns are left unchanged. - * Glob strings are transformed with Expr::globToRegex(). + * Glob strings are transformed with Glob::toRegex(). * * @param string $str Pattern: glob or regexp * @@ -63,6 +63,6 @@ public function accept() */ protected function toRegex($str) { - return Expr::create($str)->getRegex(); + return Expression::create($str)->getRegex()->render(); } } diff --git a/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php b/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php index 8b008485cb873..3a9dd55582859 100644 --- a/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\Expr; +use Symfony\Component\Finder\Expression\Expression; /** * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). @@ -54,7 +54,7 @@ public function __construct(\Iterator $iterator, array $matchPatterns, array $no */ protected function isRegex($str) { - return Expr::create($str)->isRegex(); + return Expression::create($str)->isRegex(); } /** diff --git a/src/Symfony/Component/Finder/Tests/ExprTest.php b/src/Symfony/Component/Finder/Tests/ExprTest.php deleted file mode 100644 index bc72d81ebab0c..0000000000000 --- a/src/Symfony/Component/Finder/Tests/ExprTest.php +++ /dev/null @@ -1,98 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Tests; - -use Symfony\Component\Finder\Expr; - -class ExprTest extends \PHPUnit_Framework_TestCase -{ - /** - * @dataProvider getGlobToRegexData - */ - public function testGlobToRegex($glob, $match, $noMatch) - { - foreach ($match as $m) { - $this->assertRegExp(Expr::create($glob)->getRegex(), $m, '::globToRegex() converts a glob to a regexp'); - } - - foreach ($noMatch as $m) { - $this->assertNotRegExp(Expr::create($glob)->getRegex(), $m, '::globToRegex() converts a glob to a regexp'); - } - } - - /** - * @dataProvider getTypeGuesserData - */ - public function testTypeGuesser($expr, $type) - { - $this->assertEquals($type, Expr::create($expr)->getType()); - } - - /** - * @dataProvider getCaseSensitiveData - */ - public function testCaseSensitive($expr, $isCaseSensitive) - { - $this->assertEquals($isCaseSensitive, Expr::create($expr)->isCaseSensitive()); - } - - /** - * @dataProvider getRegexBodyData - */ - public function testRegexBody($expr, $body) - { - $this->assertEquals($body, Expr::create($expr)->getBody()); - } - - public function getGlobToRegexData() - { - return array( - array('', array(''), array('f', '/')), - array('*', array('foo'), array('foo/', '/foo')), - array('foo.*', array('foo.php', 'foo.a', 'foo.'), array('fooo.php', 'foo.php/foo')), - array('fo?', array('foo', 'fot'), array('fooo', 'ffoo', 'fo/')), - array('fo{o,t}', array('foo', 'fot'), array('fob', 'fo/')), - array('foo(bar|foo)', array('foo(bar|foo)'), array('foobar', 'foofoo')), - array('foo,bar', array('foo,bar'), array('foo', 'bar')), - array('fo{o,\\,}', array('foo', 'fo,'), array()), - array('fo{o,\\\\}', array('foo', 'fo\\'), array()), - array('/foo', array('/foo'), array('foo')), - ); - } - - public function getTypeGuesserData() - { - return array( - array('{foo}', Expr::TYPE_REGEX), - array('/foo/', Expr::TYPE_REGEX), - array('foo', Expr::TYPE_GLOB), - array('foo*', Expr::TYPE_GLOB), - ); - } - - public function getCaseSensitiveData() - { - return array( - array('{foo}m', true), - array('/foo/i', false), - array('foo*', true), - ); - } - - public function getRegexBodyData() - { - return array( - array('{foo}m', 'foo'), - array('/foo/i', 'foo'), - ); - } -} diff --git a/src/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php b/src/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php new file mode 100644 index 0000000000000..c907d6a8d4d47 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Expression\Expression; + +class ExpressionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTypeGuesserData + */ + public function testTypeGuesser($expr, $type) + { + $this->assertEquals($type, Expression::create($expr)->getType()); + } + + /** + * @dataProvider getCaseSensitiveData + */ + public function testCaseSensitive($expr, $isCaseSensitive) + { + $this->assertEquals($isCaseSensitive, Expression::create($expr)->isCaseSensitive()); + } + + /** + * @dataProvider getRegexRenderingData + */ + public function testRegexRendering($expr, $body) + { + $this->assertEquals($body, Expression::create($expr)->renderPattern()); + } + + public function getTypeGuesserData() + { + return array( + array('{foo}', Expression::TYPE_REGEX), + array('/foo/', Expression::TYPE_REGEX), + array('foo', Expression::TYPE_GLOB), + array('foo*', Expression::TYPE_GLOB), + ); + } + + public function getCaseSensitiveData() + { + return array( + array('{foo}m', true), + array('/foo/i', false), + array('foo*', true), + ); + } + + public function getRegexRenderingData() + { + return array( + array('{foo}m', 'foo'), + array('/foo/i', 'foo'), + ); + } +} diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 2fc7d380ddcf1..6ad1308c95943 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -292,7 +292,7 @@ public function testIn($adapter) $finder = $this->buildFinder($adapter); $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator(); - $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'bootstrap.php', __DIR__.DIRECTORY_SEPARATOR.'ExprTest.php'), $iterator); + $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php', __DIR__.DIRECTORY_SEPARATOR.'bootstrap.php'), $iterator); } /** From e80e1151741b7ab5045c209d8f58090c1ec4f9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 08:44:03 +0200 Subject: [PATCH 28/37] [Finder] Added expression tests & fixed related bugs. --- .../Finder/Adapter/GnuFindAdapter.php | 12 +- .../Component/Finder/Expression/Regex.php | 12 +- .../Finder/Tests/Expression/GlobTest.php | 50 +++++++ .../Finder/Tests/Expression/RegexTest.php | 123 ++++++++++++++++++ .../Component/Finder/Tests/FinderTest.php | 1 + 5 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 src/Symfony/Component/Finder/Tests/Expression/GlobTest.php create mode 100644 src/Symfony/Component/Finder/Tests/Expression/RegexTest.php diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 59ba3cb12ef3c..c67d70807babc 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -83,13 +83,8 @@ public function searchInDirectory($dir) $this->buildSortCommand($command, $this->sort); } - if ($this->shell->testCommand('uniq')) { - $paths = $command->add('| uniq')->execute(); - } else { - $paths = array_unique($command->execute()); - } - - $iterator = new Iterator\FilePathsIterator($command->execute(), $dir); + $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); + $iterator = new Iterator\FilePathsIterator($paths, $dir); if ($this->exclude) { $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); @@ -251,13 +246,12 @@ private function buildContainsCommand(Command $command, array $contains, $not = { foreach ($contains as $contain) { $expr = Expression::create($contain); - $pattern = $expr->isRegex() ? $expr->setStartFlag(false)->setEndFlag(false)->renderPattern() : $expr->render(); $command ->add('| xargs -r grep -I') ->add($expr->isCaseSensitive() ? null : '-i') ->add($not ? '-L' : '-l') - ->add('-Ee')->arg($pattern); + ->add('-Ee')->arg($expr->renderPattern()); } } diff --git a/src/Symfony/Component/Finder/Expression/Regex.php b/src/Symfony/Component/Finder/Expression/Regex.php index 6394c7a57c2a2..29a18be398115 100644 --- a/src/Symfony/Component/Finder/Expression/Regex.php +++ b/src/Symfony/Component/Finder/Expression/Regex.php @@ -107,7 +107,7 @@ public function render() public function renderPattern() { return ($this->startFlag ? self::START_FLAG : '') - .($this->startFlag ? self::JOKER : '') + .($this->startJoker ? self::JOKER : '') .$this->pattern .($this->endJoker ? self::JOKER : '') .($this->endFlag ? self::END_FLAG : ''); @@ -180,7 +180,7 @@ public function setStartFlag($startFlag) /** * @return bool */ - public function getStartFlag() + public function hasStartFlag() { return $this->startFlag; } @@ -220,7 +220,7 @@ public function setStartJoker($startJoker) /** * @return bool */ - public function getStartJoker() + public function hasStartJoker() { return $this->startJoker; } @@ -258,12 +258,12 @@ private function parsePattern($pattern) $pattern = substr($pattern, 2); } - if ($this->endFlag = self::END_FLAG === substr($pattern, 0, -1)) { + if ($this->endFlag = self::END_FLAG === substr($pattern, -1)) { $pattern = substr($pattern, 0, -1); } - if ($this->endJoker = self::JOKER === substr($pattern, 0, -2)) { - $pattern = substr($pattern, 2); + if ($this->endJoker = self::JOKER === substr($pattern, -2)) { + $pattern = substr($pattern, 0, -2); } $this->pattern = $pattern; diff --git a/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php b/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php new file mode 100644 index 0000000000000..0b5d370830a36 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Expression\Expression; + +/** + * @author Jean-François Simon + */ +class GlobTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getToRegexData + */ + public function testGlobToRegex($glob, $match, $noMatch) + { + foreach ($match as $m) { + $this->assertRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp'); + } + + foreach ($noMatch as $m) { + $this->assertNotRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp'); + } + } + + public function getToRegexData() + { + return array( + array('', array(''), array('f', '/')), + array('*', array('foo'), array('foo/', '/foo')), + array('foo.*', array('foo.php', 'foo.a', 'foo.'), array('fooo.php', 'foo.php/foo')), + array('fo?', array('foo', 'fot'), array('fooo', 'ffoo', 'fo/')), + array('fo{o,t}', array('foo', 'fot'), array('fob', 'fo/')), + array('foo(bar|foo)', array('foo(bar|foo)'), array('foobar', 'foofoo')), + array('foo,bar', array('foo,bar'), array('foo', 'bar')), + array('fo{o,\\,}', array('foo', 'fo,'), array()), + array('fo{o,\\\\}', array('foo', 'fo\\'), array()), + array('/foo', array('/foo'), array('foo')), + ); + } +} diff --git a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php new file mode 100644 index 0000000000000..65105b768ac9e --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Expression\Expression; + +/** + * @author Jean-François Simon + */ +class RegexTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getHasFlagsData + */ + public function testHasFlags($regex, $start, $end) + { + $expr = new Expression($regex); + + $this->assertEquals($start, $expr->hasStartFlag()); + $this->assertEquals($end, $expr->hasEndFlag()); + } + + /** + * @dataProvider getHasJokersData + */ + public function testHasJokers($regex, $start, $end) + { + $expr = new Expression($regex); + + $this->assertEquals($start, $expr->hasStartJoker()); + $this->assertEquals($end, $expr->hasEndJoker()); + } + + /** + * @dataProvider getSetFlagsData + */ + public function testSetFlags($regex, $start, $end, $expected) + { + $expr = new Expression($regex); + $expr->setStartFlag($start)->setEndFlag($end); + + $this->assertEquals($expected, $expr->render()); + } + + /** + * @dataProvider getSetJokersData + */ + public function testSetJokers($regex, $start, $end, $expected) + { + $expr = new Expression($regex); + $expr->setStartJoker($start)->setEndJoker($end); + + $this->assertEquals($expected, $expr->render()); + } + + public function testOptions() + { + $expr = new Expression('~abc~is'); + $expr->removeOption('i')->addOption('m'); + + $this->assertEquals('~abc~sm', $expr->render()); + } + + public function testMixFlagsAndJokers() + { + $expr = new Expression('~^.*abc.*$~is'); + + $expr->setStartFlag(false)->setEndFlag(false)->setStartJoker(false)->setEndJoker(false); + $this->assertEquals('~abc~is', $expr->render()); + + $expr->setStartFlag(true)->setEndFlag(true)->setStartJoker(true)->setEndJoker(true); + $this->assertEquals('~^.*abc.*$~is', $expr->render()); + } + + public function getHasFlagsData() + { + return array( + array('~^abc~', true, false), + array('~abc$~', false, true), + array('~abc~', false, false), + array('~^abc$~', true, true), + ); + } + + public function getHasJokersData() + { + return array( + array('~.*abc~', true, false), + array('~abc.*~', false, true), + array('~abc~', false, false), + array('~.*abc.*~', true, true), + ); + } + + public function getSetFlagsData() + { + return array( + array('~abc~', true, false, '~^abc~'), + array('~abc~', false, true, '~abc$~'), + array('~abc~', false, false, '~abc~'), + array('~abc~', true, true, '~^abc$~'), + ); + } + + public function getSetJokersData() + { + return array( + array('~abc~', true, false, '~.*abc~'), + array('~abc~', false, true, '~abc.*~'), + array('~abc~', false, false, '~abc~'), + array('~abc~', true, true, '~.*abc.*~'), + ); + } +} diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 6ad1308c95943..1c467bba152e7 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -467,6 +467,7 @@ protected function toAbsoluteFixtures($files) /** * @dataProvider getContainsTestData + * @group grep */ public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expected) { From 8b9ffc6d62292345d0a2efe641100c7b943c488a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 08:54:36 +0200 Subject: [PATCH 29/37] [Finder] Moved Shell & Command classes to Shell namespace. --- .../Component/Finder/Adapter/GnuFindAdapter.php | 6 +++--- .../Exception/ShellCommandFailureException.php | 12 ++++++------ .../Component/Finder/{ => Shell}/Command.php | 16 ++++++++-------- .../Component/Finder/{ => Shell}/Shell.php | 7 ++++--- 4 files changed, 21 insertions(+), 20 deletions(-) rename src/Symfony/Component/Finder/{ => Shell}/Command.php (89%) rename src/Symfony/Component/Finder/{ => Shell}/Shell.php (89%) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index c67d70807babc..f8e9af258354b 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -12,9 +12,9 @@ namespace Symfony\Component\Finder\Adapter; use Symfony\Component\Finder\Iterator; -use Symfony\Component\Finder\Shell; +use Symfony\Component\Finder\Shell\Shell; use Symfony\Component\Finder\Expression\Expression; -use Symfony\Component\Finder\Command; +use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Iterator\SortableIterator; /** @@ -25,7 +25,7 @@ class GnuFindAdapter extends AbstractAdapter { /** - * @var \Symfony\Component\Finder\Shell + * @var Shell */ private $shell; diff --git a/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php b/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php index 0d42445bcb4aa..2658f6a508fb5 100644 --- a/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php +++ b/src/Symfony/Component/Finder/Exception/ShellCommandFailureException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Finder\Exception; use Symfony\Component\Finder\Adapter\AdapterInterface; -use Symfony\Component\Finder\Command; +use Symfony\Component\Finder\Shell\Command; /** * @author Jean-François Simon @@ -20,14 +20,14 @@ class ShellCommandFailureException extends AdapterFailureException { /** - * @var \Symfony\Component\Finder\Command + * @var Command */ private $command; /** - * @param \Symfony\Component\Finder\Adapter\AdapterInterface $adapter - * @param \Symfony\Component\Finder\Command $command - * @param \Exception|null $previous + * @param AdapterInterface $adapter + * @param Command $command + * @param \Exception|null $previous */ public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null) { @@ -36,7 +36,7 @@ public function __construct(AdapterInterface $adapter, Command $command, \Except } /** - * @return \Symfony\Component\Finder\Command + * @return Command */ public function getCommand() { diff --git a/src/Symfony/Component/Finder/Command.php b/src/Symfony/Component/Finder/Shell/Command.php similarity index 89% rename from src/Symfony/Component/Finder/Command.php rename to src/Symfony/Component/Finder/Shell/Command.php index 9d8d8d4c4d0c5..d375ce9272654 100644 --- a/src/Symfony/Component/Finder/Command.php +++ b/src/Symfony/Component/Finder/Shell/Command.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Finder; +namespace Symfony\Component\Finder\Shell; /** * @author Jean-François Simon @@ -58,7 +58,7 @@ public function __toString() * * @param Command $parent Parent command * - * @return \Symfony\Component\Finder\Command New Command instance + * @return Command New Command instance */ static public function create(Command $parent = null) { @@ -94,7 +94,7 @@ static public function quote($input) * * @param string|Command $bit * - * @return \Symfony\Component\Finder\Command The current Command instance + * @return Command The current Command instance */ public function add($bit) { @@ -108,7 +108,7 @@ public function add($bit) * * @param string|Command $bit * - * @return \Symfony\Component\Finder\Command The current Command instance + * @return Command The current Command instance */ public function top($bit) { @@ -126,7 +126,7 @@ public function top($bit) * * @param string $arg * - * @return \Symfony\Component\Finder\Command The current Command instance + * @return Command The current Command instance */ public function arg($arg) { @@ -140,7 +140,7 @@ public function arg($arg) * * @param string $esc * - * @return \Symfony\Component\Finder\Command The current Command instance + * @return Command The current Command instance */ public function cmd($esc) { @@ -154,7 +154,7 @@ public function cmd($esc) * * @param string $label The unique label * - * @return \Symfony\Component\Finder\Command The current Command instance + * @return Command The current Command instance * * @throws \RuntimeException If label already exists */ @@ -175,7 +175,7 @@ public function ins($label) * * @param string $label * - * @return \Symfony\Component\Finder\Command The labeled command + * @return Command The labeled command */ public function get($label) { diff --git a/src/Symfony/Component/Finder/Shell.php b/src/Symfony/Component/Finder/Shell/Shell.php similarity index 89% rename from src/Symfony/Component/Finder/Shell.php rename to src/Symfony/Component/Finder/Shell/Shell.php index 65b49e56609f0..0c8bc7ef03e3f 100644 --- a/src/Symfony/Component/Finder/Shell.php +++ b/src/Symfony/Component/Finder/Shell/Shell.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Finder; +namespace Symfony\Component\Finder\Shell; /** * @author Jean-François Simon @@ -17,7 +17,7 @@ class Shell { const TYPE_UNIX = 1; - const TYPE_MAC = 2; + const TYPE_DARWIN = 2; const TYPE_CYGWIN = 3; const TYPE_WINDOWS = 4; @@ -54,6 +54,7 @@ public function testCommand($command) return true; } + // todo: find a better way (command could not be available) exec('command -v '.$command, $output, $code); return 0 === $code && count($output) > 0; @@ -73,7 +74,7 @@ private function guessType() } if (false !== strpos($os, 'darwin')) { - return self::TYPE_MAC; + return self::TYPE_DARWIN; } if (0 === strpos($os, 'win')) { From 0e61db95157bb3eb8d214e37f39337449b0f0857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 09:24:14 +0200 Subject: [PATCH 30/37] [Finder] Fixed regex escaped flag/joker bug. --- .../Component/Finder/Expression/Regex.php | 29 +++++++++++++++++-- .../Finder/Tests/Expression/RegexTest.php | 2 ++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Finder/Expression/Regex.php b/src/Symfony/Component/Finder/Expression/Regex.php index 29a18be398115..2781d3664cc40 100644 --- a/src/Symfony/Component/Finder/Expression/Regex.php +++ b/src/Symfony/Component/Finder/Expression/Regex.php @@ -20,6 +20,7 @@ class Regex implements ValueInterface const END_FLAG = '$'; const BOUNDARY = '~'; const JOKER = '.*'; + const ESCAPING = '\\'; /** * @var string @@ -245,6 +246,30 @@ public function hasEndJoker() return $this->endJoker; } + /** + * @param string $expr + * + * @return Regex + */ + public function prepend($expr) + { + $this->pattern = $expr.$this->pattern; + + return $this; + } + + /** + * @param string $expr + * + * @return Regex + */ + public function append($expr) + { + $this->pattern .= $expr; + + return $this; + } + /** * @param string $pattern */ @@ -258,11 +283,11 @@ private function parsePattern($pattern) $pattern = substr($pattern, 2); } - if ($this->endFlag = self::END_FLAG === substr($pattern, -1)) { + if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) { $pattern = substr($pattern, 0, -1); } - if ($this->endJoker = self::JOKER === substr($pattern, -2)) { + if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) { $pattern = substr($pattern, 0, -2); } diff --git a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php index 65105b768ac9e..829f572440742 100644 --- a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php +++ b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php @@ -88,6 +88,7 @@ public function getHasFlagsData() array('~abc$~', false, true), array('~abc~', false, false), array('~^abc$~', true, true), + array('~^abc\\$~', true, false), ); } @@ -98,6 +99,7 @@ public function getHasJokersData() array('~abc.*~', false, true), array('~abc~', false, false), array('~.*abc.*~', true, true), + array('~.*abc\\.*~', true, false), ); } From 60e69fe853e128f094a01df344fb332d82378db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 09:24:43 +0200 Subject: [PATCH 31/37] [Finder] Fixed 'regex are not search' problem. --- .../Finder/Adapter/GnuFindAdapter.php | 36 ++++++++++++------- .../Component/Finder/Tests/FinderTest.php | 8 +++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index f8e9af258354b..c8e4c724aa2c0 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -16,6 +16,8 @@ use Symfony\Component\Finder\Expression\Expression; use Symfony\Component\Finder\Shell\Command; use Symfony\Component\Finder\Iterator\SortableIterator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Comparator\DateComparator; /** * Shell engine implementation using GNU find command. @@ -73,13 +75,16 @@ public function searchInDirectory($dir) $this->buildSizesCommand($find, $this->sizes); $this->buildDatesCommand($find, $this->dates); - if (($useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs')) && ($this->contains || $this->notContains)) { + $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs'); + $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('awk'); + + if ($useGrep && ($this->contains || $this->notContains)) { $grep = $command->ins('grep'); $this->buildContainsCommand($grep, $this->contains); $this->buildContainsCommand($grep, $this->notContains, true); } - if ($useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('awk')) { + if ($useSort) { $this->buildSortCommand($command, $this->sort); } @@ -124,9 +129,9 @@ public function getName() } /** - * @param \Symfony\Component\Finder\Command $command - * @param string[] $names - * @param bool $not + * @param Command $command + * @param string[] $names + * @param bool $not */ private function buildNamesCommand(Command $command, array $names, $not = false) { @@ -139,21 +144,26 @@ private function buildNamesCommand(Command $command, array $names, $not = false) foreach ($names as $i => $name) { $expr = Expression::create($name); + // Fixes 'not search' regex problem. + if ($expr->isRegex()) { + $expr->setStartJoker(true)->setEndJoker(true); + } + $command ->add($i > 0 ? '-or' : null) ->add($expr->isRegex() ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') : ($expr->isCaseSensitive() ? '-name' : '-iname') ) - ->arg($expr->render()); + ->arg($expr->renderPattern()); } $command->cmd(')'); } /** - * @param \Symfony\Component\Finder\Command $command - * @param \Symfony\Component\Finder\Comparator\NumberComparator[] $sizes + * @param Command $command + * @param NumberComparator[] $sizes */ private function buildSizesCommand(Command $command, array $sizes) { @@ -191,8 +201,8 @@ private function buildSizesCommand(Command $command, array $sizes) } /** - * @param \Symfony\Component\Finder\Command $command - * @param \Symfony\Component\Finder\Comparator\DateComparator[] $dates + * @param Command $command + * @param DateComparator[] $dates */ private function buildDatesCommand(Command $command, array $dates) { @@ -238,9 +248,9 @@ private function buildDatesCommand(Command $command, array $dates) } /** - * @param \Symfony\Component\Finder\Command $command - * @param array $contains - * @param bool $not + * @param Command $command + * @param array $contains + * @param bool $not */ private function buildContainsCommand(Command $command, array $contains, $not = false) { diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 1c467bba152e7..7777ae8212211 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -101,6 +101,14 @@ public function testName($adapter) $finder->name('test.ph*'); $finder->name('test.py'); $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('~^test~i'); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('~\\.php$~i'); + $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); } /** From 4e3b01785db15f036deb6fbb9bead901563fd2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 20:03:13 +0200 Subject: [PATCH 32/37] [Finder] Fixed master rebase. --- .../Component/Finder/Adapter/GnuFindAdapter.php | 11 ++++++++++- src/Symfony/Component/Finder/Expression/Regex.php | 2 +- src/Symfony/Component/Finder/Tests/FinderTest.php | 8 +++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index c8e4c724aa2c0..c1a95a8cfbdb9 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -44,6 +44,11 @@ public function __construct() */ public function searchInDirectory($dir) { + // searching directories containing or not containing strings leads to no result + if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) { + return new Iterator\FilePathsIterator(array(), $dir); + } + $command = Command::create(); $find = $command @@ -146,7 +151,11 @@ private function buildNamesCommand(Command $command, array $names, $not = false) // Fixes 'not search' regex problem. if ($expr->isRegex()) { - $expr->setStartJoker(true)->setEndJoker(true); + $expr->prepend($expr->hasStartFlag() ? '/' : '/.*')->setStartFlag(false)->setStartJoker(true); + + if (!$expr->hasEndFlag()) { + $expr->setEndJoker(true); + } } $command diff --git a/src/Symfony/Component/Finder/Expression/Regex.php b/src/Symfony/Component/Finder/Expression/Regex.php index 2781d3664cc40..50c532edac455 100644 --- a/src/Symfony/Component/Finder/Expression/Regex.php +++ b/src/Symfony/Component/Finder/Expression/Regex.php @@ -65,7 +65,7 @@ public static function create($expr) $start = substr($m[1], 0, 1); $end = substr($m[1], -1); - if (($start === $end && !preg_match('/[[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { + if (($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { return new self(substr($m[1], 1, -1), $m[2]); } } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 7777ae8212211..4e4c6c9476a85 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -493,7 +493,7 @@ public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expect */ public function testContainsOnDirectory(Adapter\AdapterInterface $adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->in(__DIR__) ->directories() ->name('Fixtures') @@ -506,7 +506,7 @@ public function testContainsOnDirectory(Adapter\AdapterInterface $adapter) */ public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter) { - $finder = Finder::create()->using($adapter); + $finder = $this->buildFinder($adapter); $finder->in(__DIR__) ->directories() ->name('Fixtures') @@ -519,6 +519,8 @@ public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter) * with inner FilesystemIterator in an ivalid state. * * @see https://bugs.php.net/bug.php?id=49104 + * + * @dataProvider getAdaptersTestData */ public function testMultipleLocations(Adapter\AdapterInterface $adapter) { @@ -528,7 +530,7 @@ public function testMultipleLocations(Adapter\AdapterInterface $adapter) ); // it is expected that there are test.py test.php in the tmpDir - $finder = new Finder(); + $finder = $this->buildFinder($adapter); $finder->in($locations)->depth('< 1')->name('test.php'); $this->assertEquals(1, count($finder)); From 144c61bb204e34304086c40535731d5ccb77243f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 20:26:40 +0200 Subject: [PATCH 33/37] [Finder] Fixed search directory. --- src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index c1a95a8cfbdb9..c77ba0d7b32f5 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -44,6 +44,9 @@ public function __construct() */ public function searchInDirectory($dir) { + // having "/../" in path make find fail + $dir = realpath($dir); + // searching directories containing or not containing strings leads to no result if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) { return new Iterator\FilePathsIterator(array(), $dir); From aa84e2d6e5a5d8feefec69aecdbe93c6c0e7d24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Wed, 1 Aug 2012 23:00:40 +0200 Subject: [PATCH 34/37] [Finder] Fixed typos, CS and small errors. --- .../Finder/Adapter/GnuFindAdapter.php | 30 +++++++++++++------ .../Exception/AdapterFailureException.php | 10 +++---- .../Finder/Exception/ExceptionInterface.php | 14 +++++++++ .../Finder/Expression/Expression.php | 11 ------- src/Symfony/Component/Finder/Finder.php | 10 +++---- .../Component/Finder/Shell/Command.php | 6 ++-- .../Finder/Tests/Expression/GlobTest.php | 3 -- .../Finder/Tests/Expression/RegexTest.php | 21 ++++++------- .../Finder/Tests/FakeAdapter/DummyAdapter.php | 2 +- 9 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 src/Symfony/Component/Finder/Exception/ExceptionInterface.php diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index c77ba0d7b32f5..19e3d30d7f9a0 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -154,10 +154,11 @@ private function buildNamesCommand(Command $command, array $names, $not = false) // Fixes 'not search' regex problem. if ($expr->isRegex()) { - $expr->prepend($expr->hasStartFlag() ? '/' : '/.*')->setStartFlag(false)->setStartJoker(true); + $regex = $expr->getRegex(); + $regex->prepend($regex->hasStartFlag() ? '/' : '/.*')->setStartFlag(false)->setStartJoker(true); - if (!$expr->hasEndFlag()) { - $expr->setEndJoker(true); + if (!$regex->hasEndFlag()) { + $regex->setEndJoker(true); } } @@ -280,12 +281,23 @@ private function buildContainsCommand(Command $command, array $contains, $not = private function buildSortCommand(Command $command, $sort) { switch ($sort) { - case SortableIterator::SORT_BY_NAME: $format = null; break; - case SortableIterator::SORT_BY_TYPE: $format = '%y'; break; - case SortableIterator::SORT_BY_ACCESSED_TIME: $format = '%A@'; break; - case SortableIterator::SORT_BY_CHANGED_TIME: $format = '%C@'; break; - case SortableIterator::SORT_BY_MODIFIED_TIME: $format = '%T@'; break; - default: throw new \InvalidArgumentException('Unknown sort options: '.$sort.'.'); + case SortableIterator::SORT_BY_NAME: + $format = null; + break; + case SortableIterator::SORT_BY_TYPE: + $format = '%y'; + break; + case SortableIterator::SORT_BY_ACCESSED_TIME: + $format = '%A@'; + break; + case SortableIterator::SORT_BY_CHANGED_TIME: + $format = '%C@'; + break; + case SortableIterator::SORT_BY_MODIFIED_TIME: + $format = '%T@'; + break; + default: + throw new \InvalidArgumentException('Unknown sort options: '.$sort.'.'); } $command->get('find')->add('-printf')->arg($format.' %h/%f\\n'); diff --git a/src/Symfony/Component/Finder/Exception/AdapterFailureException.php b/src/Symfony/Component/Finder/Exception/AdapterFailureException.php index 4d483dc083a0d..15fa22147d837 100644 --- a/src/Symfony/Component/Finder/Exception/AdapterFailureException.php +++ b/src/Symfony/Component/Finder/Exception/AdapterFailureException.php @@ -18,7 +18,7 @@ * * @author Jean-François Simon */ -class AdapterFailureException extends \RuntimeException +class AdapterFailureException extends \RuntimeException implements ExceptionInterface { /** * @var \Symfony\Component\Finder\Adapter\AdapterInterface @@ -26,9 +26,9 @@ class AdapterFailureException extends \RuntimeException private $adapter; /** - * @param \Symfony\Component\Finder\Adapter\AdapterInterface $adapter - * @param string|null $message - * @param \Exception|null $previous + * @param AdapterInterface $adapter + * @param string|null $message + * @param \Exception|null $previous */ public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null) { @@ -37,7 +37,7 @@ public function __construct(AdapterInterface $adapter, $message = null, \Excepti } /** - * @return \Symfony\Component\Finder\Adapter\AdapterInterface + * {@inheritdoc} */ public function getAdapter() { diff --git a/src/Symfony/Component/Finder/Exception/ExceptionInterface.php b/src/Symfony/Component/Finder/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000..86d391b91e5cf --- /dev/null +++ b/src/Symfony/Component/Finder/Exception/ExceptionInterface.php @@ -0,0 +1,14 @@ + + */ +interface ExceptionInterface +{ + /** + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + */ + function getAdapter(); +} diff --git a/src/Symfony/Component/Finder/Expression/Expression.php b/src/Symfony/Component/Finder/Expression/Expression.php index 5fa014d5b9459..b8124124269d9 100644 --- a/src/Symfony/Component/Finder/Expression/Expression.php +++ b/src/Symfony/Component/Finder/Expression/Expression.php @@ -54,17 +54,6 @@ public function __toString() return $this->render(); } - /** - * @param string $method - * @param array $arguments - * - * @return mixed - */ - public function __call($method, array $arguments) - { - return call_user_func_array(array($this->value, $method), $arguments); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 843102ea2b815..32774c7936924 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Finder; use Symfony\Component\Finder\Adapter; -use Symfony\Component\Finder\Exception\AdapterFailureException; +use Symfony\Component\Finder\Exception\ExceptionInterface; /** * Finder allows to build rules to find files and directories. @@ -82,7 +82,7 @@ public static function create() * @param Adapter\AdapterInterface $adapter An adapter instance * @param int $priority Highest is selected first * - * @return \Symfony\Component\Finder\Finder The current Finder instance + * @return Finder The current Finder instance */ public function register(Adapter\AdapterInterface $adapter, $priority = 0) { @@ -97,7 +97,7 @@ public function register(Adapter\AdapterInterface $adapter, $priority = 0) /** * Removes all adapters registered in the finder. * - * @return \Symfony\Component\Finder\Finder The current Finder instance + * @return Finder The current Finder instance */ public function removeAdapters() { @@ -107,7 +107,7 @@ public function removeAdapters() } /** - * Returns reegistered adapters ordered by priority without extra informations. + * Returns registered adapters ordered by priority without extra information. * * @return \Symfony\Component\Finder\Adapter\AdapterInterface[] */ @@ -654,7 +654,7 @@ private function searchInDirectory($dir) return $this ->buildAdapter($adapter['adapter']) ->searchInDirectory($dir); - } catch(AdapterFailureException $e) { + } catch(ExceptionInterface $e) { continue; } } diff --git a/src/Symfony/Component/Finder/Shell/Command.php b/src/Symfony/Component/Finder/Shell/Command.php index d375ce9272654..477c664e111f3 100644 --- a/src/Symfony/Component/Finder/Shell/Command.php +++ b/src/Symfony/Component/Finder/Shell/Command.php @@ -60,7 +60,7 @@ public function __toString() * * @return Command New Command instance */ - static public function create(Command $parent = null) + public static function create(Command $parent = null) { return new self($parent); } @@ -72,7 +72,7 @@ static public function create(Command $parent = null) * * @return string The escaped string */ - static public function escape($input) + public static function escape($input) { return escapeshellcmd($input); } @@ -84,7 +84,7 @@ static public function escape($input) * * @return string The quoted string */ - static public function quote($input) + public static function quote($input) { return escapeshellarg($input); } diff --git a/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php b/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php index 0b5d370830a36..fbaeb0e237c82 100644 --- a/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php +++ b/src/Symfony/Component/Finder/Tests/Expression/GlobTest.php @@ -13,9 +13,6 @@ use Symfony\Component\Finder\Expression\Expression; -/** - * @author Jean-François Simon - */ class GlobTest extends \PHPUnit_Framework_TestCase { /** diff --git a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php index 829f572440742..55897697905f0 100644 --- a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php +++ b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php @@ -13,9 +13,6 @@ use Symfony\Component\Finder\Expression\Expression; -/** - * @author Jean-François Simon - */ class RegexTest extends \PHPUnit_Framework_TestCase { /** @@ -25,8 +22,8 @@ public function testHasFlags($regex, $start, $end) { $expr = new Expression($regex); - $this->assertEquals($start, $expr->hasStartFlag()); - $this->assertEquals($end, $expr->hasEndFlag()); + $this->assertEquals($start, $expr->getRegex()->hasStartFlag()); + $this->assertEquals($end, $expr->getRegex()->hasEndFlag()); } /** @@ -36,8 +33,8 @@ public function testHasJokers($regex, $start, $end) { $expr = new Expression($regex); - $this->assertEquals($start, $expr->hasStartJoker()); - $this->assertEquals($end, $expr->hasEndJoker()); + $this->assertEquals($start, $expr->getRegex()->hasStartJoker()); + $this->assertEquals($end, $expr->getRegex()->hasEndJoker()); } /** @@ -46,7 +43,7 @@ public function testHasJokers($regex, $start, $end) public function testSetFlags($regex, $start, $end, $expected) { $expr = new Expression($regex); - $expr->setStartFlag($start)->setEndFlag($end); + $expr->getRegex()->setStartFlag($start)->setEndFlag($end); $this->assertEquals($expected, $expr->render()); } @@ -57,7 +54,7 @@ public function testSetFlags($regex, $start, $end, $expected) public function testSetJokers($regex, $start, $end, $expected) { $expr = new Expression($regex); - $expr->setStartJoker($start)->setEndJoker($end); + $expr->getRegex()->setStartJoker($start)->setEndJoker($end); $this->assertEquals($expected, $expr->render()); } @@ -65,7 +62,7 @@ public function testSetJokers($regex, $start, $end, $expected) public function testOptions() { $expr = new Expression('~abc~is'); - $expr->removeOption('i')->addOption('m'); + $expr->getRegex()->removeOption('i')->addOption('m'); $this->assertEquals('~abc~sm', $expr->render()); } @@ -74,10 +71,10 @@ public function testMixFlagsAndJokers() { $expr = new Expression('~^.*abc.*$~is'); - $expr->setStartFlag(false)->setEndFlag(false)->setStartJoker(false)->setEndJoker(false); + $expr->getRegex()->setStartFlag(false)->setEndFlag(false)->setStartJoker(false)->setEndJoker(false); $this->assertEquals('~abc~is', $expr->render()); - $expr->setStartFlag(true)->setEndFlag(true)->setStartJoker(true)->setEndJoker(true); + $expr->getRegex()->setStartFlag(true)->setEndFlag(true)->setStartJoker(true)->setEndJoker(true); $this->assertEquals('~^.*abc.*$~is', $expr->render()); } diff --git a/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php b/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php index f4b4a04ca2e36..26361f803612b 100644 --- a/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php +++ b/src/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php @@ -51,7 +51,7 @@ public function isSupported() /** * {@inheritdoc} */ - function getName() + public function getName() { return 'yes'; } From 46cc2d40f35ea5e68cb66039e7539c75bd9c56a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Thu, 2 Aug 2012 07:03:13 +0200 Subject: [PATCH 35/37] [Finder] Fixed namespace. --- src/Symfony/Component/Finder/Finder.php | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 32774c7936924..63fa6e70f7f75 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Finder; -use Symfony\Component\Finder\Adapter; +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Adapter\GnuFindAdapter; +use Symfony\Component\Finder\Adapter\PhpAdapter; use Symfony\Component\Finder\Exception\ExceptionInterface; /** @@ -60,8 +62,8 @@ public function __construct() { $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; - $this->register(new Adapter\GnuFindAdapter()); - $this->register(new Adapter\PhpAdapter(), -50); + $this->register(new GnuFindAdapter()); + $this->register(new PhpAdapter(), -50); } /** @@ -79,8 +81,8 @@ public static function create() /** * Registers a finder engine implementation. * - * @param Adapter\AdapterInterface $adapter An adapter instance - * @param int $priority Highest is selected first + * @param AdapterInterface $adapter An adapter instance + * @param int $priority Highest is selected first * * @return Finder The current Finder instance */ @@ -109,7 +111,7 @@ public function removeAdapters() /** * Returns registered adapters ordered by priority without extra information. * - * @return \Symfony\Component\Finder\Adapter\AdapterInterface[] + * @return AdapterInterface[] */ public function getAdapters() { @@ -663,11 +665,11 @@ private function searchInDirectory($dir) } /** - * @param Adapter\AdapterInterface $adapter + * @param AdapterInterface $adapter * - * @return Adapter\AdapterInterface + * @return AdapterInterface */ - private function buildAdapter(Adapter\AdapterInterface $adapter) + private function buildAdapter(AdapterInterface $adapter) { return $adapter ->setFollowLinks($this->followLinks) From 5fc01f257b64057f1f8f526187ba09e9fe857c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Thu, 2 Aug 2012 07:13:13 +0200 Subject: [PATCH 36/37] [Finder] Changed method name for gnu find adapter. --- .../Finder/Adapter/GnuFindAdapter.php | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 19e3d30d7f9a0..9a9fd2ab6e205 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -78,22 +78,22 @@ public function searchInDirectory($dir) $find->add('-type f'); } - $this->buildNamesCommand($find, $this->names); - $this->buildNamesCommand($find, $this->notNames, true); - $this->buildSizesCommand($find, $this->sizes); - $this->buildDatesCommand($find, $this->dates); + $this->buildNamesFiltering($find, $this->names); + $this->buildNamesFiltering($find, $this->notNames, true); + $this->buildSizesFiltering($find, $this->sizes); + $this->buildDatesFiltering($find, $this->dates); $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs'); $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('awk'); if ($useGrep && ($this->contains || $this->notContains)) { $grep = $command->ins('grep'); - $this->buildContainsCommand($grep, $this->contains); - $this->buildContainsCommand($grep, $this->notContains, true); + $this->buildContentFiltering($grep, $this->contains); + $this->buildContentFiltering($grep, $this->notContains, true); } if ($useSort) { - $this->buildSortCommand($command, $this->sort); + $this->buildSorting($command, $this->sort); } $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); @@ -141,7 +141,7 @@ public function getName() * @param string[] $names * @param bool $not */ - private function buildNamesCommand(Command $command, array $names, $not = false) + private function buildNamesFiltering(Command $command, array $names, $not = false) { if (0 === count($names)) { return; @@ -178,7 +178,7 @@ private function buildNamesCommand(Command $command, array $names, $not = false) * @param Command $command * @param NumberComparator[] $sizes */ - private function buildSizesCommand(Command $command, array $sizes) + private function buildSizesFiltering(Command $command, array $sizes) { foreach ($sizes as $i => $size) { $command->add($i > 0 ? '-and' : null); @@ -217,7 +217,7 @@ private function buildSizesCommand(Command $command, array $sizes) * @param Command $command * @param DateComparator[] $dates */ - private function buildDatesCommand(Command $command, array $dates) + private function buildDatesFiltering(Command $command, array $dates) { foreach ($dates as $i => $date) { $command->add($i > 0 ? '-and' : null); @@ -265,11 +265,12 @@ private function buildDatesCommand(Command $command, array $dates) * @param array $contains * @param bool $not */ - private function buildContainsCommand(Command $command, array $contains, $not = false) + private function buildContentFiltering(Command $command, array $contains, $not = false) { foreach ($contains as $contain) { $expr = Expression::create($contain); + // todo: avoid forking process for each $pattern by using multiple -e options $command ->add('| xargs -r grep -I') ->add($expr->isCaseSensitive() ? null : '-i') @@ -278,7 +279,7 @@ private function buildContainsCommand(Command $command, array $contains, $not = } } - private function buildSortCommand(Command $command, $sort) + private function buildSorting(Command $command, $sort) { switch ($sort) { case SortableIterator::SORT_BY_NAME: From ef0ebb8156b14d59a3b445ecf25306aa0a4fbe10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Simon?= Date: Fri, 3 Aug 2012 08:32:37 +0200 Subject: [PATCH 37/37] [Finder] Fixed gnu find regex name filtering problems. --- .../Finder/Adapter/GnuFindAdapter.php | 14 ++++--- .../Component/Finder/Expression/Regex.php | 19 +++++++++ .../Finder/Tests/Expression/RegexTest.php | 21 ++++++++++ .../Component/Finder/Tests/FinderTest.php | 41 +++++++++++++++---- 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php index 9a9fd2ab6e205..98ac30f9b2d01 100644 --- a/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -152,13 +152,17 @@ private function buildNamesFiltering(Command $command, array $names, $not = fals foreach ($names as $i => $name) { $expr = Expression::create($name); - // Fixes 'not search' regex problem. + // Fixes 'not search' and 'fuls path matching' regex problems. + // - Jokers '.' are replaced by [^/]. + // - We add '[^/]*' before and after regex (if no ^|$ flags are present). if ($expr->isRegex()) { $regex = $expr->getRegex(); - $regex->prepend($regex->hasStartFlag() ? '/' : '/.*')->setStartFlag(false)->setStartJoker(true); - - if (!$regex->hasEndFlag()) { - $regex->setEndJoker(true); + $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*') + ->setStartFlag(false) + ->setStartJoker(true) + ->replaceJokers('[^/]'); + if (!$regex->hasEndFlag() || $regex->hasEndJoker()) { + $regex->setEndJoker(false)->append('[^/]*'); } } diff --git a/src/Symfony/Component/Finder/Expression/Regex.php b/src/Symfony/Component/Finder/Expression/Regex.php index 50c532edac455..40d776394a891 100644 --- a/src/Symfony/Component/Finder/Expression/Regex.php +++ b/src/Symfony/Component/Finder/Expression/Regex.php @@ -270,6 +270,25 @@ public function append($expr) return $this; } + /** + * @param array $replacements + * + * @return Regex + */ + public function replaceJokers($replacement) + { + $replace = function ($subject) use ($replacement) { + $subject = $subject[0]; + $replace = 0 === substr_count($subject, '\\') % 2; + + return $replace ? str_replace('.', $replacement, $subject) : $subject; + }; + + $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern); + + return $this; + } + /** * @param string $pattern */ diff --git a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php index 55897697905f0..f252696a76256 100644 --- a/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php +++ b/src/Symfony/Component/Finder/Tests/Expression/RegexTest.php @@ -78,6 +78,17 @@ public function testMixFlagsAndJokers() $this->assertEquals('~^.*abc.*$~is', $expr->render()); } + /** + * @dataProvider getReplaceJokersTestData + */ + public function testReplaceJokers($regex, $expected) + { + $expr = new Expression($regex); + $expr = $expr->getRegex()->replaceJokers('@'); + + $this->assertEquals($expected, $expr->renderPattern()); + } + public function getHasFlagsData() { return array( @@ -119,4 +130,14 @@ public function getSetJokersData() array('~abc~', true, true, '~.*abc.*~'), ); } + + public function getReplaceJokersTestData() + { + return array( + array('~.abc~', '@abc'), + array('~\\.abc~', '\\.abc'), + array('~\\\\.abc~', '\\\\@abc'), + array('~\\\\\\.abc~', '\\\\\\.abc'), + ); + } } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 4e4c6c9476a85..1baf457f7db60 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -133,6 +133,18 @@ public function testNotName($adapter) $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); } + /** + * @dataProvider getRegexNameTestData + * + * @group regexName + */ + public function testRegexName($adapter, $regex) + { + $finder = $this->buildFinder($adapter); + $finder->name($regex); + $this->assertIterator($this->toAbsolute(array('test.py', 'test.php')), $finder->in(self::$tmpDir)->getIterator()); + } + /** * @dataProvider getAdaptersTestData */ @@ -595,14 +607,17 @@ public function getContainsTestData() array('ipsum dolor sit amet', '/^IPSUM/m', array('lorem.txt')), ); - $data = array(); - foreach ($this->getValidAdapters() as $adapter) { - foreach ($tests as $test) { - $data[] = array_merge(array($adapter), $test); - } - } + return $this->buildTestData($tests); + } - return $data; + public function getRegexNameTestData() + { + $tests = array( + array('~.+\\.p.+~i'), + array('~t.*s~i'), + ); + + return $this->buildTestData($tests); } private function buildFinder(Adapter\AdapterInterface $adapter) @@ -619,4 +634,16 @@ private function getValidAdapters() function (Adapter\AdapterInterface $adapter) { return $adapter->isSupported(); } ); } + + private function buildTestData(array $tests) + { + $data = array(); + foreach ($this->getValidAdapters() as $adapter) { + foreach ($tests as $test) { + $data[] = array_merge(array($adapter), $test); + } + } + + return $data; + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy