Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
/*
4
 * This file is part of the symfony package.
5
 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
 
11
 
12
/**
13
 *
14
 * Allow to build rules to find files and directories.
15
 *
16
 * All rules may be invoked several times, except for ->in() method.
17
 * Some rules are cumulative (->name() for example) whereas others are destructive
18
 * (most recent value is used, ->maxdepth() method for example).
19
 *
20
 * All methods return the current sfFinder object to allow easy chaining:
21
 *
22
 * $files = sfFinder::type('file')->name('*.php')->in(.);
23
 *
24
 * Interface loosely based on perl File::Find::Rule module.
25
 *
26
 * @package    symfony
27
 * @subpackage util
28
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
29
 * @version    SVN: $Id: sfFinder.class.php 30528 2010-08-04 16:25:14Z fabien $
30
 */
31
class sfFinder
32
{
33
  protected $type                   = 'file';
34
  protected $names                  = array();
35
  protected $prunes                 = array();
36
  protected $discards               = array();
37
  protected $execs                  = array();
38
  protected $mindepth               = 0;
39
  protected $sizes                  = array();
40
  protected $maxdepth               = 1000000;
41
  protected $relative               = false;
42
  protected $follow_link            = false;
43
  protected $sort                   = false;
44
  protected $ignore_version_control = true;
45
 
46
  /**
47
   * Sets maximum directory depth.
48
   *
49
   * Finder will descend at most $level levels of directories below the starting point.
50
   *
51
   * @param  int $level
52
   * @return sfFinder current sfFinder object
53
   */
54
  public function maxdepth($level)
55
  {
56
    $this->maxdepth = $level;
57
 
58
    return $this;
59
  }
60
 
61
  /**
62
   * Sets minimum directory depth.
63
   *
64
   * Finder will start applying tests at level $level.
65
   *
66
   * @param  int $level
67
   * @return sfFinder current sfFinder object
68
   */
69
  public function mindepth($level)
70
  {
71
    $this->mindepth = $level;
72
 
73
    return $this;
74
  }
75
 
76
  public function get_type()
77
  {
78
    return $this->type;
79
  }
80
 
81
  /**
82
   * Sets the type of elements to returns.
83
   *
84
   * @param  string $name  directory or file or any (for both file and directory)
85
   * @return sfFinder new sfFinder object
86
   */
87
  public static function type($name)
88
  {
89
    $finder = new self();
90
    return $finder->setType($name);
91
  }
92
  /**
93
   * Sets the type of elements to returns.
94
   *
95
   * @param  string $name  directory or file or any (for both file and directory)
96
   * @return sfFinder Current object
97
   */
98
  public function setType($name)
99
  {
100
    $name = strtolower($name);
101
 
102
    if (substr($name, 0, 3) === 'dir')
103
    {
104
      $this->type = 'directory';
105
 
106
      return $this;
107
    }
108
    if ($name === 'any')
109
    {
110
      $this->type = 'any';
111
 
112
      return $this;
113
    }
114
 
115
      $this->type = 'file';
116
 
117
    return $this;
118
  }
119
 
120
  /*
121
   * glob, patterns (must be //) or strings
122
   */
123
  protected function to_regex($str)
124
  {
125
    if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $str))
126
    {
127
      return $str;
128
    }
129
 
130
    return sfGlobToRegex::glob_to_regex($str);
131
  }
132
 
133
  protected function args_to_array($arg_list, $not = false)
134
  {
135
    $list = array();
136
    $nbArgList = count($arg_list);
137
    for ($i = 0; $i < $nbArgList; $i++)
138
    {
139
      if (is_array($arg_list[$i]))
140
      {
141
        foreach ($arg_list[$i] as $arg)
142
        {
143
          $list[] = array($not, $this->to_regex($arg));
144
        }
145
      }
146
      else
147
      {
148
        $list[] = array($not, $this->to_regex($arg_list[$i]));
149
      }
150
    }
151
 
152
    return $list;
153
  }
