PageRenderTime 35ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/web/vendor/nette/finder/src/Finder/Finder.php

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