Viewing file: FileFinder.php (15.77 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
/** * Hoa * * * @license * * New BSD License * * Copyright © 2007-2017, Hoa community. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the Hoa nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */
namespace Psy\Readline\Hoa;
/** * Class \Hoa\File\Finder. * * This class allows to find files easily by using filters and flags. */ class FileFinder implements \IteratorAggregate { /** * SplFileInfo classname. */ protected $_splFileInfo = \SplFileInfo::class;
/** * Paths where to look for. */ protected $_paths = [];
/** * Max depth in recursion. */ protected $_maxDepth = -1;
/** * Filters. */ protected $_filters = [];
/** * Flags. */ protected $_flags = -1;
/** * Types of files to handle. */ protected $_types = [];
/** * What comes first: parent or child? */ protected $_first = -1;
/** * Sorts. */ protected $_sorts = [];
/** * Initialize. */ public function __construct() { $this->_flags = IteratorFileSystem::KEY_AS_PATHNAME | IteratorFileSystem::CURRENT_AS_FILEINFO | IteratorFileSystem::SKIP_DOTS; $this->_first = \RecursiveIteratorIterator::SELF_FIRST;
return; }
/** * Select a directory to scan. */ public function in($paths): self { if (!\is_array($paths)) { $paths = [$paths]; }
foreach ($paths as $path) { if (1 === \preg_match('/[\*\?\[\]]/', $path)) { $iterator = new \CallbackFilterIterator( new \GlobIterator(\rtrim($path, \DIRECTORY_SEPARATOR)), function ($current) { return $current->isDir(); } );
foreach ($iterator as $fileInfo) { $this->_paths[] = $fileInfo->getPathname(); } } else { $this->_paths[] = $path; } }
return $this; }
/** * Set max depth for recursion. */ public function maxDepth(int $depth): self { $this->_maxDepth = $depth;
return $this; }
/** * Include files in the result. */ public function files(): self { $this->_types[] = 'file';
return $this; }
/** * Include directories in the result. */ public function directories(): self { $this->_types[] = 'dir';
return $this; }
/** * Include links in the result. */ public function links(): self { $this->_types[] = 'link';
return $this; }
/** * Follow symbolink links. */ public function followSymlinks(bool $flag = true): self { if (true === $flag) { $this->_flags ^= IteratorFileSystem::FOLLOW_SYMLINKS; } else { $this->_flags |= IteratorFileSystem::FOLLOW_SYMLINKS; }
return $this; }
/** * Include files that match a regex. * Example: * $this->name('#\.php$#');. */ public function name(string $regex): self { $this->_filters[] = function (\SplFileInfo $current) use ($regex) { return 0 !== \preg_match($regex, $current->getBasename()); };
return $this; }
/** * Exclude directories that match a regex. * Example: * $this->notIn('#^\.(git|hg)$#');. */ public function notIn(string $regex): self { $this->_filters[] = function (\SplFileInfo $current) use ($regex) { foreach (\explode(\DIRECTORY_SEPARATOR, $current->getPathname()) as $part) { if (0 !== \preg_match($regex, $part)) { return false; } }
return true; };
return $this; }
/** * Include files that respect a certain size. * The size is a string of the form: * operator number unit * where * • operator could be: <, <=, >, >= or =; * • number is a positive integer; * • unit could be: b (default), Kb, Mb, Gb, Tb, Pb, Eb, Zb, Yb. * Example: * $this->size('>= 12Kb');. */ public function size(string $size): self { if (0 === \preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) { return $this; }
$number = (float) ($matches[2]); $unit = $matches[3] ?? 'b'; $operator = $matches[1];
switch ($unit) { case 'b': break;
// kilo case 'Kb': $number <<= 10;
break;
// mega. case 'Mb': $number <<= 20;
break;
// giga. case 'Gb': $number <<= 30;
break;
// tera. case 'Tb': $number *= 1099511627776;
break;
// peta. case 'Pb': $number *= 1024 ** 5;
break;
// exa. case 'Eb': $number *= 1024 ** 6;
break;
// zetta. case 'Zb': $number *= 1024 ** 7;
break;
// yota. case 'Yb': $number *= 1024 ** 8;
break; }
$filter = null;
switch ($operator) { case '<': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() < $number; };
break;
case '<=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() <= $number; };
break;
case '>': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() > $number; };
break;
case '>=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() >= $number; };
break;
case '=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() === $number; };
break; }
$this->_filters[] = $filter;
return $this; }
/** * Whether we should include dots or not (respectively . and ..). */ public function dots(bool $flag = true): self { if (true === $flag) { $this->_flags ^= IteratorFileSystem::SKIP_DOTS; } else { $this->_flags |= IteratorFileSystem::SKIP_DOTS; }
return $this; }
/** * Include files that are owned by a certain owner. */ public function owner(int $owner): self { $this->_filters[] = function (\SplFileInfo $current) use ($owner) { return $current->getOwner() === $owner; };
return $this; }
/** * Format date. * Date can have the following syntax: * date * since date * until date * If the date does not have the “ago” keyword, it will be added. * Example: “42 hours” is equivalent to “since 42 hours” which is equivalent * to “since 42 hours ago”. */ protected function formatDate(string $date, &$operator): int { $operator = -1;
if (0 === \preg_match('#\bago\b#', $date)) { $date .= ' ago'; }
if (0 !== \preg_match('#^(since|until)\b(.+)$#', $date, $matches)) { $time = \strtotime($matches[2]);
if ('until' === $matches[1]) { $operator = 1; } } else { $time = \strtotime($date); }
return $time; }
/** * Include files that have been changed from a certain date. * Example: * $this->changed('since 13 days');. */ public function changed(string $date): self { $time = $this->formatDate($date, $operator);
if (-1 === $operator) { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getCTime() >= $time; }; } else { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getCTime() < $time; }; }
return $this; }
/** * Include files that have been modified from a certain date. * Example: * $this->modified('since 13 days');. */ public function modified(string $date): self { $time = $this->formatDate($date, $operator);
if (-1 === $operator) { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getMTime() >= $time; }; } else { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getMTime() < $time; }; }
return $this; }
/** * Add your own filter. * The callback will receive 3 arguments: $current, $key and $iterator. It * must return a boolean: true to include the file, false to exclude it. * Example: * // Include files that are readable * $this->filter(function ($current) { * return $current->isReadable(); * });. */ public function filter($callback): self { $this->_filters[] = $callback;
return $this; }
/** * Sort result by name. * If \Collator exists (from ext/intl), the $locale argument will be used * for its constructor. Else, strcmp() will be used. * Example: * $this->sortByName('fr_FR');. */ public function sortByName(string $locale = 'root'): self { if (true === \class_exists('Collator', false)) { $collator = new \Collator($locale);
$this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) { return $collator->compare($a->getPathname(), $b->getPathname()); }; } else { $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { return \strcmp($a->getPathname(), $b->getPathname()); }; }
return $this; }
/** * Sort result by size. * Example: * $this->sortBySize();. */ public function sortBySize(): self { $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { return $a->getSize() < $b->getSize(); };
return $this; }
/** * Add your own sort. * The callback will receive 2 arguments: $a and $b. Please see the uasort() * function. * Example: * // Sort files by their modified time. * $this->sort(function ($a, $b) { * return $a->getMTime() < $b->getMTime(); * });. */ public function sort($callable): self { $this->_sorts[] = $callable;
return $this; }
/** * Child comes first when iterating. */ public function childFirst(): self { $this->_first = \RecursiveIteratorIterator::CHILD_FIRST;
return $this; }
/** * Get the iterator. */ public function getIterator() { $_iterator = new \AppendIterator(); $types = $this->getTypes();
if (!empty($types)) { $this->_filters[] = function (\SplFileInfo $current) use ($types) { return \in_array($current->getType(), $types); }; }
$maxDepth = $this->getMaxDepth(); $splFileInfo = $this->getSplFileInfo();
foreach ($this->getPaths() as $path) { if (1 === $maxDepth) { $iterator = new \IteratorIterator( new IteratorRecursiveDirectory( $path, $this->getFlags(), $splFileInfo ), $this->getFirst() ); } else { $iterator = new \RecursiveIteratorIterator( new IteratorRecursiveDirectory( $path, $this->getFlags(), $splFileInfo ), $this->getFirst() );
if (1 < $maxDepth) { $iterator->setMaxDepth($maxDepth - 1); } }
$_iterator->append($iterator); }
foreach ($this->getFilters() as $filter) { $_iterator = new \CallbackFilterIterator( $_iterator, $filter ); }
$sorts = $this->getSorts();
if (empty($sorts)) { return $_iterator; }
$array = \iterator_to_array($_iterator);
foreach ($sorts as $sort) { \uasort($array, $sort); }
return new \ArrayIterator($array); }
/** * Set SplFileInfo classname. */ public function setSplFileInfo(string $splFileInfo): string { $old = $this->_splFileInfo; $this->_splFileInfo = $splFileInfo;
return $old; }
/** * Get SplFileInfo classname. */ public function getSplFileInfo(): string { return $this->_splFileInfo; }
/** * Get all paths. */ protected function getPaths(): array { return $this->_paths; }
/** * Get max depth. */ public function getMaxDepth(): int { return $this->_maxDepth; }
/** * Get types. */ public function getTypes(): array { return $this->_types; }
/** * Get filters. */ protected function getFilters(): array { return $this->_filters; }
/** * Get sorts. */ protected function getSorts(): array { return $this->_sorts; }
/** * Get flags. */ public function getFlags(): int { return $this->_flags; }
/** * Get first. */ public function getFirst(): int { return $this->_first; } }
|