154
 
155
  /**
156
   * Adds rules that files must match.
157
   *
158
   * You can use patterns (delimited with / sign), globs or simple strings.
159
   *
160
   * $finder->name('*.php')
161
   * $finder->name('/\.php$/') // same as above
162
   * $finder->name('test.php')
163
   *
164
   * @param  list   a list of patterns, globs or strings
165
   * @return sfFinder Current object
166
   */
167
  public function name()
168
  {
169
    $args = func_get_args();
170
    $this->names = array_merge($this->names, $this->args_to_array($args));
171
 
172
    return $this;
173
  }
174
 
175
  /**
176
   * Adds rules that files must not match.
177
   *
178
   * @see    ->name()
179
   * @param  list   a list of patterns, globs or strings
180
   * @return sfFinder Current object
181
   */
182
  public function not_name()
183
  {
184
    $args = func_get_args();
185
    $this->names = array_merge($this->names, $this->args_to_array($args, true));
186
 
187
    return $this;
188
  }
189
 
190
  /**
191
   * Adds tests for file sizes.
192
   *
193
   * $finder->size('> 10K');
194
   * $finder->size('<= 1Ki');
195
   * $finder->size(4);
196
   *
197
   * @param  list   a list of comparison strings
198
   * @return sfFinder Current object
199
   */
200
  public function size()
201
  {
202
    $args = func_get_args();
203
    $numargs = count($args);
204
    for ($i = 0; $i < $numargs; $i++)
205
    {
206
      $this->sizes[] = new sfNumberCompare($args[$i]);
207
    }
208
 
209
    return $this;
210
  }
211
 
212
  /**
213
   * Traverses no further.
214
   *
215
   * @param  list   a list of patterns, globs to match
216
   * @return sfFinder Current object
217
   */
218
  public function prune()
219
  {
220
    $args = func_get_args();
221
    $this->prunes = array_merge($this->prunes, $this->args_to_array($args));
222
 
223
    return $this;
224
  }
225
 
226
  /**
227
   * Discards elements that matches.
228
   *
229
   * @param  list   a list of patterns, globs to match
230
   * @return sfFinder Current object
231
   */
232
  public function discard()
233
  {
234
    $args = func_get_args();
235
    $this->discards = array_merge($this->discards, $this->args_to_array($args));
236
 
237
    return $this;
238
  }
239
 
240
  /**
241
   * Ignores version control directories.
242
   *
243
   * Currently supports Subversion, CVS, DARCS, Gnu Arch, Monotone, Bazaar-NG, GIT, Mercurial
244
   *
245
   * @param  bool   $ignore  falase when version control directories shall be included (default is true)
246
   *
247
   * @return sfFinder Current object
248
   */
249
  public function ignore_version_control($ignore = true)
250
  {
251
    $this->ignore_version_control = $ignore;
252
 
253
    return $this;
254
  }
255
 
256
  /**
257
   * Returns files and directories ordered by name
258
   *
259
   * @return sfFinder Current object
260
   */
261
  public function sort_by_name()
262
  {
263
    $this->sort = 'name';
264
 
265
    return $this;
266
  }
267
 
268
  /**
269
   * Returns files and directories ordered by type (directories before files), then by name
270
   *
271
   * @return sfFinder Current object
272
   */
273
  public function sort_by_type()
274
  {
275
    $this->sort = 'type';
276
 
277
    return $this;
278
  }
279
 
280
  /**
281
   * Executes function or method for each element.
282
   *
283
   * Element match if functino or method returns true.
284
   *
285
   * $finder->exec('myfunction');
286
   * $finder->exec(array($object, 'mymethod'));
287
   *
288
   * @param  mixed  function or method to call
289
   * @return sfFinder Current object
290
   */
291
  public function exec()
