1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Utils;
13:
14: use Nette,
15: RecursiveIteratorIterator;
16:
17:
18:
19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
31: class Finder extends Nette\Object implements \IteratorAggregate
32: {
33:
34: private $paths = array();
35:
36:
37: private $groups;
38:
39:
40: private $exclude = array();
41:
42:
43: private $order = RecursiveIteratorIterator::SELF_FIRST;
44:
45:
46: private $maxDepth = -1;
47:
48:
49: private $cursor;
50:
51:
52:
53: 54: 55: 56: 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: 71: 72: 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: 87: 88: 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: 103: 104: 105: 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: 125: 126: 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: 141: 142: 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: 161: 162:
163: public function childFirst()
164: {
165: $this->order = RecursiveIteratorIterator::CHILD_FIRST;
166: return $this;
167: }
168:
169:
170:
171: 172: 173: 174: 175:
176: private static function buildPattern($masks)
177: {
178: $pattern = array();
179:
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] === '/') {
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:
202:
203:
204:
205: 206: 207: 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: foreach ($this->paths as $path) {
220: $iterator->append($this->buildIterator($path));
221: }
222: return $iterator;
223: }
224: }
225:
226:
227:
228: 229: 230: 231: 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:
281:
282:
283:
284: 285: 286: 287: 288: 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: 308: 309: 310:
311: public function filter($callback)
312: {
313: $this->cursor[] = $callback;
314: return $this;
315: }
316:
317:
318:
319: 320: 321: 322: 323:
324: public function limitDepth($depth)
325: {
326: $this->maxDepth = $depth;
327: return $this;
328: }
329:
330:
331:
332: 333: 334: 335: 336: 337:
338: public function size($operator, $size = NULL)
339: {
340: if (func_num_args() === 1) {
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: 358: 359: 360: 361:
362: public function date($operator, $date = NULL)
363: {
364: if (func_num_args() === 1) {
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: 381: 382: 383: 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:
413: class RecursiveDirectoryIteratorFixed extends \RecursiveDirectoryIterator
414: {
415: function hasChildren()
416: {
417: return parent::hasChildren(TRUE);
418: }
419: }
420: }
421: