Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/**
4
 * Hoa
5
 *
6
 *
7
 * @license
8
 *
9
 * New BSD License
10
 *
11
 * Copyright © 2007-2017, Hoa community. All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *     * Redistributions of source code must retain the above copyright
16
 *       notice, this list of conditions and the following disclaimer.
17
 *     * Redistributions in binary form must reproduce the above copyright
18
 *       notice, this list of conditions and the following disclaimer in the
19
 *       documentation and/or other materials provided with the distribution.
20
 *     * Neither the name of the Hoa nor the names of its contributors may be
21
 *       used to endorse or promote products derived from this software without
22
 *       specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 */
36
 
37
namespace Psy\Readline\Hoa;
38
 
39
/**
40
 * Class \Hoa\File\Finder.
41
 *
42
 * This class allows to find files easily by using filters and flags.
43
 */
44
class FileFinder implements \IteratorAggregate
45
{
46
    /**
47
     * SplFileInfo classname.
48
     */
49
    protected $_splFileInfo = \SplFileInfo::class;
50
 
51
    /**
52
     * Paths where to look for.
53
     */
54
    protected $_paths = [];
55
 
56
    /**
57
     * Max depth in recursion.
58
     */
59
    protected $_maxDepth = -1;
60
 
61
    /**
62
     * Filters.
63
     */
64
    protected $_filters = [];
65
 
66
    /**
67
     * Flags.
68
     */
69
    protected $_flags = -1;
70
 
71
    /**
72
     * Types of files to handle.
73
     */
74
    protected $_types = [];
75
 
76
    /**
77
     * What comes first: parent or child?
78
     */
79
    protected $_first = -1;
80
 
81
    /**
82
     * Sorts.
83
     */
84
    protected $_sorts = [];
85
 
86
    /**
87
     * Initialize.
88
     */
89
    public function __construct()
90
    {
91
        $this->_flags = IteratorFileSystem::KEY_AS_PATHNAME
92
                        | IteratorFileSystem::CURRENT_AS_FILEINFO
93
                        | IteratorFileSystem::SKIP_DOTS;
94
        $this->_first = \RecursiveIteratorIterator::SELF_FIRST;
95
 
96
        return;
97
    }
98
 
99
    /**
100
     * Select a directory to scan.
101
     */
102
    public function in($paths): self
103
    {
104
        if (!\is_array($paths)) {
105
            $paths = [$paths];
106
        }
107
 
108
        foreach ($paths as $path) {
109
            if (1 === \preg_match('/[\*\?\[\]]/', $path)) {
110
                $iterator = new \CallbackFilterIterator(
111
                    new \GlobIterator(\rtrim($path, \DIRECTORY_SEPARATOR)),
112
                    function ($current) {
113
                        return $current->isDir();
114
                    }
115
                );
116
 
117
                foreach ($iterator as $fileInfo) {
118
                    $this->_paths[] = $fileInfo->getPathname();
119
                }
120
            } else {
121
                $this->_paths[] = $path;
122
            }
123
        }
124
 
125
        return $this;
126
    }
127
 
128
    /**
129
     * Set max depth for recursion.
130
     */
131
    public function maxDepth(int $depth): self
132
    {
133
        $this->_maxDepth = $depth;
134
 
135
        return $this;
136
    }
137
 
138
    /**
139
     * Include files in the result.
140
     */
141
    public function files(): self
142
    {
143
        $this->_types[] = 'file';
144
 
145
        return $this;
146
    }
147
 
148
    /**
149
     * Include directories in the result.
150
     */
151
    public function directories(): self
152
    {
153
        $this->_types[] = 'dir';
154
 
155
        return $this;
156
    }
157
 
158
    /**
159
     * Include links in the result.
160
     */
161
    public function links(): self
162
    {
163
        $this->_types[] = 'link';
164
 
165
        return $this;
166
    }
167
 
168
    /**
169
     * Follow symbolink links.
170
     */
171
    public function followSymlinks(bool $flag = true): self
172
    {
173
        if (true === $flag) {
174
            $this->_flags ^= IteratorFileSystem::FOLLOW_SYMLINKS;
175
        } else {
176
            $this->_flags |= IteratorFileSystem::FOLLOW_SYMLINKS;
177
        }
178
 
179
        return $this;
180
    }
181
 
182
    /**
183
     * Include files that match a regex.
184
     * Example:
185
     *     $this->name('#\.php$#');.
186
     */
187
    public function name(string $regex): self
188
    {
189
        $this->_filters[] = function (\SplFileInfo $current) use ($regex) {
190
            return 0 !== \preg_match($regex, $current->getBasename());
191
        };
192
 
193
        return $this;
194
    }
195
 
196
    /**
197
     * Exclude directories that match a regex.
198
     * Example:
199
     *      $this->notIn('#^\.(git|hg)$#');.
200
     */
201
    public function notIn(string $regex): self
202
    {
203
        $this->_filters[] = function (\SplFileInfo $current) use ($regex) {
204
            foreach (\explode(\DIRECTORY_SEPARATOR, $current->getPathname()) as $part) {
205
                if (0 !== \preg_match($regex, $part)) {
206
                    return false;
207
                }
208
            }
209
 
210
            return true;
211
        };
212
 
213
        return $this;
214
    }
215
 
216
    /**
217
     * Include files that respect a certain size.
218
     * The size is a string of the form:
219
     *     operator number unit
220
     * where
221
     *     • operator could be: <, <=, >, >= or =;
222
     *     • number is a positive integer;
223
     *     • unit could be: b (default), Kb, Mb, Gb, Tb, Pb, Eb, Zb, Yb.
224
     * Example:
225
     *     $this->size('>= 12Kb');.
226
     */
227
    public function size(string $size): self
228
    {
229
        if (0 === \preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) {
230
            return $this;
231
        }
232
 
233
        $number = (float) ($matches[2]);
234
        $unit = $matches[3] ?? 'b';
235
        $operator = $matches[1];
236
 
237
        switch ($unit) {
238
            case 'b':
239
                break;
240
 
241
            // kilo
242
            case 'Kb':
243
                $number <<= 10;
244
 
245
                break;
246
 
247
            // mega.
248
            case 'Mb':
249
                $number <<= 20;
250
 
251
                break;
252
 
253
            // giga.
254
            case 'Gb':
255
                $number <<= 30;
256
 
257
                break;
258
 
259
            // tera.
260
            case 'Tb':
261
                $number *= 1099511627776;
262
 
263
                break;
264
 
265
            // peta.
266
            case 'Pb':
267
                $number *= 1024 ** 5;
268
 
269
                break;
270
 
271
            // exa.
272
            case 'Eb':
273
                $number *= 1024 ** 6;
274
 
275
                break;
276
 
277
            // zetta.
278
            case 'Zb':
279
                $number *= 1024 ** 7;
280
 
281
                break;
282
 
283
            // yota.
284
            case 'Yb':
285
                $number *= 1024 ** 8;
286
 
287
                break;
288
        }
289
 
290
        $filter = null;
291
 
292
        switch ($operator) {
293
            case '<':
294
                $filter = function (\SplFileInfo $current) use ($number) {
295
                    return $current->getSize() < $number;
296
                };
297
 
298
                break;
299
 
300
            case '<=':
301
                $filter = function (\SplFileInfo $current) use ($number) {
302
                    return $current->getSize() <= $number;
303
                };
304
 
305
                break;
306
 
307
            case '>':
308
                $filter = function (\SplFileInfo $current) use ($number) {
309
                    return $current->getSize() > $number;
310
                };
311
 
312
                break;
313
 
314
            case '>=':
315
                $filter = function (\SplFileInfo $current) use ($number) {
316
                    return $current->getSize() >= $number;
317
                };
318
 
319
                break;
320
 
321
            case '=':
322
                $filter = function (\SplFileInfo $current) use ($number) {
323
                    return $current->getSize() === $number;
324
                };
325
 
326
                break;
327
        }
328
 
329
        $this->_filters[] = $filter;
330
 
331
        return $this;
332
    }
333
 
334
    /**
335
     * Whether we should include dots or not (respectively . and ..).
336
     */
337
    public function dots(bool $flag = true): self
338
    {
339
        if (true === $flag) {
340
            $this->_flags ^= IteratorFileSystem::SKIP_DOTS;
341
        } else {
342
            $this->_flags |= IteratorFileSystem::SKIP_DOTS;
343
        }
344
 
345
        return $this;
346
    }
347
 
348
    /**
349
     * Include files that are owned by a certain owner.
350
     */
351
    public function owner(int $owner): self
352
    {
353
        $this->_filters[] = function (\SplFileInfo $current) use ($owner) {
354
            return $current->getOwner() === $owner;
355
        };
356
 
357
        return $this;
358
    }
359
 
360
    /**
361
     * Format date.
362
     * Date can have the following syntax:
363
     *     date
364
     *     since date
365
     *     until date
366
     * If the date does not have the “ago” keyword, it will be added.
367
     * Example: “42 hours” is equivalent to “since 42 hours” which is equivalent
368
     * to “since 42 hours ago”.
369
     */
370
    protected function formatDate(string $date, &$operator): int
371
    {
372
        $operator = -1;
373
 
374
        if (0 === \preg_match('#\bago\b#', $date)) {
375
            $date .= ' ago';
376
        }
377
 
378
        if (0 !== \preg_match('#^(since|until)\b(.+)$#', $date, $matches)) {
379
            $time = \strtotime($matches[2]);
380
 
381
            if ('until' === $matches[1]) {
382
                $operator = 1;
383
            }
384
        } else {
385
            $time = \strtotime($date);
386
        }
387
 
388
        return $time;
389
    }
390
 
391
    /**
392
     * Include files that have been changed from a certain date.
393
     * Example:
394
     *     $this->changed('since 13 days');.
395
     */
396
    public function changed(string $date): self
397
    {
398
        $time = $this->formatDate($date, $operator);
399
 
400
        if (-1 === $operator) {
401
            $this->_filters[] = function (\SplFileInfo $current) use ($time) {
402
                return $current->getCTime() >= $time;
403
            };
404
        } else {
405
            $this->_filters[] = function (\SplFileInfo $current) use ($time) {
406
                return $current->getCTime() < $time;
407
            };
408
        }
409
 
410
        return $this;
411
    }
412
 
413
    /**
414
     * Include files that have been modified from a certain date.
415
     * Example:
416
     *     $this->modified('since 13 days');.
417
     */
418
    public function modified(string $date): self
419
    {
420
        $time = $this->formatDate($date, $operator);
421
 
422
        if (-1 === $operator) {
423
            $this->_filters[] = function (\SplFileInfo $current) use ($time) {
424
                return $current->getMTime() >= $time;
425
            };
426
        } else {
427
            $this->_filters[] = function (\SplFileInfo $current) use ($time) {
428
                return $current->getMTime() < $time;
429
            };
430
        }
431
 
432
        return $this;
433
    }
434
 
435
    /**
436
     * Add your own filter.
437
     * The callback will receive 3 arguments: $current, $key and $iterator. It
438
     * must return a boolean: true to include the file, false to exclude it.
439
     * Example:
440
     *     // Include files that are readable
441
     *     $this->filter(function ($current) {
442
     *         return $current->isReadable();
443
     *     });.
444
     */
445
    public function filter($callback): self
446
    {
447
        $this->_filters[] = $callback;
448
 
449
        return $this;
450
    }
451
 
452
    /**
453
     * Sort result by name.
454
     * If \Collator exists (from ext/intl), the $locale argument will be used
455
     * for its constructor. Else, strcmp() will be used.
456
     * Example:
457
     *     $this->sortByName('fr_FR');.
458
     */
459
    public function sortByName(string $locale = 'root'): self
460
    {
461
        if (true === \class_exists('Collator', false)) {
462
            $collator = new \Collator($locale);
463
 
464
            $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) {
465
                return $collator->compare($a->getPathname(), $b->getPathname());
466
            };
467
        } else {
468
            $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) {
469
                return \strcmp($a->getPathname(), $b->getPathname());
470
            };
471
        }
472
 
473
        return $this;
474
    }
475
 
476
    /**
477
     * Sort result by size.
478
     * Example:
479
     *     $this->sortBySize();.
480
     */
481
    public function sortBySize(): self
482
    {
483
        $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) {
484
            return $a->getSize() < $b->getSize();
485
        };
486
 
487
        return $this;
488
    }
489
 
490
    /**
491
     * Add your own sort.
492
     * The callback will receive 2 arguments: $a and $b. Please see the uasort()
493
     * function.
494
     * Example:
495
     *     // Sort files by their modified time.
496
     *     $this->sort(function ($a, $b) {
497
     *         return $a->getMTime() < $b->getMTime();
498
     *     });.
499
     */
500
    public function sort($callable): self
501
    {
502
        $this->_sorts[] = $callable;
503
 
504
        return $this;
505
    }
506
 
507
    /**
508
     * Child comes first when iterating.
509
     */
510
    public function childFirst(): self
511
    {
512
        $this->_first = \RecursiveIteratorIterator::CHILD_FIRST;
513
 
514
        return $this;
515
    }
516
 
517
    /**
518
     * Get the iterator.
519
     */
520
    public function getIterator()
521
    {
522
        $_iterator = new \AppendIterator();
523
        $types = $this->getTypes();
524
 
525
        if (!empty($types)) {
526
            $this->_filters[] = function (\SplFileInfo $current) use ($types) {
527
                return \in_array($current->getType(), $types);
528
            };
529
        }
530
 
531
        $maxDepth = $this->getMaxDepth();
532
        $splFileInfo = $this->getSplFileInfo();
533
 
534
        foreach ($this->getPaths() as $path) {
535
            if (1 === $maxDepth) {
536
                $iterator = new \IteratorIterator(
537
                    new IteratorRecursiveDirectory(
538
                        $path,
539
                        $this->getFlags(),
540
                        $splFileInfo
541
                    ),
542
                    $this->getFirst()
543
                );
544
            } else {
545
                $iterator = new \RecursiveIteratorIterator(
546
                    new IteratorRecursiveDirectory(
547
                        $path,
548
                        $this->getFlags(),
549
                        $splFileInfo
550
                    ),
551
                    $this->getFirst()
552
                );
553
 
554
                if (1 < $maxDepth) {
555
                    $iterator->setMaxDepth($maxDepth - 1);
556
                }
557
            }
558
 
559
            $_iterator->append($iterator);
560
        }
561
 
562
        foreach ($this->getFilters() as $filter) {
563
            $_iterator = new \CallbackFilterIterator(
564
                $_iterator,
565
                $filter
566
            );
567
        }
568
 
569
        $sorts = $this->getSorts();
570
 
571
        if (empty($sorts)) {
572
            return $_iterator;
573
        }
574
 
575
        $array = \iterator_to_array($_iterator);
576
 
577
        foreach ($sorts as $sort) {
578
            \uasort($array, $sort);
579
        }
580
 
581
        return new \ArrayIterator($array);
582
    }
583
 
584
    /**
585
     * Set SplFileInfo classname.
586
     */
587
    public function setSplFileInfo(string $splFileInfo): string
588
    {
589
        $old = $this->_splFileInfo;
590
        $this->_splFileInfo = $splFileInfo;
591
 
592
        return $old;
593
    }
594
 
595
    /**
596
     * Get SplFileInfo classname.
597
     */
598
    public function getSplFileInfo(): string
599
    {
600
        return $this->_splFileInfo;
601
    }
602
 
603
    /**
604
     * Get all paths.
605
     */
606
    protected function getPaths(): array
607
    {
608
        return $this->_paths;
609
    }
610
 
611
    /**
612
     * Get max depth.
613
     */
614
    public function getMaxDepth(): int
615
    {
616
        return $this->_maxDepth;
617
    }
618
 
619
    /**
620
     * Get types.
621
     */
622
    public function getTypes(): array
623
    {
624
        return $this->_types;
625
    }
626
 
627
    /**
628
     * Get filters.
629
     */
630
    protected function getFilters(): array
631
    {
632
        return $this->_filters;
633
    }
634
 
635
    /**
636
     * Get sorts.
637
     */
638
    protected function getSorts(): array
639
    {
640
        return $this->_sorts;
641
    }
642
 
643
    /**
644
     * Get flags.
645
     */
646
    public function getFlags(): int
647
    {
648
        return $this->_flags;
649
    }
650
 
651
    /**
652
     * Get first.
653
     */
654
    public function getFirst(): int
655
    {
656
        return $this->_first;
657
    }
658
}