292
  {
293
    $args = func_get_args();
294
    $numargs = count($args);
295
    for ($i = 0; $i < $numargs; $i++)
296
    {
297
      if (is_array($args[$i]) && !method_exists($args[$i][0], $args[$i][1]))
298
      {
299
        throw new sfException(sprintf('method "%s" does not exist for object "%s".', $args[$i][1], $args[$i][0]));
300
      }
301
      if (!is_array($args[$i]) && !function_exists($args[$i]))
302
      {
303
        throw new sfException(sprintf('function "%s" does not exist.', $args[$i]));
304
      }
305
 
306
      $this->execs[] = $args[$i];
307
    }
308
 
309
    return $this;
310
  }
311
 
312
  /**
313
   * Returns relative paths for all files and directories.
314
   *
315
   * @return sfFinder Current object
316
   */
317
  public function relative()
318
  {
319
    $this->relative = true;
320
 
321
    return $this;
322
  }
323
 
324
  /**
325
   * Symlink following.
326
   *
327
   * @return sfFinder Current object
328
   */
329
  public function follow_link()
330
  {
331
    $this->follow_link = true;
332
 
333
    return $this;
334
  }
335
 
336
  /**
337
   * Searches files and directories which match defined rules.
338
   *
339
   * @return array list of files and directories
340
   */
341
  public function in()
342
  {
343
    $files    = array();
344
    $here_dir = getcwd();
345
 
346
    $finder = clone $this;
347
 
348
    if ($this->ignore_version_control)
349
    {
350
      $ignores = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
351
 
352
      $finder->discard($ignores)->prune($ignores);
353
    }
354
 
355
    // first argument is an array?
356
    $numargs  = func_num_args();
357
    $arg_list = func_get_args();
358
    if ($numargs === 1 && is_array($arg_list[0]))
359
    {
360
      $arg_list = $arg_list[0];
361
      $numargs  = count($arg_list);
362
    }
363
 
364
    for ($i = 0; $i < $numargs; $i++)
365
    {
366
      $dir = realpath($arg_list[$i]);
367
 
368
      if (!is_dir($dir))
369
      {
370
        continue;
371
      }
372
 
373
      $dir = str_replace('\\', '/', $dir);
374
 
375
      // absolute path?
376
      if (!self::isPathAbsolute($dir))
377
      {
378
        $dir = $here_dir.'/'.$dir;
379
      }
380
 
381
      $new_files = str_replace('\\', '/', $finder->search_in($dir));
382
 
383
      if ($this->relative)
384
      {
385
        $new_files = str_replace(rtrim($dir, '/').'/', '', $new_files);
386
      }
387
 
388
      $files = array_merge($files, $new_files);
389
    }
390
 
391
    if ($this->sort === 'name')
392
    {
393
      sort($files);
394
    }
395
 
396
    return array_unique($files);
397
  }
398
 
399
  protected function search_in($dir, $depth = 0)
