Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • None
  • PHP

Classes

  • Arrays
  • Finder
  • Html
  • Json
  • LimitedScope
  • MimeTypeDetector
  • Neon
  • NeonEntity
  • Paginator
  • Strings
  • Tokenizer
  • Validators

Exceptions

  • AssertionException
  • JsonException
  • NeonException
  • RegexpException
  • TokenizerException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Utils;
 13: 
 14: use Nette,
 15:     RecursiveIteratorIterator;
 16: 
 17: 
 18: 
 19: /**
 20:  * Finder allows searching through directory trees using iterator.
 21:  *
 22:  * <code>
 23:  * Finder::findFiles('*.php')
 24:  *     ->size('> 10kB')
 25:  *     ->from('.')
 26:  *     ->exclude('temp');
 27:  * </code>
 28:  *
 29:  * @author     David Grudl
 30:  */
 31: class Finder extends Nette\Object implements \IteratorAggregate
 32: {
 33:     /** @var array */
 34:     private $paths = array();
 35: 
 36:     /** @var array of filters */
 37:     private $groups;
 38: 
 39:     /** @var filter for recursive traversing */
 40:     private $exclude = array();
 41: 
 42:     /** @var int */
 43:     private $order = RecursiveIteratorIterator::SELF_FIRST;
 44: 
 45:     /** @var int */
 46:     private $maxDepth = -1;
 47: 
 48:     /** @var array */
 49:     private $cursor;
 50: 
 51: 
 52: 
 53:     /**
 54:      * Begins search for files matching mask and all directories.
 55:      * @param  mixed
 56:      * @return Finder
 57:      */
 58:     public static function find($mask)
 59:     {
 60:         if (!is_array($mask)) {
 61:             $mask = func_get_args();
 62:         }
 63:         $finder = new static;
 64:         return $finder->select(array(), 'isDir')->select($mask, 'isFile');
 65:     }
 66: 
 67: 
 68: 
 69:     /**
 70:      * Begins search for files matching mask.
 71:      * @param  mixed
 72:      * @return Finder
 73:      */
 74:     public static function findFiles($mask)
 75:     {
 76:         if (!is_array($mask)) {
 77:             $mask = func_get_args();
 78:         }
 79:         $finder = new static;
 80:         return $finder->select($mask, 'isFile');
 81:     }
 82: 
 83: 
 84: 
 85:     /**
 86:      * Begins search for directories matching mask.
 87:      * @param  mixed
 88:      * @return Finder
 89:      */
 90:     public static function findDirectories($mask)
 91:     {
 92:         if (!is_array($mask)) {
 93:             $mask = func_get_args();
 94:         }
 95:         $finder = new static;
 96:         return $finder->select($mask, 'isDir');
 97:     }
 98: 
 99: 
100: 
101:     /**
102:      * Creates filtering group by mask & type selector.
103:      * @param  array
104:      * @param  string
105:      * @return Finder  provides a fluent interface
106:      */
107:     private function select($masks, $type)
108:     {
109:         $this->cursor = & $this->groups[];
110:         $pattern = self::buildPattern($masks);
111:         if ($type || $pattern) {
112:             $this->filter(function($file) use ($type, $pattern) {
113:                 return !$file->isDot()
114:                     && (!$type || $file->$type())
115:                     && (!$pattern || preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/')));
116:             });
117:         }
118:         return $this;
119:     }
120: 
121: 
122: 
123:     /**
124:      * Searchs in the given folder(s).
125:      * @param  string|array
126:      * @return Finder  provides a fluent interface
127:      */
128:     public function in($path)
129:     {
130:         if (!is_array($path)) {
131:             $path = func_get_args();
132:         }
133:         $this->maxDepth = 0;
134:         return $this->from($path);
135:     }
136: 
137: 
138: 
139:     /**
140:      * Searchs recursively from the given folder(s).
141:      * @param  string|array
142:      * @return Finder  provides a fluent interface
143:      */
144:     public function from($path)
145:     {
146:         if ($this->paths) {
147:             throw new Nette\InvalidStateException('Directory to search has already been specified.');
148:         }
149:         if (!is_array($path)) {
150:             $path = func_get_args();
151:         }
152:         $this->paths = $path;
153:         $this->cursor = & $this->exclude;
154:         return $this;
155:     }
156: 
157: 
158: 
159:     /**
160:      * Shows folder content prior to the folder.
161:      * @return Finder  provides a fluent interface
162:      */
163:     public function childFirst()
164:     {
165:         $this->order = RecursiveIteratorIterator::CHILD_FIRST;
166:         return $this;
167:     }
168: 
169: 
170: 
171:     /**
172:      * Converts Finder pattern to regular expression.
173:      * @param  array
174:      * @return string
175:      */
176:     private static function buildPattern($masks)
177:     {
178:         $pattern = array();
179:         // TODO: accept regexp
180:         foreach ($masks as $mask) {
181:             $mask = rtrim(strtr($mask, '\\', '/'), '/');
182:             $prefix = '';
183:             if ($mask === '') {
184:                 continue;
185: 
186:             } elseif ($mask === '*') {
187:                 return NULL;
188: 
189:             } elseif ($mask[0] === '/') { // absolute fixing
190:                 $mask = ltrim($mask, '/');
191:                 $prefix = '(?<=^/)';
192:             }
193:             $pattern[] = $prefix . strtr(preg_quote($mask, '#'),
194:                 array('\*\*' => '.*', '\*' => '[^/]*', '\?' => '[^/]', '\[\!' => '[^', '\[' => '[', '\]' => ']', '\-' => '-'));
195:         }
196:         return $pattern ? '#/(' . implode('|', $pattern) . ')\z#i' : NULL;
197:     }
198: 
199: 
200: 
201:     /********************* iterator generator ****************d*g**/
202: 
203: 
204: 
205:     /**
206:      * Returns iterator.
207:      * @return \Iterator
208:      */
209:     public function getIterator()
210:     {
211:         if (!$this->paths) {
212:             throw new Nette\InvalidStateException('Call in() or from() to specify directory to search.');
213: 
214:         } elseif (count($this->paths) === 1) {
215:             return $this->buildIterator($this->paths[0]);
216: 
217:         } else {
218:             $iterator = new \AppendIterator();
219:             $iterator->append($workaround = new \ArrayIterator(array('workaround PHP bugs #49104, #63077')));
220:             foreach ($this->paths as $path) {
221:                 $iterator->append($this->buildIterator($path));
222:             }
223:             unset($workaround[0]);
224:             return $iterator;
225:         }
226:     }
227: 
228: 
229: 
230:     /**
231:      * Returns per-path iterator.
232:      * @param  string
233:      * @return \Iterator
234:      */
235:     private function buildIterator($path)
236:     {
237:         if (PHP_VERSION_ID < 50301) {
238:             $iterator = new Nette\Utils\RecursiveDirectoryIteratorFixed($path);
239:         } else {
240:             $iterator = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
241:         }
242: 
243:         if ($this->exclude) {
244:             $filters = $this->exclude;
245:             $iterator = new Nette\Iterators\RecursiveFilter($iterator, function($file) use ($filters) {
246:                 if (!$file->isDot() && !$file->isFile()) {
247:                     foreach ($filters as $filter) {
248:                         if (!call_user_func($filter, $file)) {
249:                             return FALSE;
250:                         }
251:                     }
252:                 }
253:                 return TRUE;
254:             });
255:         }
256: 
257:         if ($this->maxDepth !== 0) {
258:             $iterator = new RecursiveIteratorIterator($iterator, $this->order);
259:             $iterator->setMaxDepth($this->maxDepth);
260:         }
261: 
262:         if ($this->groups) {
263:             $groups = $this->groups;
264:             $iterator = new Nette\Iterators\Filter($iterator, function($file) use ($groups) {
265:                 foreach ($groups as $filters) {
266:                     foreach ($filters as $filter) {
267:                         if (!call_user_func($filter, $file)) {
268:                             continue 2;
269:                         }
270:                     }
271:                     return TRUE;
272:                 }
273:                 return FALSE;
274:             });
275:         }
276: 
277:         return $iterator;
278:     }
279: 
280: 
281: 
282:     /********************* filtering ****************d*g**/
283: 
284: 
285: 
286:     /**
287:      * Restricts the search using mask.
288:      * Excludes directories from recursive traversing.
289:      * @param  mixed
290:      * @return Finder  provides a fluent interface
291:      */
292:     public function exclude($masks)
293:     {
294:         if (!is_array($masks)) {
295:             $masks = func_get_args();
296:         }
297:         $pattern = self::buildPattern($masks);
298:         if ($pattern) {
299:             $this->filter(function($file) use ($pattern) {
300:                 return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/'));
301:             });
302:         }
303:         return $this;
304:     }
305: 
306: 
307: 
308:     /**
309:      * Restricts the search using callback.
310:      * @param  callable
311:      * @return Finder  provides a fluent interface
312:      */
313:     public function filter($callback)
314:     {
315:         $this->cursor[] = $callback;
316:         return $this;
317:     }
318: 
319: 
320: 
321:     /**
322:      * Limits recursion level.
323:      * @param  int
324:      * @return Finder  provides a fluent interface
325:      */
326:     public function limitDepth($depth)
327:     {
328:         $this->maxDepth = $depth;
329:         return $this;
330:     }
331: 
332: 
333: 
334:     /**
335:      * Restricts the search by size.
336:      * @param  string  "[operator] [size] [unit]" example: >=10kB
337:      * @param  int
338:      * @return Finder  provides a fluent interface
339:      */
340:     public function size($operator, $size = NULL)
341:     {
342:         if (func_num_args() === 1) { // in $operator is predicate
343:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?\z#i', $operator, $matches)) {
344:                 throw new Nette\InvalidArgumentException('Invalid size predicate format.');
345:             }
346:             list(, $operator, $size, $unit) = $matches;
347:             static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
348:             $size *= $units[strtolower($unit)];
349:             $operator = $operator ? $operator : '=';
350:         }
351:         return $this->filter(function($file) use ($operator, $size) {
352:             return Finder::compare($file->getSize(), $operator, $size);
353:         });
354:     }
355: 
356: 
357: 
358:     /**
359:      * Restricts the search by modified time.
360:      * @param  string  "[operator] [date]" example: >1978-01-23
361:      * @param  mixed
362:      * @return Finder  provides a fluent interface
363:      */
364:     public function date($operator, $date = NULL)
365:     {
366:         if (func_num_args() === 1) { // in $operator is predicate
367:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)\z#i', $operator, $matches)) {
368:                 throw new Nette\InvalidArgumentException('Invalid date predicate format.');
369:             }
370:             list(, $operator, $date) = $matches;
371:             $operator = $operator ? $operator : '=';
372:         }
373:         $date = Nette\DateTime::from($date)->format('U');
374:         return $this->filter(function($file) use ($operator, $date) {
375:             return Finder::compare($file->getMTime(), $operator, $date);
376:         });
377:     }
378: 
379: 
380: 
381:     /**
382:      * Compares two values.
383:      * @param  mixed
384:      * @param  mixed
385:      * @return bool
386:      */
387:     public static function compare($l, $operator, $r)
388:     {
389:         switch ($operator) {
390:         case '>':
391:             return $l > $r;
392:         case '>=':
393:             return $l >= $r;
394:         case '<':
395:             return $l < $r;
396:         case '<=':
397:             return $l <= $r;
398:         case '=':
399:         case '==':
400:             return $l == $r;
401:         case '!':
402:         case '!=':
403:         case '<>':
404:             return $l != $r;
405:         }
406:         throw new Nette\InvalidArgumentException("Unknown operator $operator.");
407:     }
408: 
409: }
410: 
411: 
412: 
413: if (PHP_VERSION_ID < 50301) {
414:     /** @internal */
415:     class RecursiveDirectoryIteratorFixed extends \RecursiveDirectoryIterator
416:     {
417:         function hasChildren()
418:         {
419:             return parent::hasChildren(TRUE);
420:         }
421:     }
422: }
423: 
Nette Framework 2.0.8 API API documentation generated by ApiGen 2.8.0