diff --git a/src/Symfony/Components/Finder/Finder.php b/src/Symfony/Components/Finder/Finder.php new file mode 100644 index 0000000000..2fd5099d34 --- /dev/null +++ b/src/Symfony/Components/Finder/Finder.php @@ -0,0 +1,361 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times, except for ->in() method. + * Some rules are cumulative (->name() for example) whereas others are destructive + * (most recent value is used, ->maxDepth() method for example). + * + * All methods return the current Finder object to allow easy chaining: + * + * $finder = new Finder(); + * $iterator = $finder->files()->name('*.php')->in(__DIR__); + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class Finder +{ + protected $mode = 0; + protected $names = array(); + protected $notNames = array(); + protected $exclude = array(); + protected $filters = array(); + protected $mindepth = 0; + protected $maxdepth = INF; + protected $sizes = array(); + protected $followLinks = false; + protected $sort = false; + protected $ignoreVCS = true; + + /** + * Restricts the matching to directories only. + * + * @return Symfony\Components\Finder The current Finder instance + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return Symfony\Components\Finder The current Finder instance + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Sets the maximum directory depth. + * + * The Finder will descend at most $level levels of directories below the starting point. + * + * @param int $level The max depth + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\LimitDepthFilterIterator + */ + public function maxDepth($level) + { + $this->maxdepth = (double) $level; + + return $this; + } + + /** + * Sets the minimum directory depth. + * + * The Finder will start matching at level $level. + * + * @param int $level The min depth + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\LimitDepthFilterIterator + */ + public function minDepth($level) + { + $this->mindepth = (integer) $level; + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\FilenameFilterIterator + */ + public function name($pattern) + { + $this->names[] = $pattern; + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\FilenameFilterIterator + */ + public function notName($pattern) + { + $this->notNames[] = $pattern; + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * + * @param string $size A size range string + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\SizeRangeFilterIterator + * @see Symfony\Components\Finder\NumberCompare + */ + public function size($size) + { + $this->sizes[] = new NumberCompare($size); + + return $this; + } + + /** + * Excludes directories. + * + * @param string $dir A directory to exclude + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\ExcludeDirectoryFilterIterator + */ + public function exclude($dir) + { + $this->exclude[] = $dir; + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\IgnoreVcsFilterIterator + */ + public function ignoreVCS($ignoreVCS) + { + $this->ignoreVCS = (Boolean) $ignoreVCS; + + return $this; + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @param Closure $closure An anonymous function + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\SortableIterator + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\SortableIterator + */ + public function sortByName() + { + $this->sort = Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\SortableIterator + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @param Closure $closure An anonymous function + * + * @return Symfony\Components\Finder The current Finder instance + * + * @see Symfony\Components\Finder\Iterator\CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return Symfony\Components\Finder The current Finder instance + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string $dir A directory path + * + * @return \Iterator An iterator + * + * @throws \InvalidArgumentException if the directory does not exist + */ + public function in($dir) + { + if (is_array($dir)) + { + $iterator = new Iterator\ChainIterator(); + foreach ($dir as $d) + { + $iterator->attach($this->searchInDirectory($d)); + } + + return $iterator; + } + + return $this->searchInDirectory($dir); + } + + protected function searchInDirectory($dir) + { + if (!is_dir($dir)) + { + throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); + } + + $flags = \FilesystemIterator::SKIP_DOTS; + + if ($this->followLinks) + { + $flags |= \FilesystemIterator::FOLLOW_SYMLINKS; + } + + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, $flags), \RecursiveIteratorIterator::SELF_FIRST); + + if ($this->mode) + { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->mindepth > 0 || $this->maxdepth < INF) + { + $iterator = new Iterator\LimitDepthFilterIterator($iterator, $dir, $this->mindepth, $this->maxdepth); + } + + if ($this->exclude) + { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + if ($this->ignoreVCS) + { + $iterator = new Iterator\IgnoreVcsFilterIterator($iterator); + } + + if ($this->names || $this->notNames) + { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->sizes) + { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->filters) + { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->sort) + { + $iterator = new Iterator\SortableIterator($iterator, $this->sort); + } + + return $iterator; + } +} diff --git a/src/Symfony/Framework/WebBundle/Util/Glob.php b/src/Symfony/Components/Finder/Glob.php similarity index 56% rename from src/Symfony/Framework/WebBundle/Util/Glob.php rename to src/Symfony/Components/Finder/Glob.php index 580098de8f..33fe546006 100644 --- a/src/Symfony/Framework/WebBundle/Util/Glob.php +++ b/src/Symfony/Components/Finder/Glob.php @@ -1,6 +1,6 @@ php port - * @author Richard Clamp perl version + * @subpackage Components_Finder + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version * @copyright 2004-2005 Fabien Potencier * @copyright 2002 Richard Clamp */ class Glob { - protected static $strict_leading_dot = true; - protected static $strict_wildcard_slash = true; - - public static function setStrictLeadingDot($boolean) - { - self::$strict_leading_dot = $boolean; - } - - public static function setStrictWildcardSlash($boolean) - { - self::$strict_wildcard_slash = $boolean; - } - /** - * Returns a compiled regex which is the equivalent of the globbing pattern. + * Returns a regexp which is the equivalent of the glob pattern. * - * @param string $glob pattern - * @return string regex + * @param string $glob The glob pattern + * + * @return string regex The regexp */ - public static function toRegex($glob) + static public function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true) { - $first_byte = true; + $firstByte = true; $escaping = false; - $in_curlies = 0; + $inCurlies = 0; $regex = ''; $sizeGlob = strlen($glob); for ($i = 0; $i < $sizeGlob; $i++) { $car = $glob[$i]; - if ($first_byte) + if ($firstByte) { - if (self::$strict_leading_dot && $car !== '.') + if ($strictLeadingDot && $car !== '.') { $regex .= '(?=[^\.])'; } - $first_byte = false; + $firstByte = false; } if ($car === '/') { - $first_byte = true; + $firstByte = true; } if ($car === '.' || $car === '(' || $car === ')' || $car === '|' || $car === '+' || $car === '^' || $car === '$') @@ -87,25 +75,31 @@ class Glob } elseif ($car === '*') { - $regex .= ($escaping ? '\\*' : (self::$strict_wildcard_slash ? '[^/]*' : '.*')); + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); } elseif ($car === '?') { - $regex .= ($escaping ? '\\?' : (self::$strict_wildcard_slash ? '[^/]' : '.')); + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); } elseif ($car === '{') { - $regex .= ($escaping ? '\\{' : '('); - if (!$escaping) ++$in_curlies; + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) + { + ++$inCurlies; + } } - elseif ($car === '}' && $in_curlies) + elseif ($car === '}' && $inCurlies) { - $regex .= ($escaping ? '}' : ')'); - if (!$escaping) --$in_curlies; + $regex .= $escaping ? '}' : ')'; + if (!$escaping) + { + --$inCurlies; + } } - elseif ($car === ',' && $in_curlies) + elseif ($car === ',' && $inCurlies) { - $regex .= ($escaping ? ',' : '|'); + $regex .= $escaping ? ',' : '|'; } elseif ($car === '\\') { diff --git a/src/Symfony/Components/Finder/Iterator/ChainIterator.php b/src/Symfony/Components/Finder/Iterator/ChainIterator.php new file mode 100644 index 0000000000..4be240c013 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/ChainIterator.php @@ -0,0 +1,90 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ChainIterator iterates through several iterators, one at a time. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class ChainIterator implements \Iterator +{ + protected $iterators; + protected $current; + protected $cursor; + + /** + * Constructor. + * + * @param array $iterators An array of \Iterator instances + */ + public function __construct(array $iterators = array()) + { + $this->iterators = array(); + foreach ($iterators as $iterator) + { + $this->attach($iterator); + } + $this->rewind(); + } + + public function attach(\Iterator $iterator) + { + $this->iterators[] = $iterator; + } + + public function rewind() + { + $this->cursor = 0; + $this->current = 0; + foreach ($this->iterators as $iterator) + { + $iterator->rewind(); + } + } + + public function valid() + { + if ($this->current > count($this->iterators) - 1) + { + return false; + } + + // still something for the current iterator? + if ($this->iterators[$this->current]->valid()) + { + return true; + } + + // go to the next one + ++$this->current; + + return $this->valid(); + } + + public function next() + { + $this->iterators[$this->current]->next(); + } + + public function current() + { + return $this->iterators[$this->current]->current(); + } + + public function key() + { + return $this->cursor++; + } +} diff --git a/src/Symfony/Components/Finder/Iterator/CustomFilterIterator.php b/src/Symfony/Components/Finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000000..099b341de4 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,60 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class CustomFilterIterator extends \FilterIterator +{ + protected $filters = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $filters An array of \Closure + */ + public function __construct(\Iterator $iterator, array $filters) + { + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->getInnerIterator()->current(); + + foreach ($this->filters as $filter) + { + if (false === $filter($fileinfo)) + { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Components/Finder/Iterator/ExcludeDirectoryFilterIterator.php b/src/Symfony/Components/Finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000000..3b5022f6c6 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator +{ + protected $patterns; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->patterns = array(); + foreach ($directories as $directory) + { + $this->patterns[] = '#/'.preg_quote($directory, '#').'(/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->getInnerIterator()->current(); + + foreach ($this->patterns as $pattern) + { + $path = $fileinfo->getPathname(); + if ($fileinfo->isDir()) + { + $path .= '/'; + } + + if (preg_match($pattern, $path)) + { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Components/Finder/Iterator/FileTypeFilterIterator.php b/src/Symfony/Components/Finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000000..3b257eb1f2 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,61 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class FileTypeFilterIterator extends \FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + protected $mode; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param integer $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->getInnerIterator()->current(); + + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) + { + return false; + } + elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) + { + return false; + } + + return true; + } +} diff --git a/src/Symfony/Components/Finder/Iterator/FilenameFilterIterator.php b/src/Symfony/Components/Finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000000..b93b2a2ef0 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,109 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class FilenameFilterIterator extends \FilterIterator +{ + protected $matchRegexps; + protected $noMatchRegexps; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $matchPatterns An array of patterns that need to match + * @param array $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + $this->matchRegexps = array(); + foreach ($matchPatterns as $pattern) + { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + $this->noMatchRegexps = array(); + foreach ($noMatchPatterns as $pattern) + { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->getInnerIterator()->current(); + + // should at least match one rule + if ($this->matchRegexps) + { + $match = false; + foreach ($this->matchRegexps as $regex) + { + if (preg_match($regex, $fileinfo->getFilename())) + { + $match = true; + break; + } + } + } + else + { + $match = true; + } + + // should at least not match one rule to exclude + if ($this->noMatchRegexps) + { + $exclude = false; + foreach ($this->noMatchRegexps as $regex) + { + if (preg_match($regex, $fileinfo->getFilename())) + { + $exclude = true; + break; + } + } + } + else + { + $exclude = false; + } + + return $match && !$exclude; + } + + protected function toRegex($str) + { + if (preg_match('/^([^a-zA-Z0-9\\\\]).+?\\1[ims]?$/', $str)) + { + return $str; + } + + return Glob::toRegex($str); + } +} diff --git a/src/Symfony/Components/Finder/Iterator/IgnoreVcsFilterIterator.php b/src/Symfony/Components/Finder/Iterator/IgnoreVcsFilterIterator.php new file mode 100644 index 0000000000..f92a4b21a3 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/IgnoreVcsFilterIterator.php @@ -0,0 +1,34 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * IgnoreVcsFilterIterator filters out VCS files and directories. + * + * It currently supports Subversion, CVS, DARCS, Gnu Arch, Monotone, Bazaar-NG, GIT, and Mercurial. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class IgnoreVcsFilterIterator extends ExcludeDirectoryFilterIterator +{ + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + */ + public function __construct(\Iterator $iterator) + { + parent::__construct($iterator, array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg')); + } +} diff --git a/src/Symfony/Components/Finder/Iterator/LimitDepthFilterIterator.php b/src/Symfony/Components/Finder/Iterator/LimitDepthFilterIterator.php new file mode 100644 index 0000000000..3c2196a2f2 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/LimitDepthFilterIterator.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * LimitDepthFilterIterator limits the directory depth. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class LimitDepthFilterIterator extends \FilterIterator +{ + protected $minDepth = 0; + protected $maxDepth = INF; + protected $baseDir; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param string $baseDir The base directory for the depth comparison + * @param integer $minDepth The minimum depth + * @param integer $maxDepth The maximum depth + */ + public function __construct(\Iterator $iterator, $baseDir, $minDepth, $maxDepth) + { + $this->baseDir = new \SplFileInfo($baseDir); + $this->minDepth = (integer) $minDepth; + $this->maxDepth = (double) $maxDepth; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->getInnerIterator()->current(); + + $depth = substr_count(str_replace('\\', '/', $fileinfo->getPath()), '/') - substr_count(str_replace('\\', '/', $this->baseDir->getPathname()), '/'); + + if ($depth > $this->maxDepth) + { + return false; + } + + if ($depth < $this->minDepth) + { + return false; + } + + return true; + } +} diff --git a/src/Symfony/Components/Finder/Iterator/SizeRangeFilterIterator.php b/src/Symfony/Components/Finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000000..bfbc9876b0 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,63 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + protected $patterns = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $patterns An array of \NumberCompare instances + */ + public function __construct(\Iterator $iterator, array $patterns) + { + $this->patterns = $patterns; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->getInnerIterator()->current(); + + if (!$fileinfo->isFile()) + { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->patterns as $compare) + { + if (!$compare->test($filesize)) + { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Components/Finder/Iterator/SortableIterator.php b/src/Symfony/Components/Finder/Iterator/SortableIterator.php new file mode 100644 index 0000000000..5a9b639545 --- /dev/null +++ b/src/Symfony/Components/Finder/Iterator/SortableIterator.php @@ -0,0 +1,67 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier + */ +class SortableIterator extends \ArrayIterator +{ + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param integer|\Closure $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a \Closure instance) + */ + public function __construct(\Iterator $iterator, $sort) + { + if (!$sort instanceof \Closure && self::SORT_BY_NAME == $sort) + { + $sort = function ($a, $b) + { + return strcmp($a->getRealpath(), $b->getRealpath()); + }; + } + elseif (!$sort instanceof \Closure && self::SORT_BY_TYPE == $sort) + { + $sort = function ($a, $b) + { + if ($a->isDir() && $b->isFile()) + { + return -1; + } + elseif ($a->isFile() && $b->isDir()) + { + return 1; + } + + return strcmp($a->getRealpath(), $b->getRealpath()); + }; + } + elseif (!$sort instanceof \Closure) + { + throw new \InvalidArgumentException(sprintf('The SortableIterator takes a \Closure or a valid built-in sort algorithm as an argument (%s given).', $sort)); + } + + $array = new \ArrayObject(iterator_to_array($iterator)); + $array->uasort($sort); + + parent::__construct($array); + } +} diff --git a/src/Symfony/Components/Finder/NumberCompare.php b/src/Symfony/Components/Finder/NumberCompare.php new file mode 100644 index 0000000000..b9ffb97815 --- /dev/null +++ b/src/Symfony/Components/Finder/NumberCompare.php @@ -0,0 +1,109 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * NumberCompare compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @package Symfony + * @subpackage Components_Finder + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberCompare +{ + protected $target; + protected $comparison; + + /** + * Constructor. + * + * @param string $test A comparison string + */ + public function __construct($test) + { + if (!preg_match('#^\s*([<>=]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) + { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a test.', $test)); + } + + $this->target = $matches[2]; + $this->comparison = isset($matches[1]) ? $matches[1] : '=='; + + $magnitude = strtolower(isset($matches[3]) ? $matches[3] : ''); + switch ($magnitude) + { + case 'k': + $this->target *= 1000; + break; + case 'ki': + $this->target *= 1024; + break; + case 'm': + $this->target *= 1000000; + break; + case 'mi': + $this->target *= 1024*1024; + break; + case 'g': + $this->target *= 1000000000; + break; + case 'gi': + $this->target *= 1024*1024*1024; + break; + } + } + + /** + * Tests a number against the test. + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function test($number) + { + if ($this->comparison === '>') + { + return ($number > $this->target); + } + + if ($this->comparison === '>=') + { + return ($number >= $this->target); + } + + if ($this->comparison === '<') + { + return ($number < $this->target); + } + + if ($this->comparison === '<=') + { + return ($number <= $this->target); + } + + return ($number == $this->target); + } +} diff --git a/src/Symfony/Framework/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php b/src/Symfony/Framework/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php index 741408ee10..c97b2304d0 100644 --- a/src/Symfony/Framework/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php +++ b/src/Symfony/Framework/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php @@ -7,8 +7,8 @@ use Symfony\Components\Console\Input\InputOption; use Symfony\Components\Console\Input\InputInterface; use Symfony\Components\Console\Output\OutputInterface; use Symfony\Components\Console\Output\Output; +use Symfony\Components\Finder\Finder; use Symfony\Framework\WebBundle\Util\Filesystem; -use Symfony\Framework\WebBundle\Util\Finder; use Doctrine\Common\Cli\Configuration; use Doctrine\Common\Cli\CliController as DoctrineCliController; use Doctrine\ORM\EntityManager; @@ -77,10 +77,14 @@ class LoadDataFixturesDoctrineCommand extends DoctrineCommand { if (is_dir($path)) { - $found = Finder::type('file') + $finder = new Finder(); + $found = iterator_to_array($finder + ->files() ->name('*.php') - ->in($path); - } else { + ->in($path)); + } + else + { $found = array($path); } $files = array_merge($files, $found); diff --git a/src/Symfony/Framework/WebBundle/Util/Finder.php b/src/Symfony/Framework/WebBundle/Util/Finder.php deleted file mode 100644 index cb38db59c5..0000000000 --- a/src/Symfony/Framework/WebBundle/Util/Finder.php +++ /dev/null @@ -1,597 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -/** - * Allows to build rules to find files and directories. - * - * All rules may be invoked several times, except for ->in() method. - * Some rules are cumulative (->name() for example) whereas others are destructive - * (most recent value is used, ->maxdepth() method for example). - * - * All methods return the current Finder object to allow easy chaining: - * - * $files = Finder::type('file')->name('*.php')->in(.); - * - * Interface loosely based on perl File::Find::Rule module. - * - * @package Symfony - * @subpackage Framework_WebBundle - * @author Fabien Potencier - */ -class Finder -{ - protected $type = 'file'; - protected $names = array(); - protected $prunes = array(); - protected $discards = array(); - protected $execs = array(); - protected $mindepth = 0; - protected $sizes = array(); - protected $maxdepth = 1000000; - protected $relative = false; - protected $followLinks = false; - protected $sort = false; - protected $ignoreVersionControl = true; - - /** - * Sets maximum directory depth. - * - * Finder will descend at most $level levels of directories below the starting point. - * - * @param int $level - * @return object current Finder object - */ - public function maxDepth($level) - { - $this->maxdepth = $level; - - return $this; - } - - /** - * Sets minimum directory depth. - * - * Finder will start applying tests at level $level. - * - * @param int $level - * @return object current Finder object - */ - public function minDepth($level) - { - $this->mindepth = $level; - - return $this; - } - - public function getType() - { - return $this->type; - } - - /** - * Sets the type of elements to returns. - * - * @param string $name directory or file or any (for both file and directory) - * @return object new Finder object - */ - public static function type($name) - { - $finder = new self(); - return $finder->setType($name); - } - /** - * Sets the type of elements to returns. - * - * @param string $name directory or file or any (for both file and directory) - * @return Finder Current object - */ - public function setType($name) - { - $name = strtolower($name); - - if (substr($name, 0, 3) === 'dir') - { - $this->type = 'directory'; - - return $this; - } - if ($name === 'any') - { - $this->type = 'any'; - - return $this; - } - - $this->type = 'file'; - - return $this; - } - - /* - * glob, patterns (must be //) or strings - */ - protected function toRegex($str) - { - if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $str)) - { - return $str; - } - - return Glob::toRegex($str); - } - - protected function argsToArray($arg_list, $not = false) - { - $list = array(); - $nbArgList = count($arg_list); - for ($i = 0; $i < $nbArgList; $i++) - { - if (is_array($arg_list[$i])) - { - foreach ($arg_list[$i] as $arg) - { - $list[] = array($not, $this->toRegex($arg)); - } - } - else - { - $list[] = array($not, $this->toRegex($arg_list[$i])); - } - } - - return $list; - } - - /** - * Adds rules that files must match. - * - * You can use patterns (delimited with / sign), globs or simple strings. - * - * $finder->name('*.php') - * $finder->name('/\.php$/') // same as above - * $finder->name('test.php') - * - * @param list a list of patterns, globs or strings - * @return Finder Current object - */ - public function name() - { - $args = func_get_args(); - $this->names = array_merge($this->names, $this->argsToArray($args)); - - return $this; - } - - /** - * Adds rules that files must not match. - * - * @see ->name() - * @param list a list of patterns, globs or strings - * @return Finder Current object - */ - public function notName() - { - $args = func_get_args(); - $this->names = array_merge($this->names, $this->argsToArray($args, true)); - - return $this; - } - - /** - * Adds tests for file sizes. - * - * $finder->size('> 10K'); - * $finder->size('<= 1Ki'); - * $finder->size(4); - * - * @param list a list of comparison strings - * @return Finder Current object - */ - public function size() - { - $args = func_get_args(); - $numargs = count($args); - for ($i = 0; $i < $numargs; $i++) - { - $this->sizes[] = new NumberCompare($args[$i]); - } - - return $this; - } - - /** - * Traverses no further. - * - * @param list a list of patterns, globs to match - * @return Finder Current object - */ - public function prune() - { - $args = func_get_args(); - $this->prunes = array_merge($this->prunes, $this->argsToArray($args)); - - return $this; - } - - /** - * Discards elements that matches. - * - * @param list a list of patterns, globs to match - * @return Finder Current object - */ - public function discard() - { - $args = func_get_args(); - $this->discards = array_merge($this->discards, $this->argsToArray($args)); - - return $this; - } - - /** - * Ignores version control directories. - * - * Currently supports Subversion, CVS, DARCS, Gnu Arch, Monotone, Bazaar-NG, GIT, Mercurial - * - * @param bool $ignore false when version control directories shall be included (default is true) - * - * @return Finder Current object - */ - public function ignoreVersionControl($ignore = true) - { - $this->ignoreVersionControl = $ignore; - - return $this; - } - - /** - * Returns files and directories ordered by name - * - * @return Finder Current object - */ - public function sortByName() - { - $this->sort = 'name'; - - return $this; - } - - /** - * Returns files and directories ordered by type (directories before files), then by name - * - * @return Finder Current object - */ - public function sortByType() - { - $this->sort = 'type'; - - return $this; - } - - /** - * Executes function or method for each element. - * - * Element match if function or method returns true. - * - * $finder->exec('myfunction'); - * $finder->exec(array($object, 'mymethod')); - * - * @param mixed function or method to call - * @return Finder Current object - * - * @throws \InvalidArgumentException If function or method does not exist - */ - public function exec() - { - $args = func_get_args(); - $numargs = count($args); - for ($i = 0; $i < $numargs; $i++) - { - if (is_array($args[$i]) && !method_exists($args[$i][0], $args[$i][1])) - { - throw new \InvalidArgumentException(sprintf('Method "%s" does not exist for object "%s".', $args[$i][1], $args[$i][0])); - } - if (!is_array($args[$i]) && !function_exists($args[$i])) - { - throw new \InvalidArgumentException(sprintf('Function "%s" does not exist.', $args[$i])); - } - - $this->execs[] = $args[$i]; - } - - return $this; - } - - /** - * Returns relative paths for all files and directories. - * - * @return Finder Current object - */ - public function relative() - { - $this->relative = true; - - return $this; - } - - /** - * Symlink following. - * - * @return Finder Current object - */ - public function followLinks() - { - $this->followLinks = true; - - return $this; - } - - /** - * Searches files and directories which match defined rules. - * - * @return array list of files and directories - */ - public function in() - { - $files = array(); - $here_dir = getcwd(); - - $finder = clone $this; - - if ($this->ignoreVersionControl) - { - $ignores = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); - - $finder->discard($ignores)->prune($ignores); - } - - // first argument is an array? - $numargs = func_num_args(); - $arg_list = func_get_args(); - if ($numargs === 1 && is_array($arg_list[0])) - { - $arg_list = $arg_list[0]; - $numargs = count($arg_list); - } - - for ($i = 0; $i < $numargs; $i++) - { - $dir = realpath($arg_list[$i]); - - if (!is_dir($dir)) - { - continue; - } - - $dir = str_replace('\\', '/', $dir); - - // absolute path? - if (!self::isPathAbsolute($dir)) - { - $dir = $here_dir.'/'.$dir; - } - - $new_files = str_replace('\\', '/', $finder->searchIn($dir)); - - if ($this->relative) - { - $new_files = str_replace(rtrim($dir, '/').'/', '', $new_files); - } - - $files = array_merge($files, $new_files); - } - - if ($this->sort === 'name') - { - sort($files); - } - - return array_unique($files); - } - - protected function searchIn($dir, $depth = 0) - { - if ($depth > $this->maxdepth) - { - return array(); - } - - $dir = realpath($dir); - - if ((!$this->followLinks) && is_link($dir)) - { - return array(); - } - - $files = array(); - $temp_files = array(); - $temp_folders = array(); - if (is_dir($dir)) - { - $current_dir = opendir($dir); - while (false !== $entryname = readdir($current_dir)) - { - if ($entryname == '.' || $entryname == '..') continue; - - $current_entry = $dir.DIRECTORY_SEPARATOR.$entryname; - if ((!$this->followLinks) && is_link($current_entry)) - { - continue; - } - - if (is_dir($current_entry)) - { - if ($this->sort === 'type') - { - $temp_folders[$entryname] = $current_entry; - } - else - { - if (($this->type === 'directory' || $this->type === 'any') && ($depth >= $this->mindepth) && !$this->isDiscarded($dir, $entryname) && $this->matchNames($dir, $entryname) && $this->execOk($dir, $entryname)) - { - $files[] = $current_entry; - } - - if (!$this->isPruned($dir, $entryname)) - { - $files = array_merge($files, $this->searchIn($current_entry, $depth + 1)); - } - } - } - else - { - if (($this->type !== 'directory' || $this->type === 'any') && ($depth >= $this->mindepth) && !$this->isDiscarded($dir, $entryname) && $this->matchNames($dir, $entryname) && $this->sizeOk($dir, $entryname) && $this->execOk($dir, $entryname)) - { - if ($this->sort === 'type') - { - $temp_files[] = $current_entry; - } - else - { - $files[] = $current_entry; - } - } - } - } - - if ($this->sort === 'type') - { - ksort($temp_folders); - foreach($temp_folders as $entryname => $current_entry) - { - if (($this->type === 'directory' || $this->type === 'any') && ($depth >= $this->mindepth) && !$this->isDiscarded($dir, $entryname) && $this->matchNames($dir, $entryname) && $this->execOk($dir, $entryname)) - { - $files[] = $current_entry; - } - - if (!$this->isPruned($dir, $entryname)) - { - $files = array_merge($files, $this->searchIn($current_entry, $depth + 1)); - } - } - - sort($temp_files); - $files = array_merge($files, $temp_files); - } - - closedir($current_dir); - } - - return $files; - } - - protected function matchNames($dir, $entry) - { - if (!count($this->names)) return true; - - // Flags indicating that there was attempts to match - // at least one "not_name" or "name" rule respectively - // to following variables: - $one_not_name_rule = false; - $one_name_rule = false; - - foreach ($this->names as $args) - { - list($not, $regex) = $args; - $not ? $one_not_name_rule = true : $one_name_rule = true; - if (preg_match($regex, $entry)) - { - // We must match ONLY ONE "not_name" or "name" rule: - // if "not_name" rule matched then we return "false" - // if "name" rule matched then we return "true" - return $not ? false : true; - } - } - - if ($one_not_name_rule && $one_name_rule) - { - return false; - } - else if ($one_not_name_rule) - { - return true; - } - else if ($one_name_rule) - { - return false; - } - return true; - } - - protected function sizeOk($dir, $entry) - { - if (0 === count($this->sizes)) return true; - - if (!is_file($dir.DIRECTORY_SEPARATOR.$entry)) return true; - - $filesize = filesize($dir.DIRECTORY_SEPARATOR.$entry); - foreach ($this->sizes as $number_compare) - { - if (!$number_compare->test($filesize)) return false; - } - - return true; - } - - protected function isPruned($dir, $entry) - { - if (0 === count($this->prunes)) return false; - - foreach ($this->prunes as $args) - { - $regex = $args[1]; - if (preg_match($regex, $entry)) return true; - } - - return false; - } - - protected function isDiscarded($dir, $entry) - { - if (0 === count($this->discards)) return false; - - foreach ($this->discards as $args) - { - $regex = $args[1]; - if (preg_match($regex, $entry)) return true; - } - - return false; - } - - protected function execOk($dir, $entry) - { - if (0 === count($this->execs)) return true; - - foreach ($this->execs as $exec) - { - if (!call_user_func_array($exec, array($dir, $entry))) return false; - } - - return true; - } - - public static function isPathAbsolute($path) - { - if ($path{0} === '/' || $path{0} === '\\' || - (strlen($path) > 3 && ctype_alpha($path{0}) && - $path{1} === ':' && - ($path{2} === '\\' || $path{2} === '/') - ) - ) - { - return true; - } - - return false; - } -} diff --git a/src/Symfony/Framework/WebBundle/Util/NumberCompare.php b/src/Symfony/Framework/WebBundle/Util/NumberCompare.php deleted file mode 100644 index 0be9ef49a6..0000000000 --- a/src/Symfony/Framework/WebBundle/Util/NumberCompare.php +++ /dev/null @@ -1,89 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -/** - * Numeric comparisons. - * - * NumberCompare compiles a simple comparison to an anonymous - * subroutine, which you can call with a value to be tested again. - - * Now this would be very pointless, if NumberCompare didn't understand - * magnitudes. - - * The target value may use magnitudes of kilobytes (k, ki), - * megabytes (m, mi), or gigabytes (g, gi). Those suffixed - * with an i use the appropriate 2**n version in accordance with the - * IEC standard: http://physics.nist.gov/cuu/Units/binary.html - * - * based on perl Number::Compare module. - * - * @package Symfony - * @subpackage Framework_WebBundle - * @author Fabien Potencier php port - * @author Richard Clamp perl version - * @copyright 2004-2005 Fabien Potencier - * @copyright 2002 Richard Clamp - * @see http://physics.nist.gov/cuu/Units/binary.html - */ -class NumberCompare -{ - protected $test = ''; - - public function __construct($test) - { - $this->test = $test; - } - /** - * @throws \RuntimeException If the test is not understood - */ - public function test($number) - { - if (!preg_match('{^([<>]=?)?(.*?)([kmg]i?)?$}i', $this->test, $matches)) - { - throw new \RuntimeException(sprintf('Don\'t understand "%s" as a test.', $this->test)); - } - - $target = array_key_exists(2, $matches) ? $matches[2] : ''; - $magnitude = array_key_exists(3, $matches) ? $matches[3] : ''; - if (strtolower($magnitude) === 'k') $target *= 1000; - if (strtolower($magnitude) === 'ki') $target *= 1024; - if (strtolower($magnitude) === 'm') $target *= 1000000; - if (strtolower($magnitude) === 'mi') $target *= 1024*1024; - if (strtolower($magnitude) === 'g') $target *= 1000000000; - if (strtolower($magnitude) === 'gi') $target *= 1024*1024*1024; - - $comparison = array_key_exists(1, $matches) ? $matches[1] : '=='; - if ($comparison === '==' || $comparison == '') - { - return ($number == $target); - } - if ($comparison === '>') - { - return ($number > $target); - } - if ($comparison === '>=') - { - return ($number >= $target); - } - if ($comparison === '<') - { - return ($number < $target); - } - if ($comparison === '<=') - { - return ($number <= $target); - } - - return false; - } -} diff --git a/tests/Symfony/Tests/Components/Finder/FinderTest.php b/tests/Symfony/Tests/Components/Finder/FinderTest.php new file mode 100644 index 0000000000..7d69853959 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/FinderTest.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder; + +use Symfony\Components\Finder\Finder; + +require_once __DIR__.'/Iterator/RealIteratorTestCase.php'; + +class FinderTest extends Iterator\RealIteratorTestCase +{ + static protected $tmpDir; + + static public function setUpBeforeClass() + { + parent::setUpBeforeClass(); + + self::$tmpDir = sys_get_temp_dir().'/symfony2_finder/'; + } + + public function testDirectories() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->directories()); + $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)); + + $finder = new Finder(); + $finder->directories(); + $finder->files(); + $finder->directories(); + $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)); + } + + public function testFiles() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->files()); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)); + + $finder = new Finder(); + $finder->files(); + $finder->directories(); + $finder->files(); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)); + } + + public function testMaxDepth() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->maxDepth(0)); + $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + } + + public function testMinDepth() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->minDepth(1)); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)); + } + + public function testMinMaxDepth() + { + $finder = new Finder(); + $finder->maxDepth(0); + $finder->minDepth(1); + $this->assertIterator(array(), $finder->in(self::$tmpDir)); + } + + public function testName() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->name('*.php')); + $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)); + + $finder = new Finder(); + $finder->name('test.ph*'); + $finder->name('test.py'); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)); + } + + public function testNotName() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->notName('*.php')); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + + $finder = new Finder(); + $finder->notName('*.php'); + $finder->notName('*.py'); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->in(self::$tmpDir)); + + $finder = new Finder(); + $finder->name('test.ph*'); + $finder->name('test.py'); + $finder->notName('*.php'); + $finder->notName('*.py'); + $this->assertIterator(array(), $finder->in(self::$tmpDir)); + } + + public function testSize() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500')); + $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)); + } + + public function testExclude() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->exclude('foo')); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + } + + public function testIgnoreVCS() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->ignoreVCS(false)); + $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + + $finder = new Finder(); + $this->assertSame($finder, $finder->ignoreVCS(true)); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + } + + public function testSortByName() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->sortByName()); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + } + + public function testSortByType() + { + $finder = new Finder(); + $this->assertSame($finder, $finder->sortByType()); + $this->assertIterator($this->toAbsolute(array('foo', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)); + } + + public function testSort() + { + $finder = new Finder(); + $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)); + } + + public function testFilter() + { + $finder = new Finder(); + $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)); + } + + public function testFollowLinks() + { + if ('\\' == DIRECTORY_SEPARATOR) + { + return; + } + + $finder = new Finder(); + $this->assertSame($finder, $finder->followLinks()); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)); + } + + public function testIn() + { + $finder = new Finder(); + try + { + $finder->in('foobar'); + $this->fail('->in() throws a \InvalidArgumentException if the directory does not exist'); + } + catch (\Exception $e) + { + $this->assertInstanceOf('InvalidArgumentException', $e, '->in() throws a \InvalidArgumentException if the directory does not exist'); + } + + $finder = new Finder(); + $iterator = $finder->files()->name('*.php')->maxDepth(0)->in(array(self::$tmpDir, __DIR__)); + + $this->assertIterator(array(self::$tmpDir.'test.php', __DIR__.'/FinderTest.php', __DIR__.'/GlobTest.php', __DIR__.'/NumberCompareTest.php'), $iterator); + } + + protected function toAbsolute($files) + { + $f = array(); + foreach ($files as $file) + { + $f[] = self::$tmpDir.$file; + } + + return $f; + } +} diff --git a/tests/Symfony/Tests/Components/Finder/GlobTest.php b/tests/Symfony/Tests/Components/Finder/GlobTest.php new file mode 100644 index 0000000000..d66ed0a59b --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/GlobTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder; + +use Symfony\Components\Finder\Glob; + +class GlobTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getToRegexData + */ + public function testToRegex($glob, $match, $noMatch) + { + foreach ($match as $m) + { + $this->assertRegExp(Glob::toRegex($glob), $m, '::toRegex() converts a glob to a regexp'); + } + + foreach ($noMatch as $m) + { + $this->assertNotRegExp(Glob::toRegex($glob), $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/tests/Symfony/Tests/Components/Finder/Iterator/ChainIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/ChainIteratorTest.php new file mode 100644 index 0000000000..d807808bf3 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/ChainIteratorTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\ChainIterator; + +require_once __DIR__.'/IteratorTestCase.php'; + +class ChainIteratorTest extends IteratorTestCase +{ + public function testAccept() + { + $inner1 = new Iterator(array('test.php', 'test.py')); + $inner2 = new Iterator(array()); + $inner3 = new Iterator(array('foo.php')); + + $iterator = new ChainIterator(array($inner1, $inner2, $inner3)); + + $this->assertIterator(array('test.php', 'test.py', 'foo.php'), $iterator); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/CustomFilterIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/CustomFilterIteratorTest.php new file mode 100644 index 0000000000..59ed27fc01 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/CustomFilterIteratorTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\CustomFilterIterator; + +require_once __DIR__.'/IteratorTestCase.php'; + +class CustomFilterIteratorTest extends IteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($filters, $expected) + { + $inner = new Iterator(array('test.php', 'test.py', 'foo.php')); + + $iterator = new CustomFilterIterator($inner, $filters); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(array(function (\SplFileInfo $fileinfo) { return false; }), array()), + array(array(function (\SplFileInfo $fileinfo) { return preg_match('/^test/', $fileinfo) > 0; }), array('test.php', 'test.py')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/ExcludeDirectoryFileIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/ExcludeDirectoryFileIteratorTest.php new file mode 100644 index 0000000000..e90403a0d4 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/ExcludeDirectoryFileIteratorTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\ExcludeDirectoryFilterIterator; + +require_once __DIR__.'/IteratorTestCase.php'; + +class ExcludeDirectoryFilterIteratorTest extends IteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($directories, $expected) + { + $inner = new Iterator(array('/foo/test.php', '/foo/test.py', '/bar/foo.php')); + + $iterator = new ExcludeDirectoryFilterIterator($inner, $directories); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(array('foo'), array('/bar/foo.php')), + array(array('fo'), array('/foo/test.php', '/foo/test.py', '/bar/foo.php')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/FileTypeFilterIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/FileTypeFilterIteratorTest.php new file mode 100644 index 0000000000..d453614997 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/FileTypeFilterIteratorTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\FileTypeFilterIterator; + +require_once __DIR__.'/RealIteratorTestCase.php'; + +class FileTypeFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($mode, $expected) + { + $inner = new Iterator(self::$files); + + $iterator = new FileTypeFilterIterator($inner, $mode); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(FileTypeFilterIterator::ONLY_FILES, array(sys_get_temp_dir().'/symfony2_finder/test.py', sys_get_temp_dir().'/symfony2_finder/foo/bar.tmp', sys_get_temp_dir().'/symfony2_finder/test.php')), + array(FileTypeFilterIterator::ONLY_DIRECTORIES, array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/toto')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/FilenameFilterIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/FilenameFilterIteratorTest.php new file mode 100644 index 0000000000..4ab12198a8 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/FilenameFilterIteratorTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\FilenameFilterIterator; + +require_once __DIR__.'/IteratorTestCase.php'; + +class FilenameFilterIteratorTest extends IteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($matchPatterns, $noMatchPatterns, $expected) + { + $inner = new Iterator(array('test.php', 'test.py', 'foo.php')); + + $iterator = new FilenameFilterIterator($inner, $matchPatterns, $noMatchPatterns); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(array('test.*'), array(), array('test.php', 'test.py')), + array(array(), array('test.*'), array('foo.php')), + array(array('*.php'), array('test.*'), array('foo.php')), + array(array('*.php', '*.py'), array('foo.*'), array('test.php', 'test.py')), + array(array('/\.php$/'), array(), array('test.php', 'foo.php')), + array(array(), array('/\.php$/'), array('test.py')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/IgnoreVcsFilterIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/IgnoreVcsFilterIteratorTest.php new file mode 100644 index 0000000000..d9058ef77a --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/IgnoreVcsFilterIteratorTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\IgnoreVcsFilterIterator; + +require_once __DIR__.'/IteratorTestCase.php'; + +class IgnoreVcsFilterIteratorTest extends IteratorTestCase +{ + public function testAccept() + { + $inner = new Iterator(array('/.git/test.php', '/foo/test.py', '/bar/foo.php')); + + $iterator = new IgnoreVcsFilterIterator($inner); + + $this->assertIterator(array('/foo/test.py', '/bar/foo.php'), $iterator); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/Iterator.php b/tests/Symfony/Tests/Components/Finder/Iterator/Iterator.php new file mode 100644 index 0000000000..20d1104996 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/Iterator.php @@ -0,0 +1,56 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ +class Iterator implements \Iterator +{ + protected $values; + + public function __construct(array $values = array()) + { + $this->values = array(); + foreach ($values as $value) + { + $this->attach(new \SplFileInfo($value)); + } + $this->rewind(); + } + + public function attach(\SplFileInfo $fileinfo) + { + $this->values[] = $fileinfo; + } + + public function rewind() + { + reset($this->values); + } + + public function valid() + { + return false !== $this->current(); + } + + public function next() + { + next($this->values); + } + + public function current() + { + return current($this->values); + } + + public function key() + { + return key($this->values); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/IteratorTestCase.php b/tests/Symfony/Tests/Components/Finder/Iterator/IteratorTestCase.php new file mode 100644 index 0000000000..6a18352ded --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/IteratorTestCase.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +require_once __DIR__.'/Iterator.php'; + +class IteratorTestCase extends \PHPUnit_Framework_TestCase +{ + protected function assertIterator($expected, \Iterator $iterator) + { + $values = array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator)); + + $this->assertEquals($expected, array_values($values)); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/LimitDepthFilterIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/LimitDepthFilterIteratorTest.php new file mode 100644 index 0000000000..4d7353e9f0 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/LimitDepthFilterIteratorTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\LimitDepthFilterIterator; + +require_once __DIR__.'/RealIteratorTestCase.php'; + +class LimitDepthFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($baseDir, $minDepth, $maxDepth, $expected) + { + $inner = new Iterator(self::$files); + + $iterator = new LimitDepthFilterIterator($inner, $baseDir, $minDepth, $maxDepth); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(sys_get_temp_dir().'/symfony2_finder', 0, INF, array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/test.py', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/foo/bar.tmp', sys_get_temp_dir().'/symfony2_finder/test.php', sys_get_temp_dir().'/symfony2_finder/toto')), + array(sys_get_temp_dir().'/symfony2_finder', 0, 0, array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/test.py', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/test.php', sys_get_temp_dir().'/symfony2_finder/toto')), + array(sys_get_temp_dir().'/symfony2_finder', 1, 1, array(sys_get_temp_dir().'/symfony2_finder/foo/bar.tmp')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/RealIteratorTestCase.php b/tests/Symfony/Tests/Components/Finder/Iterator/RealIteratorTestCase.php new file mode 100644 index 0000000000..2ddc349944 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/RealIteratorTestCase.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +require_once __DIR__.'/IteratorTestCase.php'; + +class RealIteratorTestCase extends IteratorTestCase +{ + static protected $files; + + static public function setUpBeforeClass() + { + $tmpDir = sys_get_temp_dir().'/symfony2_finder'; + self::$files = array($tmpDir.'/.git', $tmpDir.'/test.py', $tmpDir.'/foo', $tmpDir.'/foo/bar.tmp', $tmpDir.'/test.php', $tmpDir.'/toto'); + + if (is_dir($tmpDir)) + { + self::tearDownAfterClass(); + rmdir($tmpDir); + } + mkdir($tmpDir); + + foreach (self::$files as $file) + { + if (false !== ($pos = strpos($file, '.')) && '/' !== $file[$pos - 1]) + { + touch($file); + } + else + { + mkdir($file); + } + } + + file_put_contents($tmpDir.'/test.php', str_repeat(' ', 800)); + file_put_contents($tmpDir.'/test.py', str_repeat(' ', 2000)); + } + + static public function tearDownAfterClass() + { + foreach (self::$files as $file) + { + if (false !== ($pos = strpos($file, '.')) && '/' !== $file[$pos - 1]) + { + @unlink($file); + } + else + { + @rmdir($file); + } + } + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/SizeRangeFilterIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/SizeRangeFilterIteratorTest.php new file mode 100644 index 0000000000..8a1606b68a --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/SizeRangeFilterIteratorTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Components\Finder\NumberCompare; + +require_once __DIR__.'/RealIteratorTestCase.php'; + +class SizeRangeFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($size, $expected) + { + $inner = new Iterator(self::$files); + + $iterator = new SizeRangeFilterIterator($inner, $size); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(array(new NumberCompare('< 1K'), new NumberCompare('> 0.5K')), array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/test.php', sys_get_temp_dir().'/symfony2_finder/toto')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/Iterator/SortableIteratorTest.php b/tests/Symfony/Tests/Components/Finder/Iterator/SortableIteratorTest.php new file mode 100644 index 0000000000..ad052694d7 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/Iterator/SortableIteratorTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder\Iterator; + +use Symfony\Components\Finder\Iterator\SortableIterator; + +require_once __DIR__.'/RealIteratorTestCase.php'; + +class SortableIteratorTest extends RealIteratorTestCase +{ + public function testConstructor() + { + try + { + new SortableIterator(new Iterator(array()), 'foobar'); + $this->fail('__construct() throws an \InvalidArgumentException exception if the mode is not valid'); + } + catch (\Exception $e) + { + $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException exception if the mode is not valid'); + } + } + + /** + * @dataProvider getAcceptData + */ + public function testAccept($mode, $expected) + { + $inner = new Iterator(self::$files); + + $iterator = new SortableIterator($inner, $mode); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(SortableIterator::SORT_BY_NAME, array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/foo/bar.tmp', sys_get_temp_dir().'/symfony2_finder/test.php', sys_get_temp_dir().'/symfony2_finder/test.py', sys_get_temp_dir().'/symfony2_finder/toto')), + array(SortableIterator::SORT_BY_TYPE, array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/toto', sys_get_temp_dir().'/symfony2_finder/foo/bar.tmp', sys_get_temp_dir().'/symfony2_finder/test.php', sys_get_temp_dir().'/symfony2_finder/test.py')), + array(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }, array(sys_get_temp_dir().'/symfony2_finder/.git', sys_get_temp_dir().'/symfony2_finder/foo', sys_get_temp_dir().'/symfony2_finder/foo/bar.tmp', sys_get_temp_dir().'/symfony2_finder/test.php', sys_get_temp_dir().'/symfony2_finder/test.py', sys_get_temp_dir().'/symfony2_finder/toto')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/Finder/NumberCompareTest.php b/tests/Symfony/Tests/Components/Finder/NumberCompareTest.php new file mode 100644 index 0000000000..0a30c14d49 --- /dev/null +++ b/tests/Symfony/Tests/Components/Finder/NumberCompareTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\Finder; + +use Symfony\Components\Finder\NumberCompare; + +class NumberCompareTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + try + { + new NumberCompare('foobar'); + $this->fail('->test() throws an \InvalidArgumentException if the test expression is not valid.'); + } + catch (\Exception $e) + { + $this->assertInstanceOf('InvalidArgumentException', $e, '->test() throws an \InvalidArgumentException if the test expression is not valid.'); + } + } + + /** + * @dataProvider getTestData + */ + public function testTest($test, $match, $noMatch) + { + foreach ($match as $m) + { + $c = new NumberCompare($test); + $this->assertTrue($c->test($m), '->test() tests a string against the expression'); + } + + foreach ($noMatch as $m) + { + $c = new NumberCompare($test); + $this->assertFalse($c->test($m), '->test() tests a string against the expression'); + } + } + + public function getTestData() + { + return array( + array('< 1000', array('500', '999'), array('1000', '1500')), + + array('< 1K', array('500', '999'), array('1000', '1500')), + array('<1k', array('500', '999'), array('1000', '1500')), + array(' < 1 K ', array('500', '999'), array('1000', '1500')), + array('<= 1K', array('1000'), array('1001')), + array('> 1K', array('1001'), array('1000')), + array('>= 1K', array('1000'), array('999')), + + array('< 1KI', array('500', '1023'), array('1024', '1500')), + array('<= 1KI', array('1024'), array('1025')), + array('> 1KI', array('1025'), array('1024')), + array('>= 1KI', array('1024'), array('1023')), + + array('1KI', array('1024'), array('1023', '1025')), + array('==1KI', array('1024'), array('1023', '1025')), + + array('==1m', array('1000000'), array('999999', '1000001')), + array('==1mi', array(1024*1024), array(1024*1024-1, 1024*1024+1)), + + array('==1g', array('1000000000'), array('999999999', '1000000001')), + array('==1gi', array(1024*1024*1024), array(1024*1024*1024-1, 1024*1024*1024+1)), + ); + } +}