400
  {
401
    if ($depth > $this->maxdepth)
402
    {
403
      return array();
404
    }
405
 
406
    $dir = realpath($dir);
407
 
408
    if ((!$this->follow_link) && is_link($dir))
409
    {
410
      return array();
411
    }
412
 
413
    $files = array();
414
    $temp_files = array();
415
    $temp_folders = array();
416
    if (is_dir($dir) && is_readable($dir))
417
    {
418
      $current_dir = opendir($dir);
419
      while (false !== $entryname = readdir($current_dir))
420
      {
421
        if ($entryname == '.' || $entryname == '..') continue;
422
 
423
        $current_entry = $dir.DIRECTORY_SEPARATOR.$entryname;
424
        if ((!$this->follow_link) && is_link($current_entry))
425
        {
426
          continue;
427
        }
428
 
429
        if (is_dir($current_entry))
430
        {
431
          if ($this->sort === 'type')
432
          {
433
            $temp_folders[$entryname] = $current_entry;
434
          }
435
          else
436
          {
437
            if (($this->type === 'directory' || $this->type === 'any') && ($depth >= $this->mindepth) && !$this->is_discarded($dir, $entryname) && $this->match_names($dir, $entryname) && $this->exec_ok($dir, $entryname))
438
            {
439
              $files[] = $current_entry;
440
            }
441
 
442
            if (!$this->is_pruned($dir, $entryname))
443
            {
444
              $files = array_merge($files, $this->search_in($current_entry, $depth + 1));
445
            }
446
          }
447
        }
448
        else
449
        {
450
          if (($this->type !== 'directory' || $this->type === 'any') && ($depth >= $this->mindepth) && !$this->is_discarded($dir, $entryname) && $this->match_names($dir, $entryname) && $this->size_ok($dir, $entryname) && $this->exec_ok($dir, $entryname))
451
          {
452
            if ($this->sort === 'type')
453
            {
454
              $temp_files[] = $current_entry;
455
            }
456
            else
457
            {
458
              $files[] = $current_entry;
459
            }
460
          }
461
        }
462
      }
463
 
464
      if ($this->sort === 'type')
465
      {
466
        ksort($temp_folders);
467
        foreach($temp_folders as $entryname => $current_entry)
468
        {
469
          if (($this->type === 'directory' || $this->type === 'any') && ($depth >= $this->mindepth) && !$this->is_discarded($dir, $entryname) && $this->match_names($dir, $entryname) && $this->exec_ok($dir, $entryname))
470
          {
471
            $files[] = $current_entry;
472
          }
473
 
474
          if (!$this->is_pruned($dir, $entryname))
475
          {
476
            $files = array_merge($files, $this->search_in($current_entry, $depth + 1));
477
          }
478
        }
479
 
480
        sort($temp_files);
481
        $files = array_merge($files, $temp_files);
482
      }
483
 
484
      closedir($current_dir);
485
    }
486
 
487
    return $files;
488
  }
489
 
490
  protected function match_names($dir, $entry)
491
  {
492
    if (!count($this->names)) return true;
493
 
494
    // Flags indicating that there was attempts to match
495
    // at least one "not_name" or "name" rule respectively
496
    // to following variables:
497
    $one_not_name_rule = false;
498
    $one_name_rule = false;
499
 
500
    foreach ($this->names as $args)
501
    {
502
      list($not, $regex) = $args;
503
      $not ? $one_not_name_rule = true : $one_name_rule = true;
504
      if (preg_match($regex, $entry))
505
      {
506
        // We must match ONLY ONE "not_name" or "name" rule:
507
        // if "not_name" rule matched then we return "false"
508
        // if "name" rule matched then we return "true"
509
        return $not ? false : true;
510
      }
511
    }
512
 
513
    if ($one_not_name_rule && $one_name_rule)
514
    {
515
      return false;
516
    }
517
    else if ($one_not_name_rule)
518
    {
519
      return true;
520
    }
521
    else if ($one_name_rule)
522
    {
523
      return false;
524
    }
525
    return true;
526
  }
527
 
528
  protected function size_ok($dir, $entry)
529
  {
530
    if (0 === count($this->sizes)) return true;
531
 
532
    if (!is_file($dir.DIRECTORY_SEPARATOR.$entry)) return true;
533
 
534
    $filesize = filesize($dir.DIRECTORY_SEPARATOR.$entry);
535
    foreach ($this->sizes as $number_compare)
536
    {
537
      if (!$number_compare->test($filesize)) return false;
538
    }
539
 
540
    return true;
541
  }
542
 
543
  protected function is_pruned($dir, $entry)
544
  {
545
    if (0 === count($this->prunes)) return false;
546
 
547
    foreach ($this->prunes as $args)
548
    {
549
      $regex = $args[1];
550
      if (preg_match($regex, $entry)) return true;
551
    }
552
 
553
    return false;
554
  }
555
 
556
  protected function is_discarded($dir, $entry)
