PageRenderTime 24ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette/Utils/Finder.php

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