file_cache_준비

This commit is contained in:
2020-06-26 21:24:24 +09:00
parent 05904d31fd
commit 40968b301a
60 changed files with 8621 additions and 109 deletions
+42
View File
@@ -0,0 +1,42 @@
{
"name": "nette/finder",
"description": "🔍 Nette Finder: find files and directories with an intuitive API.",
"keywords": ["nette", "filesystem", "iterator", "glob"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.1",
"nette/utils": "^2.4 || ^3.0"
},
"require-dev": {
"nette/tester": "^2.0",
"tracy/tracy": "^2.3",
"phpstan/phpstan": "^0.12"
},
"conflict": {
"nette/nette": "<2.2"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse --level 5 src",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "2.5-dev"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
+60
View File
@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
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 "Nette Framework" 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 owner or 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.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
+181
View File
@@ -0,0 +1,181 @@
Nette Finder: Files Searching
=============================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/finder.svg)](https://packagist.org/packages/nette/finder)
[![Build Status](https://travis-ci.org/nette/finder.svg?branch=master)](https://travis-ci.org/nette/finder)
[![Coverage Status](https://coveralls.io/repos/github/nette/finder/badge.svg?branch=master)](https://coveralls.io/github/nette/finder?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/finder/v/stable)](https://github.com/nette/finder/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/finder/blob/master/license.md)
Introduction
------------
Nette Finder makes browsing the directory structure really easy.
Documentation can be found on the [website](https://doc.nette.org/finder).
If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you!
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/finder
```
It requires PHP version 7.1 and supports PHP up to 7.4.
Usage
-----
How to find all `*.txt` files in `$dir` directory without recursing subdirectories?
```php
foreach (Finder::findFiles('*.txt')->in($dir) as $key => $file) {
echo $key; // $key is a string containing absolute filename with path
echo $file; // $file is an instance of SplFileInfo
}
```
As a result, the finder returns instances of `SplFileInfo`.
If the directory does not exist, an `UnexpectedValueException` is thrown.
And what about searching for `*.txt` files in `$dir` including subdirectories? Instead of `in()`, use `from()`:
```php
foreach (Finder::findFiles('*.txt')->from($dir) as $file) {
echo $file;
}
```
Search by more masks, even inside more directories within one iteration:
```php
foreach (Finder::findFiles('*.txt', '*.php')
->in($dir1, $dir2) as $file) {
...
}
```
Parameters can also be arrays:
```php
foreach (Finder::findFiles($masks)->in($dirs) as $file) {
...
}
```
Searching for `*.txt` files containing a number in the name:
```php
foreach (Finder::findFiles('*[0-9]*.txt')->from($dir) as $file) {
...
}
```
Searching for `*.txt` files, except those containing '`X`' in the name:
```php
foreach (Finder::findFiles('*.txt')
->exclude('*X*')->from($dir) as $file) {
...
}
```
`exclude()` is specified just after `findFiles()`, thus it applies to filename.
Directories to omit can be specified using the `exclude` **after** `from` clause:
```php
foreach (Finder::findFiles('*.php')
->from($dir)->exclude('temp', '.git') as $file) {
...
}
```
Here `exclude()` is after `from()`, thus it applies to the directory name.
And now something a bit more complicated: searching for `*.txt` files located in subdirectories starting with '`te`', but not '`temp`':
```php
foreach (Finder::findFiles('te*/*.txt')
->exclude('temp*/*')->from($dir) as $file) {
...
}
```
Depth of search can be limited using the `limitDepth()` method.
Searching for directories
-------------------------
In addition to files, it is possible to search for directories using `Finder::findDirectories('subdir*')`, or to search for files and directories: `Finder::find('file.txt')`.
Filtering
---------
You can also filter results. For example by size. This way we will traverse the files of size between 100B and 200B:
```php
foreach (Finder::findFiles('*.php')->size('>=', 100)->size('<=', 200)
->from($dir) as $file) {
...
}
```
Or files changed in the last two weeks:
```php
foreach (Finder::findFiles('*.php')->date('>', '- 2 weeks')
->from($dir) as $file) {
...
}
```
Here we traverse PHP files with number of lines greater than 1000. As a filter we use a custom callback:
```php
$finder = Finder::findFiles('*.php')->filter(function($file) {
return count(file($file->getPathname())) > 1000;
})->from($dir);
```
Finder, find images larger than 50px × 50px:
```php
foreach (Finder::findFiles('*')
->dimensions('>50', '>50')->from($dir) as $file) {
...
}
```
Connection to Amazon S3
-----------------------
It's possible to use custom streams, for example Zend_Service_Amazon_S3:
```php
$s3 = new Zend_Service_Amazon_S3($key, $secret);
$s3->registerStreamWrapper('s3');
foreach (Finder::findFiles('photos*')
->size('<=', 1e6)->in('s3://bucket-name') as $file) {
echo $file;
}
```
Handy, right? You will certainly find a use for Finder in your applications.
+380
View File
@@ -0,0 +1,380 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Utils;
use Nette;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
/**
* Finder allows searching through directory trees using iterator.
*
* <code>
* Finder::findFiles('*.php')
* ->size('> 10kB')
* ->from('.')
* ->exclude('temp');
* </code>
*/
class Finder implements \IteratorAggregate, \Countable
{
use Nette\SmartObject;
/** @var callable extension methods */
private static $extMethods = [];
/** @var array */
private $paths = [];
/** @var array of filters */
private $groups = [];
/** @var array filter for recursive traversing */
private $exclude = [];
/** @var int */
private $order = RecursiveIteratorIterator::SELF_FIRST;
/** @var int */
private $maxDepth = -1;
/** @var array */
private $cursor;
/**
* Begins search for files matching mask and all directories.
* @param string|string[] $masks
* @return static
*/
public static function find(...$masks): self
{
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
return (new static)->select($masks, 'isDir')->select($masks, 'isFile');
}
/**
* Begins search for files matching mask.
* @param string|string[] $masks
* @return static
*/
public static function findFiles(...$masks): self
{
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
return (new static)->select($masks, 'isFile');
}
/**
* Begins search for directories matching mask.
* @param string|string[] $masks
* @return static
*/
public static function findDirectories(...$masks): self
{
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
return (new static)->select($masks, 'isDir');
}
/**
* Creates filtering group by mask & type selector.
* @return static
*/
private function select(array $masks, string $type): self
{
$this->cursor = &$this->groups[];
$pattern = self::buildPattern($masks);
$this->filter(function (RecursiveDirectoryIterator $file) use ($type, $pattern): bool {
return !$file->isDot()
&& $file->$type()
&& (!$pattern || preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/')));
});
return $this;
}
/**
* Searches in the given folder(s).
* @param string|string[] $paths
* @return static
*/
public function in(...$paths): self
{
$this->maxDepth = 0;
return $this->from(...$paths);
}
/**
* Searches recursively from the given folder(s).
* @param string|string[] $paths
* @return static
*/
public function from(...$paths): self
{
if ($this->paths) {
throw new Nette\InvalidStateException('Directory to search has already been specified.');
}
$this->paths = is_array($paths[0]) ? $paths[0] : $paths;
$this->cursor = &$this->exclude;
return $this;
}
/**
* Shows folder content prior to the folder.
* @return static
*/
public function childFirst(): self
{
$this->order = RecursiveIteratorIterator::CHILD_FIRST;
return $this;
}
/**
* Converts Finder pattern to regular expression.
*/
private static function buildPattern(array $masks): ?string
{
$pattern = [];
foreach ($masks as $mask) {
$mask = rtrim(strtr($mask, '\\', '/'), '/');
$prefix = '';
if ($mask === '') {
continue;
} elseif ($mask === '*') {
return null;
} elseif ($mask[0] === '/') { // absolute fixing
$mask = ltrim($mask, '/');
$prefix = '(?<=^/)';
}
$pattern[] = $prefix . strtr(preg_quote($mask, '#'),
['\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-']);
}
return $pattern ? '#/(' . implode('|', $pattern) . ')$#Di' : null;
}
/********************* iterator generator ****************d*g**/
/**
* Get the number of found files and/or directories.
*/
public function count(): int
{
return iterator_count($this->getIterator());
}
/**
* Returns iterator.
*/
public function getIterator(): \Iterator
{
if (!$this->paths) {
throw new Nette\InvalidStateException('Call in() or from() to specify directory to search.');
} elseif (count($this->paths) === 1) {
return $this->buildIterator((string) $this->paths[0]);
} else {
$iterator = new \AppendIterator();
foreach ($this->paths as $path) {
$iterator->append($this->buildIterator((string) $path));
}
return $iterator;
}
}
/**
* Returns per-path iterator.
*/
private function buildIterator(string $path): \Iterator
{
$iterator = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
if ($this->exclude) {
$iterator = new \RecursiveCallbackFilterIterator($iterator, function ($foo, $bar, RecursiveDirectoryIterator $file): bool {
if (!$file->isDot() && !$file->isFile()) {
foreach ($this->exclude as $filter) {
if (!$filter($file)) {
return false;
}
}
}
return true;
});
}
if ($this->maxDepth !== 0) {
$iterator = new RecursiveIteratorIterator($iterator, $this->order);
$iterator->setMaxDepth($this->maxDepth);
}
$iterator = new \CallbackFilterIterator($iterator, function ($foo, $bar, \Iterator $file): bool {
while ($file instanceof \OuterIterator) {
$file = $file->getInnerIterator();
}
foreach ($this->groups as $filters) {
foreach ($filters as $filter) {
if (!$filter($file)) {
continue 2;
}
}
return true;
}
return false;
});
return $iterator;
}
/********************* filtering ****************d*g**/
/**
* Restricts the search using mask.
* Excludes directories from recursive traversing.
* @param string|string[] $masks
* @return static
*/
public function exclude(...$masks): self
{
$masks = $masks && is_array($masks[0]) ? $masks[0] : $masks;
$pattern = self::buildPattern($masks);
if ($pattern) {
$this->filter(function (RecursiveDirectoryIterator $file) use ($pattern): bool {
return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/'));
});
}
return $this;
}
/**
* Restricts the search using callback.
* @param callable $callback function (RecursiveDirectoryIterator $file): bool
* @return static
*/
public function filter(callable $callback): self
{
$this->cursor[] = $callback;
return $this;
}
/**
* Limits recursion level.
* @return static
*/
public function limitDepth(int $depth): self
{
$this->maxDepth = $depth;
return $this;
}
/**
* Restricts the search by size.
* @param string $operator "[operator] [size] [unit]" example: >=10kB
* @return static
*/
public function size(string $operator, int $size = null): self
{
if (func_num_args() === 1) { // in $operator is predicate
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) {
throw new Nette\InvalidArgumentException('Invalid size predicate format.');
}
[, $operator, $size, $unit] = $matches;
static $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
$size *= $units[strtolower($unit)];
$operator = $operator ?: '=';
}
return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $size): bool {
return self::compare($file->getSize(), $operator, $size);
});
}
/**
* Restricts the search by modified time.
* @param string $operator "[operator] [date]" example: >1978-01-23
* @param string|int|\DateTimeInterface $date
* @return static
*/
public function date(string $operator, $date = null): self
{
if (func_num_args() === 1) { // in $operator is predicate
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) {
throw new Nette\InvalidArgumentException('Invalid date predicate format.');
}
[, $operator, $date] = $matches;
$operator = $operator ?: '=';
}
$date = DateTime::from($date)->format('U');
return $this->filter(function (RecursiveDirectoryIterator $file) use ($operator, $date): bool {
return self::compare($file->getMTime(), $operator, $date);
});
}
/**
* Compares two values.
*/
public static function compare($l, string $operator, $r): bool
{
switch ($operator) {
case '>':
return $l > $r;
case '>=':
return $l >= $r;
case '<':
return $l < $r;
case '<=':
return $l <= $r;
case '=':
case '==':
return $l == $r;
case '!':
case '!=':
case '<>':
return $l != $r;
default:
throw new Nette\InvalidArgumentException("Unknown operator $operator.");
}
}
/********************* extension methods ****************d*g**/
public function __call(string $name, array $args)
{
return isset(self::$extMethods[$name])
? (self::$extMethods[$name])($this, ...$args)
: Nette\Utils\ObjectHelpers::strictCall(get_class($this), $name, array_keys(self::$extMethods));
}
public static function extensionMethod(string $name, callable $callback): void
{
self::$extMethods[$name] = $callback;
}
}