557
  {
558
    if (0 === count($this->discards)) return false;
559
 
560
    foreach ($this->discards as $args)
561
    {
562
      $regex = $args[1];
563
      if (preg_match($regex, $entry)) return true;
564
    }
565
 
566
    return false;
567
  }
568
 
569
  protected function exec_ok($dir, $entry)
570
  {
571
    if (0 === count($this->execs)) return true;
572
 
573
    foreach ($this->execs as $exec)
574
    {
575
      if (!call_user_func_array($exec, array($dir, $entry))) return false;
576
    }
577
 
578
    return true;
579
  }
580
 
581
  public static function isPathAbsolute($path)
582
  {
583
    if ($path{0} === '/' || $path{0} === '\\' ||
584
        (strlen($path) > 3 && ctype_alpha($path{0}) &&
585
         $path{1} === ':' &&
586
         ($path{2} === '\\' || $path{2} === '/')
587
        )
588
       )
589
    {
590
      return true;
591
    }
592
 
593
    return false;
594
  }
595
}
596
 
597
/**
598
 * Match globbing patterns against text.
599
 *
600
 *   if match_glob("foo.*", "foo.bar") echo "matched\n";
601
 *
602
 * // prints foo.bar and foo.baz
603
 * $regex = glob_to_regex("foo.*");
604
 * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t)
605
 * {
606
 *   if (/$regex/) echo "matched: $car\n";
607
 * }
608
 *
609
 * sfGlobToRegex implements glob(3) style matching that can be used to match
610
 * against text, rather than fetching names from a filesystem.
611
 *
612
 * based on perl Text::Glob module.
613
 *
614
 * @package    symfony
615
 * @subpackage util
616
 * @author     Fabien Potencier <fabien.potencier@gmail.com> php port
617
 * @author     Richard Clamp <richardc@unixbeard.net> perl version
618
 * @copyright  2004-2005 Fabien Potencier <fabien.potencier@gmail.com>
619
 * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
620
 * @version    SVN: $Id: sfFinder.class.php 30528 2010-08-04 16:25:14Z fabien $
621
 */
622
class sfGlobToRegex
623
{
624
  protected static $strict_leading_dot = true;
625
  protected static $strict_wildcard_slash = true;
626
 
627
  public static function setStrictLeadingDot($boolean)
628
  {
629
    self::$strict_leading_dot = $boolean;
630
  }
631
 
632
  public static function setStrictWildcardSlash($boolean)
633
  {
634
    self::$strict_wildcard_slash = $boolean;
635
  }
636
 
637
  /**
638
   * Returns a compiled regex which is the equiavlent of the globbing pattern.
639
   *
640
   * @param  string $glob  pattern
641
   * @return string regex
642
   */
643
  public static function glob_to_regex($glob)
644
  {
645
    $first_byte = true;
646
    $escaping = false;
647
    $in_curlies = 0;
648
    $regex = '';
649
    $sizeGlob = strlen($glob);
650
    for ($i = 0; $i < $sizeGlob; $i++)
651
    {
652
      $car = $glob[$i];
653
      if ($first_byte)
654
      {
655
        if (self::$strict_leading_dot && $car !== '.')
656
        {
657
          $regex .= '(?=[^\.])';
658
        }
659
 
660
        $first_byte = false;
661
      }
662
 
663
      if ($car === '/')
664
      {
665
        $first_byte = true;
666
      }
667
 
668
      if ($car === '.' || $car === '(' || $car === ')' || $car === '|' || $car === '+' || $car === '^' || $car === '$')
669
      {
670
        $regex .= "\\$car";
671
      }
672
      elseif ($car === '*')
673
      {
674
        $regex .= ($escaping ? '\\*' : (self::$strict_wildcard_slash ? '[^/]*' : '.*'));
675
      }
676
      elseif ($car === '?')
677
      {
678
        $regex .= ($escaping ? '\\?' : (self::$strict_wildcard_slash ? '[^/]' : '.'));
679
      }
680
      elseif ($car === '{')
681
      {
682
        $regex .= ($escaping ? '\\{' : '(');
683
        if (!$escaping) ++$in_curlies;
684
      }
685
      elseif ($car === '}' && $in_curlies)
686
      {
687
        $regex .= ($escaping ? '}' : ')');
688
        if (!$escaping) --$in_curlies;
689
      }
690
      elseif ($car === ',' && $in_curlies)
691
      {
692
        $regex .= ($escaping ? ',' : '|');
693
      }
694
      elseif ($car === '\\')
695
      {
696
        if ($escaping)
697
        {
698
          $regex .= '\\\\';
699
          $escaping = false;
700
        }
701
        else
702
        {
703
          $escaping = true;
704
        }
705
 
706
        continue;
707
      }
708
      else
709
      {
710
        $regex .= $car;
711
      }
712
      $escaping = false;
713
    }
714
 
715
    return '#^'.$regex.'$#';
716
  }
717
}
718
 
