PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/DocBlox/Parser/Files.php

http://github.com/mvriel/Docblox
PHP | 433 lines | 225 code | 38 blank | 170 comment | 32 complexity | 70396307b78fd70213326edcd893ad8b MD5 | raw file
Possible License(s): LGPL-3.0, BSD-3-Clause, CC-BY-SA-3.0
  1. <?php
  2. /**
  3. * DocBlox
  4. *
  5. * PHP Version 5
  6. *
  7. * @category DocBlox
  8. * @package Parser
  9. * @author Mike van Riel <mike.vanriel@naenius.com>
  10. * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com)
  11. * @license http://www.opensource.org/licenses/mit-license.php MIT
  12. * @link http://docblox-project.org
  13. */
  14. /**
  15. * Files container handling directory scanning, project root detection and ignores.
  16. *
  17. * @category DocBlox
  18. * @package Parser
  19. * @author Mike van Riel <mike.vanriel@naenius.com>
  20. * @license http://www.opensource.org/licenses/mit-license.php MIT
  21. * @link http://docblox-project.org
  22. */
  23. class DocBlox_Parser_Files extends DocBlox_Parser_Abstract
  24. {
  25. /**
  26. * the glob patterns which directories/files to ignore during parsing and
  27. * how many files were ignored.
  28. *
  29. * Structure of this array is:
  30. *
  31. * array(
  32. * 0 => <GLOB>
  33. * 1 => <COUNT of USES>
  34. * )
  35. *
  36. * @var array[]
  37. */
  38. protected $ignore_patterns = array();
  39. /**
  40. * @var string[] Array containing a list of allowed line endings;
  41. * defaults to php, php3 and phtml.
  42. */
  43. protected $allowed_extensions = array('php', 'php3', 'phtml');
  44. /** @var string[] An array containing the file names which must be processed */
  45. protected $files = array();
  46. /** @var string Detected root folder for this project */
  47. protected $project_root = null;
  48. /**
  49. * Sets the patterns by which to detect which files to ignore.
  50. *
  51. * @param array $patterns Glob-like patterns to filter files.
  52. *
  53. * @return void
  54. */
  55. public function setIgnorePatterns(array $patterns)
  56. {
  57. $this->ignore_patterns = array();
  58. foreach ($patterns as $pattern) {
  59. $this->addIgnorePattern($pattern);
  60. }
  61. }
  62. /**
  63. * Returns the ignore patterns.
  64. *
  65. * @return array
  66. */
  67. public function getIgnorePatterns()
  68. {
  69. // extract first element; second is a count
  70. $result = array();
  71. foreach ($this->ignore_patterns as $pattern) {
  72. $result[] = $pattern[0];
  73. }
  74. return $result;
  75. }
  76. /**
  77. * Adds an ignore pattern to the collection.
  78. *
  79. * @param string $pattern Glob-like pattern to filter files with.
  80. *
  81. * @return void
  82. */
  83. public function addIgnorePattern($pattern)
  84. {
  85. $this->convertToPregCompliant($pattern);
  86. $this->ignore_patterns[] = array($pattern, 0);
  87. }
  88. /**
  89. * Sets a list of allowed extensions; if not used php, php3 and phtml
  90. * is assumed.
  91. *
  92. * @param array $extensions An array containing extensions to match for.
  93. *
  94. * @return void
  95. */
  96. public function setAllowedExtensions(array $extensions)
  97. {
  98. $this->allowed_extensions = array();
  99. foreach ($extensions as $extension) {
  100. $this->addAllowedExtension($extension);
  101. }
  102. }
  103. /**
  104. * Adds a file extension to the list of allowed extensions.
  105. *
  106. * No dot is necessary and will even prevent the extension from being
  107. * picked up.
  108. *
  109. * @param string $extension Allowed file Extension to add (i.e. php).
  110. *
  111. * @return void
  112. */
  113. public function addAllowedExtension($extension)
  114. {
  115. $this->allowed_extensions[] = $extension;
  116. }
  117. /**
  118. * Adds the content of a set of directories to the list of files to parse.
  119. *
  120. * @param array $paths The paths whose contents to add to the collection.
  121. *
  122. * @return void
  123. */
  124. public function addDirectories(array $paths)
  125. {
  126. foreach ($paths as $path) {
  127. $this->addDirectory($path);
  128. }
  129. }
  130. /**
  131. * Retrieve all files in the given directory and add them to the parsing list.
  132. *
  133. * @param string $path A path to a folder, may be relative, absolute or
  134. * even phar.
  135. *
  136. * @throws InvalidArgumentException if the given path is not a folder.
  137. *
  138. * @return void
  139. */
  140. public function addDirectory($path)
  141. {
  142. $result = glob($path);
  143. if ($result === false) {
  144. throw new DocBlox_Parser_Exception(
  145. '"'.$path . '" does not match an existing directory pattern'
  146. );
  147. }
  148. // if the given path is the only one AND there are no registered files.
  149. // then use this as project root instead of the calculated version.
  150. // This will make sure than when a _single_ path is given, that the
  151. // root will not inadvertently skip to a higher location because no
  152. // file were found in the given location.
  153. // i.e. if only path `src` us given and no PHP files reside there, but
  154. // they do reside in `src/php` then with this code `src` will remain
  155. // root so that ignore statements work as expected. Without this the
  156. // root would be `src/php`, which is unexpected when only a single folder
  157. // is provided.
  158. if ((count($result) == 1) && (empty($this->files))) {
  159. $this->project_root = realpath(reset($result));
  160. } else {
  161. $this->project_root = null;
  162. }
  163. foreach($result as $result_path) {
  164. // if the given is not a directory, skip it
  165. if (!is_dir($result_path)) {
  166. continue;
  167. }
  168. // get all files recursively to the files array
  169. $files_iterator = new RecursiveDirectoryIterator($result_path);
  170. // add the CATCH_GET_CHILD option to make sure that an unreadable
  171. // directory does not halt process but skip that folder
  172. $recursive_iterator = new RecursiveIteratorIterator(
  173. $files_iterator,
  174. RecursiveIteratorIterator::LEAVES_ONLY,
  175. RecursiveIteratorIterator::CATCH_GET_CHILD
  176. );
  177. /** @var SplFileInfo $file */
  178. foreach ($recursive_iterator as $file) {
  179. // skipping dots (should any be encountered)
  180. if (($file->getFilename() == '.') || ($file->getFilename() == '..')) {
  181. continue;
  182. }
  183. // Phar files return false on a call to getRealPath
  184. $this->addFile(
  185. (substr($file->getPathname(), 0, 7) != 'phar://')
  186. ? $file->getRealPath()
  187. : $file->getPathname()
  188. );
  189. }
  190. }
  191. }
  192. /**
  193. * Adds a list of individual files to the collection.
  194. *
  195. * @param array $paths File locations, may be absolute, relative or even phar.
  196. *
  197. * @return void
  198. */
  199. public function addFiles(array $paths)
  200. {
  201. if (!empty($paths)) {
  202. // if separate files are provided then the root must always be
  203. // calculated.
  204. $this->project_root = null;
  205. }
  206. foreach ($paths as $path) {
  207. $this->addFile($path);
  208. }
  209. }
  210. /**
  211. * Adds a file to the collection.
  212. *
  213. * @param string $path File location, may be absolute, relative or even phar.
  214. *
  215. * @return void
  216. */
  217. public function addFile($path)
  218. {
  219. // if it is not a file contained in a phar; check it out with a glob
  220. if (substr($path, 0, 7) != 'phar://') {
  221. // search file(s) with the given expressions
  222. $result = glob($path);
  223. foreach ($result as $file) {
  224. // if the path is not a file OR it's extension does not match
  225. // the given, then do not process it.
  226. if (!is_file($file) || (!empty($this->allowed_extensions)
  227. && !in_array(
  228. strtolower(pathinfo($file, PATHINFO_EXTENSION)),
  229. $this->allowed_extensions
  230. ))
  231. ) {
  232. continue;
  233. }
  234. $this->files[] = realpath($file);
  235. }
  236. } else {
  237. // only process if it is a file and it matches the allowed extensions
  238. if (is_file($path) && (empty($this->allowed_extensions)
  239. || in_array(
  240. strtolower(pathinfo($path, PATHINFO_EXTENSION)),
  241. $this->allowed_extensions
  242. ))
  243. ) {
  244. $this->files[] = $path;
  245. }
  246. }
  247. }
  248. /**
  249. * Returns a list of files that are ready to be parsed.
  250. *
  251. * Please note that the ignore pattern will be applied and all files are
  252. * converted to absolute paths.
  253. *
  254. * @return string[]
  255. */
  256. public function getFiles()
  257. {
  258. $result = array();
  259. foreach ($this->files as $filename) {
  260. // check whether this file is ignored; we do this in two steps:
  261. // 1. Determine whether this is a relative or absolute path, if the
  262. // string does not start with *, ?, / or \ then we assume that it is
  263. // a relative path
  264. // 2. check whether the given pattern matches with the filename (or
  265. // relative filename in case of a relative comparison)
  266. foreach ($this->ignore_patterns as $key => $pattern) {
  267. $glob = $pattern[0];
  268. if ((($glob[0] !== '*')
  269. && ($glob[0] !== '?')
  270. && ($glob[0] !== '/')
  271. && ($glob[0] !== '\\')
  272. && (preg_match(
  273. '/^' . $glob . '$/',
  274. $this->getRelativeFilename($filename)
  275. )))
  276. || (preg_match('/^' . $glob . '$/', $filename))
  277. ) {
  278. // increase ignore usage with 1
  279. $this->ignore_patterns[$key][1]++;
  280. $this->log(
  281. 'File "' . $filename . '" matches ignore pattern, '
  282. . 'will be skipped', DocBlox_Core_Log::INFO
  283. );
  284. continue 2;
  285. }
  286. }
  287. $result[] = $filename;
  288. }
  289. // detect if ignore patterns have been unused
  290. foreach ($this->ignore_patterns as $pattern) {
  291. if ($pattern[1] < 1) {
  292. $this->log(
  293. 'Ignore pattern "' . $pattern[0] . '" has not been used '
  294. . 'during processing'
  295. );
  296. }
  297. }
  298. return $result;
  299. }
  300. /**
  301. * Calculates the project root from the given files by determining their
  302. * highest common path.
  303. *
  304. * @return string
  305. */
  306. public function getProjectRoot()
  307. {
  308. if ($this->project_root === null) {
  309. $base = '';
  310. $file = reset($this->files);
  311. // realpath does not work on phar files
  312. $file = (substr($file, 0, 7) != 'phar://')
  313. ? realpath($file)
  314. : $file;
  315. $parts = explode(DIRECTORY_SEPARATOR, $file);
  316. foreach ($parts as $part) {
  317. $base_part = $base . $part . DIRECTORY_SEPARATOR;
  318. foreach ($this->files as $dir) {
  319. // realpath does not work on phar files
  320. $dir = (substr($dir, 0, 7) != 'phar://')
  321. ? realpath($dir)
  322. : $dir;
  323. if (substr($dir, 0, strlen($base_part)) != $base_part) {
  324. return $base;
  325. }
  326. }
  327. $base = $base_part;
  328. }
  329. $this->project_root = $base;
  330. }
  331. return $this->project_root;
  332. }
  333. /**
  334. * Converts $string into a string that can be used with preg_match.
  335. *
  336. * @param string &$string Glob-like pattern with wildcards ? and *.
  337. *
  338. * @author Greg Beaver <cellog@php.net>
  339. * @author mike van Riel <mike.vanriel@naenius.com>
  340. *
  341. * @see PhpDocumentor/phpDocumentor/Io.php
  342. *
  343. * @return void
  344. */
  345. protected function convertToPregCompliant(&$string)
  346. {
  347. $y = (DIRECTORY_SEPARATOR == '\\') ? '\\\\' : '\/';
  348. $string = str_replace('/', DIRECTORY_SEPARATOR, $string);
  349. $x = strtr(
  350. $string,
  351. array(
  352. '?' => '.',
  353. '*' => '.*',
  354. '.' => '\\.',
  355. '\\' => '\\\\',
  356. '/' => '\\/',
  357. '[' => '\\[',
  358. ']' => '\\]',
  359. '-' => '\\-'
  360. )
  361. );
  362. if ((strpos($string, DIRECTORY_SEPARATOR) !== false)
  363. && (strrpos($string, DIRECTORY_SEPARATOR) === strlen($string) - 1)
  364. ) {
  365. $x = "(?:.*$y$x?.*|$x.*)";
  366. }
  367. $string = $x;
  368. }
  369. /**
  370. * Returns the filename, relative to the root of the project directory.
  371. *
  372. * @param string $filename The filename to make relative.
  373. *
  374. * @throws InvalidArgumentException if file is not in the project root.
  375. *
  376. * @return string
  377. */
  378. protected function getRelativeFilename($filename)
  379. {
  380. // strip path from filename
  381. $result = ltrim(substr($filename, strlen($this->getProjectRoot())), '/');
  382. if ($result === '') {
  383. throw new InvalidArgumentException(
  384. 'File is not present in the given project path: ' . $filename
  385. );
  386. }
  387. return $result;
  388. }
  389. }