Blame | Letzte Änderung | Log anzeigen | RSS feed
<?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;// kilocase '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;}}