719
/**
720
 * Numeric comparisons.
721
 *
722
 * sfNumberCompare compiles a simple comparison to an anonymous
723
 * subroutine, which you can call with a value to be tested again.
724
 
725
 * Now this would be very pointless, if sfNumberCompare didn't understand
726
 * magnitudes.
727
 
728
 * The target value may use magnitudes of kilobytes (k, ki),
729
 * megabytes (m, mi), or gigabytes (g, gi).  Those suffixed
730
 * with an i use the appropriate 2**n version in accordance with the
731
 * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
732
 *
733
 * based on perl Number::Compare module.
734
 *
735
 * @package    symfony
736
 * @subpackage util
737
 * @author     Fabien Potencier <fabien.potencier@gmail.com> php port
738
 * @author     Richard Clamp <richardc@unixbeard.net> perl version
739
 * @copyright  2004-2005 Fabien Potencier <fabien.potencier@gmail.com>
740
 * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
741
 * @see        http://physics.nist.gov/cuu/Units/binary.html
742
 * @version    SVN: $Id: sfFinder.class.php 30528 2010-08-04 16:25:14Z fabien $
743
 */
744
class sfNumberCompare
745
{
746
  protected $test = '';
747
 
748
  public function __construct($test)
749
  {
750
    $this->test = $test;
751
  }
752
 
753
  public function test($number)
754
  {
755
    if (!preg_match('{^([<>]=?)?(.*?)([kmg]i?)?$}i', $this->test, $matches))
756
    {
757
      throw new sfException(sprintf('don\'t understand "%s" as a test.', $this->test));
758
    }
759
 
760
    $target = array_key_exists(2, $matches) ? $matches[2] : '';
761
    $magnitude = array_key_exists(3, $matches) ? $matches[3] : '';
762
    if (strtolower($magnitude) === 'k')  $target *=           1000;
763
    if (strtolower($magnitude) === 'ki') $target *=           1024;
764
    if (strtolower($magnitude) === 'm')  $target *=        1000000;
765
    if (strtolower($magnitude) === 'mi') $target *=      1024*1024;
766
    if (strtolower($magnitude) === 'g')  $target *=     1000000000;
767
    if (strtolower($magnitude) === 'gi') $target *= 1024*1024*1024;
768
 
769
    $comparison = array_key_exists(1, $matches) ? $matches[1] : '==';
770
    if ($comparison === '==' || $comparison == '')
771
    {
772
      return ($number == $target);
773
    }
774
    if ($comparison === '>')
775
    {
776
      return ($number > $target);
777
    }
778
    if ($comparison === '>=')
779
    {
780
      return ($number >= $target);
781
    }
782
    if ($comparison === '<')
783
    {
784
      return ($number < $target);
785
    }
786
    if ($comparison === '<=')
787
    {
788
      return ($number <= $target);
789
    }
790
 
791
    return false;
792
  }
793
}