Permalink
Browse files

[Finder] Added bsd adapter (need tests).

  • Loading branch information...
jfsimon committed Oct 30, 2012
1 parent d4ff123 commit 708a23f56ceeb3f55c8634faea8ee282dbea8564
Showing with 411 additions and 306 deletions.
  1. +352 −0 Adapter/AbstractFindAdapter.php
  2. +51 −0 Adapter/BsdFindAdapter.php
  3. +6 −306 Adapter/GnuFindAdapter.php
  4. +2 −0 Finder.php
@@ -0,0 +1,352 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Finder\Adapter;
+
+use Symfony\Component\Finder\Iterator;
+use Symfony\Component\Finder\Shell\Shell;
+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.
+ *
+ * @author Jean-François Simon <contact@jfsimon.fr>
+ */
+abstract class AbstractFindAdapter extends AbstractAdapter
+{
+ /**
+ * @var Shell
+ */
+ protected $shell;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->shell = new Shell();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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);
+ }
+
+ $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) {
+ $find->add('-follow');
+ }
+
+ $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) {
+ $find->add('-maxdepth')->add($this->maxDepth+1);
+ }
+
+ if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
+ $find->add('-type d');
+ } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
+ $find->add('-type f');
+ }
+
+ $this->buildNamesFiltering($find, $this->names);
+ $this->buildNamesFiltering($find, $this->notNames, true);
+ $this->buildPathsFiltering($find, $dir, $this->paths);
+ $this->buildPathsFiltering($find, $dir, $this->notPaths, 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('cut');
+
+ if ($useGrep && ($this->contains || $this->notContains)) {
+ $grep = $command->ins('grep');
+ $this->buildContentFiltering($grep, $this->contains);
+ $this->buildContentFiltering($grep, $this->notContains, true);
+ }
+
+ if ($useSort) {
+ $this->buildSorting($command, $this->sort);
+ }
+
+ $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);
+ }
+
+ if (!$useGrep && ($this->contains || $this->notContains)) {
+ $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
+ }
+
+ if ($this->filters) {
+ $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
+ }
+
+ if (!$useSort && $this->sort) {
+ $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
+ $iterator = $iteratorAggregate->getIterator();
+ }
+
+ return $iterator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isSupported()
+ {
+ return $this->shell->testCommand('find');
+ }
+
+ /**
+ * @param Command $command
+ * @param string[] $names
+ * @param bool $not
+ */
+ private function buildNamesFiltering(Command $command, array $names, $not = false)
+ {
+ if (0 === count($names)) {
+ return;
+ }
+
+ $command->add($not ? '-not' : null)->cmd('(');
+
+ foreach ($names as $i => $name) {
+ $expr = Expression::create($name);
+
+ // Fixes 'not search' and 'full 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)
+ ->replaceJokers('[^/]');
+ if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
+ $regex->setEndJoker(false)->append('[^/]*');
+ }
+ }
+
+ $command
+ ->add($i > 0 ? '-or' : null)
+ ->add($expr->isRegex()
+ ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
+ : ($expr->isCaseSensitive() ? '-name' : '-iname')
+ )
+ ->arg($expr->renderPattern());
+ }
+
+ $command->cmd(')');
+ }
+
+ /**
+ * @param Command $command
+ * @param string $dir
+ * @param string[] $paths
+ * @param bool $not
+ * @return void
+ */
+ private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
+ {
+ if (0 === count($paths)) {
+ return;
+ }
+
+ $command->add($not ? '-not' : null)->cmd('(');
+
+ foreach ($paths as $i => $path) {
+ $expr = Expression::create($path);
+
+ // Fixes 'not search' regex problems.
+ if ($expr->isRegex()) {
+ $regex = $expr->getRegex();
+ $regex->prepend($regex->hasStartFlag() ? '' : '.*')->setEndJoker(!$regex->hasEndFlag());
+ } else {
+ $expr->prepend('*')->append('*');
+ }
+
+ $command
+ ->add($i > 0 ? '-or' : null)
+ ->add($expr->isRegex()
+ ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
+ : ($expr->isCaseSensitive() ? '-path' : '-ipath')
+ )
+ ->arg($expr->prepend($dir.DIRECTORY_SEPARATOR)->renderPattern());
+ }
+
+ $command->cmd(')');
+ }
+
+ /**
+ * @param Command $command
+ * @param NumberComparator[] $sizes
+ */
+ private function buildSizesFiltering(Command $command, array $sizes)
+ {
+ foreach ($sizes as $i => $size) {
+ $command->add($i > 0 ? '-and' : null);
+
+ if ('<=' === $size->getOperator()) {
+ $command->add('-size -'.($size->getTarget()+1).'c');
+ continue;
+ }
+
+ if ('<' === $size->getOperator()) {
+ $command->add('-size -'.$size->getTarget().'c');
+ continue;
+ }
+
+ if ('>=' === $size->getOperator()) {
+ $command->add('-size +'.($size->getTarget()-1).'c');
+ continue;
+ }
+
+ if ('>' === $size->getOperator()) {
+ $command->add('-size +'.$size->getTarget().'c');
+ continue;
+ }
+
+ if ('!=' === $size->getOperator()) {
+ $command->add('-size -'.$size->getTarget().'c');
+ $command->add('-size +'.$size->getTarget().'c');
+ continue;
+ }
+
+ $command->add('-size '.$size->getTarget().'c');
+ }
+ }
+
+ /**
+ * @param Command $command
+ * @param DateComparator[] $dates
+ */
+ private function buildDatesFiltering(Command $command, array $dates)
+ {
+ 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
+ $command->add(' -mmin -0');
+ // we will have no result so we don't need to continue
+ return;
+ }
+
+ if ('<=' === $date->getOperator()) {
+ $command->add('-mmin +'.($mins-1));
+ continue;
+ }
+
+ if ('<' === $date->getOperator()) {
+ $command->add('-mmin +'.$mins);
+ continue;
+ }
+
+ if ('>=' === $date->getOperator()) {
+ $command->add('-mmin -'.($mins+1));
+ continue;
+ }
+
+ if ('>' === $date->getOperator()) {
+ $command->add('-mmin -'.$mins);
+ continue;
+ }
+
+ if ('!=' === $date->getOperator()) {
+ $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
+ continue;
+ }
+
+ $command->add('-mmin '.$mins);
+ }
+ }
+
+ /**
+ * @param Command $command
+ * @param array $contains
+ * @param bool $not
+ */
+ 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')
+ ->add($not ? '-L' : '-l')
+ ->add('-Ee')->arg($expr->renderPattern());
+ }
+ }
+
+ /**
+ * @param \Symfony\Component\Finder\Shell\Command $command
+ * @param string $sort
+ * @throws \InvalidArgumentException
+ */
+ private function buildSorting(Command $command, $sort)
+ {
+ switch ($sort) {
+ case SortableIterator::SORT_BY_NAME:
+ $command->ins('sort')->add('| sort');
+ return;
+ 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.'.');
+ }
+
+ $this->buildFormatSorting($command, $format);
+ }
+
+ /**
+ * @param Command $command
+ * @param string $format
+ */
+ abstract protected function buildFormatSorting(Command $command, $format);
+}
Oops, something went wrong.

0 comments on commit 708a23f

Please sign in to comment.