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
  • Overview
  • Namespace
  • Class
  • Tree
  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) . ')$#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(); // buggy!
219:             foreach ($this->paths as $path) {
220:                 $iterator->append($this->buildIterator($path));
221:             }
222:             return $iterator;
223:         }
224:     }
225: 
226: 
227: 
228:     /**
229:      * Returns per-path iterator.
230:      * @param  string
231:      * @return \Iterator
232:      */
233:     private function buildIterator($path)
234:     {
235:         if (PHP_VERSION_ID < 50301) {
236:             $iterator = new Nette\Utils\RecursiveDirectoryIteratorFixed($path);
237:         } else {
238:             $iterator = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
239:         }
240: 
241:         if ($this->exclude) {
242:             $filters = $this->exclude;
243:             $iterator = new Nette\Iterators\RecursiveFilter($iterator, function($file) use ($filters) {
244:                 if (!$file->isDot() && !$file->isFile()) {
245:                     foreach ($filters as $filter) {
246:                         if (!call_user_func($filter, $file)) {
247:                             return FALSE;
248:                         }
249:                     }
250:                 }
251:                 return TRUE;
252:             });
253:         }
254: 
255:         if ($this->maxDepth !== 0) {
256:             $iterator = new RecursiveIteratorIterator($iterator, $this->order);
257:             $iterator->setMaxDepth($this->maxDepth);
258:         }
259: 
260:         if ($this->groups) {
261:             $groups = $this->groups;
262:             $iterator = new Nette\Iterators\Filter($iterator, function($file) use ($groups) {
263:                 foreach ($groups as $filters) {
264:                     foreach ($filters as $filter) {
265:                         if (!call_user_func($filter, $file)) {
266:                             continue 2;
267:                         }
268:                     }
269:                     return TRUE;
270:                 }
271:                 return FALSE;
272:             });
273:         }
274: 
275:         return $iterator;
276:     }
277: 
278: 
279: 
280:     /********************* filtering ****************d*g**/
281: 
282: 
283: 
284:     /**
285:      * Restricts the search using mask.
286:      * Excludes directories from recursive traversing.
287:      * @param  mixed
288:      * @return Finder  provides a fluent interface
289:      */
290:     public function exclude($masks)
291:     {
292:         if (!is_array($masks)) {
293:             $masks = func_get_args();
294:         }
295:         $pattern = self::buildPattern($masks);
296:         if ($pattern) {
297:             $this->filter(function($file) use ($pattern) {
298:                 return !preg_match($pattern, '/' . strtr($file->getSubPathName(), '\\', '/'));
299:             });
300:         }
301:         return $this;
302:     }
303: 
304: 
305: 
306:     /**
307:      * Restricts the search using callback.
308:      * @param  callable
309:      * @return Finder  provides a fluent interface
310:      */
311:     public function filter($callback)
312:     {
313:         $this->cursor[] = $callback;
314:         return $this;
315:     }
316: 
317: 
318: 
319:     /**
320:      * Limits recursion level.
321:      * @param  int
322:      * @return Finder  provides a fluent interface
323:      */
324:     public function limitDepth($depth)
325:     {
326:         $this->maxDepth = $depth;
327:         return $this;
328:     }
329: 
330: 
331: 
332:     /**
333:      * Restricts the search by size.
334:      * @param  string  "[operator] [size] [unit]" example: >=10kB
335:      * @param  int
336:      * @return Finder  provides a fluent interface
337:      */
338:     public function size($operator, $size = NULL)
339:     {
340:         if (func_num_args() === 1) { // in $operator is predicate
341:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#i', $operator, $matches)) {
342:                 throw new Nette\InvalidArgumentException('Invalid size predicate format.');
343:             }
344:             list(, $operator, $size, $unit) = $matches;
345:             static $units = array('' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9);
346:             $size *= $units[strtolower($unit)];
347:             $operator = $operator ? $operator : '=';
348:         }
349:         return $this->filter(function($file) use ($operator, $size) {
350:             return Finder::compare($file->getSize(), $operator, $size);
351:         });
352:     }
353: 
354: 
355: 
356:     /**
357:      * Restricts the search by modified time.
358:      * @param  string  "[operator] [date]" example: >1978-01-23
359:      * @param  mixed
360:      * @return Finder  provides a fluent interface
361:      */
362:     public function date($operator, $date = NULL)
363:     {
364:         if (func_num_args() === 1) { // in $operator is predicate
365:             if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#i', $operator, $matches)) {
366:                 throw new Nette\InvalidArgumentException('Invalid date predicate format.');
367:             }
368:             list(, $operator, $date) = $matches;
369:             $operator = $operator ? $operator : '=';
370:         }
371:         $date = Nette\DateTime::from($date)->format('U');
372:         return $this->filter(function($file) use ($operator, $date) {
373:             return Finder::compare($file->getMTime(), $operator, $date);
374:         });
375:     }
376: 
377: 
378: 
379:     /**
380:      * Compares two values.
381:      * @param  mixed
382:      * @param  mixed
383:      * @return bool
384:      */
385:     public static function compare($l, $operator, $r)
386:     {
387:         switch ($operator) {
388:         case '>':
389:             return $l > $r;
390:         case '>=':
391:             return $l >= $r;
392:         case '<':
393:             return $l < $r;
394:         case '<=':
395:             return $l <= $r;
396:         case '=':
397:         case '==':
398:             return $l == $r;
399:         case '!':
400:         case '!=':
401:         case '<>':
402:             return $l != $r;
403:         }
404:         throw new Nette\InvalidArgumentException("Unknown operator $operator.");
405:     }
406: 
407: }
408: 
409: 
410: 
411: if (PHP_VERSION_ID < 50301) {
412:     /** @internal */
413:     class RecursiveDirectoryIteratorFixed extends \RecursiveDirectoryIterator
414:     {
415:         function hasChildren()
416:         {
417:             return parent::hasChildren(TRUE);
418:         }
419:     }
420: }
421: 
Nette Framework 2.0.3 API API documentation generated by ApiGen 2.7.0