PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/ApiGen/Generator.php

https://github.com/sethmay/apigen
PHP | 1572 lines | 1094 code | 163 blank | 315 comment | 115 complexity | 316a8ae9a0bc87f51e40f48a8803c736 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * ApiGen 2.2.0 - API documentation generator for PHP 5.3+
  4. *
  5. * Copyright (c) 2010 David Grudl (http://davidgrudl.com)
  6. * Copyright (c) 2011 Jaroslav Hanslík (https://github.com/kukulich)
  7. * Copyright (c) 2011 Ondřej Nešpor (https://github.com/Andrewsville)
  8. *
  9. * For the full copyright and license information, please view
  10. * the file LICENSE.md that was distributed with this source code.
  11. */
  12. namespace ApiGen;
  13. use Nette, FSHL;
  14. use TokenReflection\Broker;
  15. /**
  16. * Generates a HTML API documentation.
  17. *
  18. * @author Jaroslav Hanslík
  19. * @author Ondřej Nešpor
  20. * @author David Grudl
  21. */
  22. class Generator extends Nette\Object
  23. {
  24. /**
  25. * Library name.
  26. *
  27. * @var string
  28. */
  29. const NAME = 'ApiGen';
  30. /**
  31. * Library version.
  32. *
  33. * @var string
  34. */
  35. const VERSION = '2.2.0';
  36. /**
  37. * Configuration.
  38. *
  39. * @var \ApiGen\Config
  40. */
  41. private $config;
  42. /**
  43. * List of parsed classes.
  44. *
  45. * @var \ArrayObject
  46. */
  47. private $parsedClasses = null;
  48. /**
  49. * List of parsed constants.
  50. *
  51. * @var \ArrayObject
  52. */
  53. private $parsedConstants = null;
  54. /**
  55. * List of parsed functions.
  56. *
  57. * @var \ArrayObject
  58. */
  59. private $parsedFunctions = null;
  60. /**
  61. * List of packages.
  62. *
  63. * @var array
  64. */
  65. private $packages = array();
  66. /**
  67. * List of namespaces.
  68. *
  69. * @var array
  70. */
  71. private $namespaces = array();
  72. /**
  73. * List of classes.
  74. *
  75. * @var array
  76. */
  77. private $classes = array();
  78. /**
  79. * List of interfaces.
  80. *
  81. * @var array
  82. */
  83. private $interfaces = array();
  84. /**
  85. * List of traits.
  86. *
  87. * @var array
  88. */
  89. private $traits = array();
  90. /**
  91. * List of exceptions.
  92. *
  93. * @var array
  94. */
  95. private $exceptions = array();
  96. /**
  97. * List of constants.
  98. *
  99. * @var array
  100. */
  101. private $constants = array();
  102. /**
  103. * List of functions.
  104. *
  105. * @var array
  106. */
  107. private $functions = array();
  108. /**
  109. * List of symlinks.
  110. *
  111. * @var array
  112. */
  113. private $symlinks = array();
  114. /**
  115. * Progressbar settings and status.
  116. *
  117. * @var array
  118. */
  119. private $progressbar = array(
  120. 'skeleton' => '[%s] %\' 6.2f%%',
  121. 'width' => 80,
  122. 'bar' => 70,
  123. 'current' => 0,
  124. 'maximum' => 1
  125. );
  126. /**
  127. * Sets configuration.
  128. *
  129. * @param array $config
  130. */
  131. public function __construct(Config $config)
  132. {
  133. $this->config = $config;
  134. $this->parsedClasses = new \ArrayObject();
  135. }
  136. /**
  137. * Scans and parses PHP files.
  138. *
  139. * @return array
  140. * @throws \ApiGen\Exception If no PHP files have been found.
  141. */
  142. public function parse()
  143. {
  144. $files = array();
  145. $flags = \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO | \RecursiveDirectoryIterator::SKIP_DOTS;
  146. if (defined('\RecursiveDirectoryIterator::FOLLOW_SYMLINKS')) {
  147. // Available from PHP 5.3.1
  148. $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
  149. }
  150. foreach ($this->config->source as $source) {
  151. $entries = array();
  152. if (is_dir($source)) {
  153. foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, $flags)) as $entry) {
  154. if (!$entry->isFile()) {
  155. continue;
  156. }
  157. $entries[] = $entry;
  158. }
  159. } else {
  160. $entries[] = new \SplFileInfo($source);
  161. }
  162. foreach ($entries as $entry) {
  163. $includeFile = false;
  164. // Check to see if the files should be included
  165. foreach ($this->config->include as $mask) {
  166. if (fnmatch($mask, $entry->getPathName(), FNM_NOESCAPE)) {
  167. $includeFile = true;
  168. }
  169. }
  170. // Check to see if the files matches an exlude mask
  171. foreach ($this->config->exclude as $mask) {
  172. if (fnmatch($mask, $entry->getPathName(), FNM_NOESCAPE)) {
  173. $includeFile = false;
  174. }
  175. }
  176. if ($includeFile)
  177. {
  178. $files[$entry->getPathName()] = $entry->getSize();
  179. if ($entry->getPathName() !== $entry->getRealPath()) {
  180. $this->symlinks[$entry->getRealPath()] = $entry->getPathName();
  181. }
  182. }
  183. }
  184. }
  185. if (empty($files)) {
  186. throw new Exception('No PHP files found.');
  187. }
  188. if ($this->config->progressbar) {
  189. $this->prepareProgressBar(array_sum($files));
  190. }
  191. $broker = new Broker(new Backend($this, !empty($this->config->undocumented)), Broker::OPTION_DEFAULT & ~(Broker::OPTION_PARSE_FUNCTION_BODY | Broker::OPTION_SAVE_TOKEN_STREAM));
  192. foreach ($files as $file => $size) {
  193. $broker->processFile($file);
  194. $this->incrementProgressBar($size);
  195. }
  196. // Classes
  197. $this->parsedClasses->exchangeArray($broker->getClasses(Backend::TOKENIZED_CLASSES | Backend::INTERNAL_CLASSES | Backend::NONEXISTENT_CLASSES));
  198. $this->parsedClasses->uksort('strcasecmp');
  199. // Constants
  200. $this->parsedConstants = new \ArrayObject($broker->getConstants());
  201. $this->parsedConstants->uksort('strcasecmp');
  202. // Functions
  203. $this->parsedFunctions = new \ArrayObject($broker->getFunctions());
  204. $this->parsedFunctions->uksort('strcasecmp');
  205. $documentedCounter = function($count, $element) {
  206. return $count += (int) $element->isDocumented();
  207. };
  208. return array(
  209. count($broker->getClasses(Backend::TOKENIZED_CLASSES)),
  210. count($this->parsedConstants),
  211. count($this->parsedFunctions),
  212. count($broker->getClasses(Backend::INTERNAL_CLASSES)),
  213. array_reduce($broker->getClasses(Backend::TOKENIZED_CLASSES), $documentedCounter),
  214. array_reduce($this->parsedConstants->getArrayCopy(), $documentedCounter),
  215. array_reduce($this->parsedFunctions->getArrayCopy(), $documentedCounter),
  216. array_reduce($broker->getClasses(Backend::INTERNAL_CLASSES), $documentedCounter)
  217. );
  218. }
  219. /**
  220. * Returns configuration.
  221. *
  222. * @return mixed
  223. */
  224. public function getConfig()
  225. {
  226. return $this->config;
  227. }
  228. /**
  229. * Returns parsed class list.
  230. *
  231. * @return \ArrayObject
  232. */
  233. public function getParsedClasses()
  234. {
  235. return $this->parsedClasses;
  236. }
  237. /**
  238. * Returns parsed constant list.
  239. *
  240. * @return \ArrayObject
  241. */
  242. public function getParsedConstants()
  243. {
  244. return $this->parsedConstants;
  245. }
  246. /**
  247. * Returns parsed function list.
  248. *
  249. * @return \ArrayObject
  250. */
  251. public function getParsedFunctions()
  252. {
  253. return $this->parsedFunctions;
  254. }
  255. /**
  256. * Wipes out the destination directory.
  257. *
  258. * @return boolean
  259. */
  260. public function wipeOutDestination()
  261. {
  262. // Temporary directory
  263. $tmpDir = $this->config->destination . '/tmp';
  264. if (is_dir($tmpDir) && !$this->deleteDir($tmpDir)) {
  265. return false;
  266. }
  267. // Resources
  268. foreach ($this->config->template['resources'] as $resource) {
  269. $path = $this->config->destination . '/' . $resource;
  270. if (is_dir($path) && !$this->deleteDir($path)) {
  271. return false;
  272. } elseif (is_file($path) && !@unlink($path)) {
  273. return false;
  274. }
  275. }
  276. // Common files
  277. $filenames = array_keys($this->config->template['templates']['common']);
  278. foreach (Nette\Utils\Finder::findFiles($filenames)->from($this->config->destination) as $item) {
  279. if (!@unlink($item)) {
  280. return false;
  281. }
  282. }
  283. // Optional files
  284. foreach ($this->config->template['templates']['optional'] as $optional) {
  285. $file = $this->config->destination . '/' . $optional['filename'];
  286. if (is_file($file) && !@unlink($file)) {
  287. return false;
  288. }
  289. }
  290. // Main files
  291. $masks = array_map(function($config) {
  292. return preg_replace('~%[^%]*?s~', '*', $config['filename']);
  293. }, $this->config->template['templates']['main']);
  294. $filter = function($item) use ($masks) {
  295. foreach ($masks as $mask) {
  296. if (fnmatch($mask, $item->getFilename())) {
  297. return true;
  298. }
  299. }
  300. return false;
  301. };
  302. foreach (Nette\Utils\Finder::findFiles('*')->filter($filter)->from($this->config->destination) as $item) {
  303. if (!@unlink($item)) {
  304. return false;
  305. }
  306. }
  307. return true;
  308. }
  309. /**
  310. * Generates API documentation.
  311. *
  312. * @throws \ApiGen\Exception If destination directory is not writable.
  313. */
  314. public function generate()
  315. {
  316. @mkdir($this->config->destination, 0755, true);
  317. if (!is_dir($this->config->destination) || !is_writable($this->config->destination)) {
  318. throw new Exception(sprintf('Directory %s isn\'t writable.', $this->config->destination));
  319. }
  320. // Copy resources
  321. foreach ($this->config->template['resources'] as $resourceSource => $resourceDestination) {
  322. // File
  323. $resourcePath = $this->getTemplateDir() . '/' . $resourceSource;
  324. if (is_file($resourcePath)) {
  325. copy($resourcePath, $this->forceDir($this->config->destination . '/' . $resourceDestination));
  326. continue;
  327. }
  328. // Dir
  329. foreach ($iterator = Nette\Utils\Finder::findFiles('*')->from($resourcePath)->getIterator() as $item) {
  330. copy($item->getPathName(), $this->forceDir($this->config->destination . '/' . $resourceDestination . '/' . $iterator->getSubPathName()));
  331. }
  332. }
  333. // Categorize by packages and namespaces
  334. $this->categorize();
  335. // Prepare progressbar
  336. if ($this->config->progressbar) {
  337. $max = count($this->packages)
  338. + count($this->namespaces)
  339. + count($this->classes)
  340. + count($this->interfaces)
  341. + count($this->traits)
  342. + count($this->exceptions)
  343. + count($this->constants)
  344. + count($this->functions)
  345. + count($this->config->template['templates']['common'])
  346. + (int) !empty($this->config->undocumented)
  347. + (int) $this->config->tree
  348. + (int) $this->config->deprecated
  349. + (int) $this->config->todo
  350. + (int) $this->isSitemapEnabled()
  351. + (int) $this->isOpensearchEnabled()
  352. + (int) $this->isRobotsEnabled();
  353. if ($this->config->sourceCode) {
  354. $tokenizedFilter = function(ReflectionClass $class) {
  355. return $class->isTokenized();
  356. };
  357. $max += count(array_filter($this->classes, $tokenizedFilter))
  358. + count(array_filter($this->interfaces, $tokenizedFilter))
  359. + count(array_filter($this->traits, $tokenizedFilter))
  360. + count(array_filter($this->exceptions, $tokenizedFilter))
  361. + count($this->constants)
  362. + count($this->functions);
  363. unset($tokenizedFilter);
  364. }
  365. $this->prepareProgressBar($max);
  366. }
  367. // Create temporary directory
  368. $tmp = $this->config->destination . DIRECTORY_SEPARATOR . 'tmp';
  369. @mkdir($tmp, 0755, true);
  370. // Prepare template
  371. $template = new Template($this);
  372. $template->setCacheStorage(new Nette\Caching\Storages\PhpFileStorage($tmp));
  373. $template->generator = self::NAME;
  374. $template->version = self::VERSION;
  375. $template->config = $this->config;
  376. // Common files
  377. $this->generateCommon($template);
  378. // Optional files
  379. $this->generateOptional($template);
  380. // List of undocumented elements
  381. if (!empty($this->config->undocumented)) {
  382. $this->generateUndocumented();
  383. }
  384. // List of deprecated elements
  385. if ($this->config->deprecated) {
  386. $this->generateDeprecated($template);
  387. }
  388. // List of tasks
  389. if ($this->config->todo) {
  390. $this->generateTodo($template);
  391. }
  392. // Classes/interfaces/traits/exceptions tree
  393. if ($this->config->tree) {
  394. $this->generateTree($template);
  395. }
  396. // Generate packages summary
  397. $this->generatePackages($template);
  398. // Generate namespaces summary
  399. $this->generateNamespaces($template);
  400. // Generate classes, interfaces, traits, exceptions, constants and functions files
  401. $this->generateElements($template);
  402. // Delete temporary directory
  403. $this->deleteDir($tmp);
  404. }
  405. /**
  406. * Categorizes by packages and namespaces.
  407. *
  408. * @return \ApiGen\Generator
  409. */
  410. private function categorize()
  411. {
  412. foreach (array('classes', 'constants', 'functions') as $type) {
  413. foreach ($this->{'parsed' . ucfirst($type)} as $elementName => $element) {
  414. if (!$element->isDocumented()) {
  415. continue;
  416. }
  417. $packageName = $element->getPseudoPackageName();
  418. $namespaceName = $element->getPseudoNamespaceName();
  419. if ($element instanceof ReflectionConstant) {
  420. $this->constants[$elementName] = $element;
  421. $this->packages[$packageName]['constants'][$elementName] = $element;
  422. $this->namespaces[$namespaceName]['constants'][$element->getShortName()] = $element;
  423. } elseif ($element instanceof ReflectionFunction) {
  424. $this->functions[$elementName] = $element;
  425. $this->packages[$packageName]['functions'][$elementName] = $element;
  426. $this->namespaces[$namespaceName]['functions'][$element->getShortName()] = $element;
  427. } elseif ($element->isInterface()) {
  428. $this->interfaces[$elementName] = $element;
  429. $this->packages[$packageName]['interfaces'][$elementName] = $element;
  430. $this->namespaces[$namespaceName]['interfaces'][$element->getShortName()] = $element;
  431. } elseif ($element->isTrait()) {
  432. $this->traits[$elementName] = $element;
  433. $this->packages[$packageName]['traits'][$elementName] = $element;
  434. $this->namespaces[$namespaceName]['traits'][$element->getShortName()] = $element;
  435. } elseif ($element->isException()) {
  436. $this->exceptions[$elementName] = $element;
  437. $this->packages[$packageName]['exceptions'][$elementName] = $element;
  438. $this->namespaces[$namespaceName]['exceptions'][$element->getShortName()] = $element;
  439. } else {
  440. $this->classes[$elementName] = $element;
  441. $this->packages[$packageName]['classes'][$elementName] = $element;
  442. $this->namespaces[$namespaceName]['classes'][$element->getShortName()] = $element;
  443. }
  444. }
  445. }
  446. // Sorting for namespaces and packages
  447. $main = $this->config->main;
  448. $sort = function($one, $two) use ($main) {
  449. // \ as separator has to be first
  450. $one = str_replace('\\', ' ', $one);
  451. $two = str_replace('\\', ' ', $two);
  452. if ($main) {
  453. if (0 === strpos($one, $main) && 0 !== strpos($two, $main)) {
  454. return -1;
  455. } elseif (0 !== strpos($one, $main) && 0 === strpos($two, $main)) {
  456. return 1;
  457. }
  458. }
  459. return strcasecmp($one, $two);
  460. };
  461. // Select only packages or namespaces
  462. $userPackages = count(array_diff(array_keys($this->packages), array('PHP', 'None')));
  463. $userNamespaces = count(array_diff(array_keys($this->namespaces), array('PHP', 'None')));
  464. if ($userNamespaces > 0 || 0 === $userPackages) {
  465. $this->packages = array();
  466. // Don't generate only 'None' namespace
  467. if (1 === count($this->namespaces) && isset($this->namespaces['None'])) {
  468. $this->namespaces = array();
  469. }
  470. foreach (array_keys($this->namespaces) as $namespaceName) {
  471. // Add missing parent namespaces
  472. $parent = '';
  473. foreach (explode('\\', $namespaceName) as $part) {
  474. $parent = ltrim($parent . '\\' . $part, '\\');
  475. if (!isset($this->namespaces[$parent])) {
  476. $this->namespaces[$parent] = array('classes' => array(), 'interfaces' => array(), 'traits' => array(), 'exceptions' => array(), 'constants' => array(), 'functions' => array());
  477. }
  478. }
  479. // Add missing element types
  480. foreach ($this->getElementTypes() as $type) {
  481. if (!isset($this->namespaces[$namespaceName][$type])) {
  482. $this->namespaces[$namespaceName][$type] = array();
  483. }
  484. }
  485. }
  486. uksort($this->namespaces, $sort);
  487. } else {
  488. $this->namespaces = array();
  489. foreach (array_keys($this->packages) as $packageName) {
  490. // Add missing parent packages
  491. $parent = '';
  492. foreach (explode('\\', $packageName) as $part) {
  493. $parent = ltrim($parent . '\\' . $part, '\\');
  494. if (!isset($this->packages[$parent])) {
  495. $this->packages[$parent] = array('classes' => array(), 'interfaces' => array(), 'traits' => array(), 'exceptions' => array(), 'constants' => array(), 'functions' => array());
  496. }
  497. }
  498. // Add missing class types
  499. foreach ($this->getElementTypes() as $type) {
  500. if (!isset($this->packages[$packageName][$type])) {
  501. $this->packages[$packageName][$type] = array();
  502. }
  503. }
  504. }
  505. uksort($this->packages, $sort);
  506. }
  507. return $this;
  508. }
  509. /**
  510. * Generates common files.
  511. *
  512. * @param \ApiGen\Template $template Template
  513. * @return \ApiGen\Generator
  514. */
  515. private function generateCommon(Template $template)
  516. {
  517. $template->namespace = null;
  518. $template->namespaces = array_keys($this->namespaces);
  519. $template->package = null;
  520. $template->packages = array_keys($this->packages);
  521. $template->class = null;
  522. $template->classes = array_filter($this->classes, $this->getMainFilter());
  523. $template->interfaces = array_filter($this->interfaces, $this->getMainFilter());
  524. $template->traits = array_filter($this->traits, $this->getMainFilter());
  525. $template->exceptions = array_filter($this->exceptions, $this->getMainFilter());
  526. $template->constant = null;
  527. $template->constants = array_filter($this->constants, $this->getMainFilter());
  528. $template->function = null;
  529. $template->functions = array_filter($this->functions, $this->getMainFilter());
  530. // Elements for autocomplete
  531. $elements = array();
  532. foreach ($this->getElementTypes() as $type) {
  533. foreach ($this->$type as $element) {
  534. $type = $element instanceof ReflectionClass ? 'class' : ($element instanceof ReflectionConstant ? 'constant' : 'function');
  535. $elements[] = array($type, $element->getName());
  536. }
  537. }
  538. usort($elements, function($one, $two) {
  539. return strcasecmp($one[1], $two[1]);
  540. });
  541. $template->elements = $elements;
  542. foreach ($this->config->template['templates']['common'] as $dest => $source) {
  543. $template
  544. ->setFile($this->getTemplateDir() . '/' . $source)
  545. ->save($this->forceDir($this->config->destination . '/' . $dest));
  546. $this->incrementProgressBar();
  547. }
  548. unset($template->elements);
  549. return $this;
  550. }
  551. /**
  552. * Generates optional files.
  553. *
  554. * @param \ApiGen\Template $template Template
  555. * @return \ApiGen\Generator
  556. */
  557. private function generateOptional(Template $template)
  558. {
  559. if ($this->isSitemapEnabled()) {
  560. $template
  561. ->setFile($this->getTemplatePath('sitemap', 'optional'))
  562. ->save($this->forceDir($this->getTemplateFileName('sitemap', 'optional')));
  563. $this->incrementProgressBar();
  564. }
  565. if ($this->isOpensearchEnabled()) {
  566. $template
  567. ->setFile($this->getTemplatePath('opensearch', 'optional'))
  568. ->save($this->forceDir($this->getTemplateFileName('opensearch', 'optional')));
  569. $this->incrementProgressBar();
  570. }
  571. if ($this->isRobotsEnabled()) {
  572. $template
  573. ->setFile($this->getTemplatePath('robots', 'optional'))
  574. ->save($this->forceDir($this->getTemplateFileName('robots', 'optional')));
  575. $this->incrementProgressBar();
  576. }
  577. return $this;
  578. }
  579. /**
  580. * Generates list of undocumented elements.
  581. *
  582. * @return \ApiGen\Generator
  583. * @throws \ApiGen\Exception If file isn't writable.
  584. */
  585. private function generateUndocumented()
  586. {
  587. // Function for element labels
  588. $labeler = function($element) {
  589. if ($element instanceof ReflectionClass) {
  590. if ($element->isInterface()) {
  591. return sprintf('interface %s', $element->getName());
  592. } elseif ($element->isTrait()) {
  593. return sprintf('trait %s', $element->getName());
  594. } elseif ($element->isException()) {
  595. return sprintf('exception %s', $element->getName());
  596. } else {
  597. return sprintf('class %s', $element->getName());
  598. }
  599. } elseif ($element instanceof ReflectionMethod) {
  600. return sprintf('method %s::%s()', $element->getDeclaringClassName(), $element->getName());
  601. } elseif ($element instanceof ReflectionFunction) {
  602. return sprintf('function %s()', $element->getName());
  603. } elseif ($element instanceof ReflectionConstant) {
  604. if ($className = $element->getDeclaringClassName()) {
  605. return sprintf('constant %s::%s', $className, $element->getName());
  606. } else {
  607. return sprintf('constant %s', $element->getName());
  608. }
  609. } elseif ($element instanceof ReflectionProperty) {
  610. return sprintf('property %s::$%s', $element->getDeclaringClassName(), $element->getName());
  611. } elseif ($element instanceof ReflectionParameter) {
  612. if ($element->getDeclaringFunction() instanceof ReflectionMethod) {
  613. $parentLabel = sprintf('method %s::%s()', $element->getDeclaringClassName(), $element->getDeclaringFunctionName());
  614. } else {
  615. $parentLabel = sprintf('function %s()', $element->getDeclaringFunctionName());
  616. }
  617. return sprintf('parameter $%s of %s', $element->getName(), $parentLabel);
  618. }
  619. };
  620. $undocumented = array();
  621. foreach ($this->getElementTypes() as $type) {
  622. foreach ($this->$type as $parentElement) {
  623. // Skip elements not from the main project
  624. if (!$parentElement->isMain()) {
  625. continue;
  626. }
  627. // Internal elements don't have documentation
  628. if ($parentElement->isInternal()) {
  629. continue;
  630. }
  631. $elements = array($parentElement);
  632. if ($parentElement instanceof ReflectionClass) {
  633. $elements = array_merge(
  634. $elements,
  635. array_values($parentElement->getOwnMethods()),
  636. array_values($parentElement->getOwnConstants()),
  637. array_values($parentElement->getOwnProperties())
  638. );
  639. }
  640. $fileName = $parentElement->getFileName();
  641. $tokens = $parentElement->getBroker()->getFileTokens($parentElement->getFileName());
  642. foreach ($elements as $element) {
  643. $line = $element->getStartLine();
  644. $label = $labeler($element);
  645. $annotations = $element->getAnnotations();
  646. // Documentation
  647. if (empty($element->shortDescription)) {
  648. if (empty($annotations)) {
  649. $undocumented[$fileName][] = array('error', $line, sprintf('Missing documentation of %s', $label));
  650. continue;
  651. }
  652. // Description
  653. $undocumented[$fileName][] = array('error', $line, sprintf('Missing description of %s', $label));
  654. }
  655. // Documentation of method
  656. if ($element instanceof ReflectionMethod || $element instanceof ReflectionFunction) {
  657. // Parameters
  658. foreach ($element->getParameters() as $no => $parameter) {
  659. if (!isset($annotations['param'][$no])) {
  660. $undocumented[$fileName][] = array('error', $line, sprintf('Missing documentation of %s', $labeler($parameter)));
  661. continue;
  662. }
  663. if (!preg_match('~^[\\w\\\\]+(?:\\|[\\w\\\\]+)*\\s+\$' . $parameter->getName() . '(?:\\s+.+)?$~s', $annotations['param'][$no])) {
  664. $undocumented[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of %s', $annotations['param'][$no], $labeler($parameter)));
  665. }
  666. unset($annotations['param'][$no]);
  667. }
  668. if (isset($annotations['param'])) {
  669. foreach ($annotations['param'] as $annotation) {
  670. $undocumented[$fileName][] = array('warning', $line, sprintf('Existing documentation "%s" of nonexistent parameter of %s', $annotation, $label));
  671. }
  672. }
  673. // Return values
  674. $return = false;
  675. $tokens->seek($element->getStartPosition())
  676. ->find(T_FUNCTION);
  677. while ($tokens->next() && $tokens->key() < $element->getEndPosition()) {
  678. $type = $tokens->getType();
  679. if (T_FUNCTION === $type) {
  680. // Skip annonymous functions
  681. $tokens->find('{')->findMatchingBracket();
  682. } elseif (T_RETURN === $type && !$tokens->skipWhitespaces()->is(';')) {
  683. // Skip return without return value
  684. $return = true;
  685. break;
  686. }
  687. }
  688. if ($return && !isset($annotations['return'])) {
  689. $undocumented[$fileName][] = array('error', $line, sprintf('Missing documentation of return value of %s', $label));
  690. } elseif (isset($annotations['return'])) {
  691. if (!$return && 'void' !== $annotations['return'][0] && ($element instanceof ReflectionFunction || (!$parentElement->isInterface() && !$element->isAbstract()))) {
  692. $undocumented[$fileName][] = array('warning', $line, sprintf('Existing documentation "%s" of nonexistent return value of %s', $annotations['return'][0], $label));
  693. } elseif (!preg_match('~^[\\w\\\\]+(?:\\|[\\w\\\\]+)*(?:\\s+.+)?$~s', $annotations['return'][0])) {
  694. $undocumented[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of return value of %s', $annotations['return'][0], $label));
  695. }
  696. }
  697. if (isset($annotations['return'][1])) {
  698. $undocumented[$fileName][] = array('warning', $line, sprintf('Duplicate documentation "%s" of return value of %s', $annotations['return'][1], $label));
  699. }
  700. // Throwing exceptions
  701. $throw = false;
  702. $tokens->seek($element->getStartPosition())
  703. ->find(T_FUNCTION);
  704. while ($tokens->next() && $tokens->key() < $element->getEndPosition()) {
  705. $type = $tokens->getType();
  706. if (T_TRY === $type) {
  707. // Skip try
  708. $tokens->find('{')->findMatchingBracket();
  709. } elseif (T_THROW === $type) {
  710. $throw = true;
  711. break;
  712. }
  713. }
  714. if ($throw && !isset($annotations['throws'])) {
  715. $undocumented[$fileName][] = array('error', $line, sprintf('Missing documentation of throwing an exception of %s', $label));
  716. } elseif (isset($annotations['throws']) && !preg_match('~^[\\w\\\\]+(?:\\|[\w\\\\]+)*(?:\\s+.+)?$~s', $annotations['throws'][0])) {
  717. $undocumented[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of throwing an exception of %s', $annotations['throws'][0], $label));
  718. }
  719. }
  720. // Data type of constants & properties
  721. if ($element instanceof ReflectionProperty || $element instanceof ReflectionConstant) {
  722. if (!isset($annotations['var'])) {
  723. $undocumented[$fileName][] = array('error', $line, sprintf('Missing documentation of the data type of %s', $label));
  724. } elseif (!preg_match('~^[\\w\\\\]+(?:\\|[\w\\\\]+)*(?:\\s+.+)?$~s', $annotations['var'][0])) {
  725. $undocumented[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of the data type of %s', $annotations['var'][0], $label));
  726. }
  727. if (isset($annotations['var'][1])) {
  728. $undocumented[$fileName][] = array('warning', $line, sprintf('Duplicate documentation "%s" of the data type of %s', $annotations['var'][1], $label));
  729. }
  730. }
  731. }
  732. unset($tokens);
  733. }
  734. }
  735. uksort($undocumented, 'strcasecmp');
  736. $file = @fopen($this->config->undocumented, 'w');
  737. if (false === $file) {
  738. throw new Exception(sprintf('File %s isn\'t writable.', $this->config->undocumented));
  739. }
  740. fwrite($file, sprintf('<?xml version="1.0" encoding="UTF-8"?>%s', "\n"));
  741. fwrite($file, sprintf('<checkstyle version="1.3.0">%s', "\n"));
  742. foreach ($undocumented as $fileName => $reports) {
  743. fwrite($file, sprintf('%s<file name="%s">%s', "\t", $fileName, "\n"));
  744. // Sort by line
  745. usort($reports, function($one, $two) {
  746. return strnatcmp($one[1], $two[1]);
  747. });
  748. foreach ($reports as $report) {
  749. list($severity, $line, $message) = $report;
  750. $message = preg_replace('~\\s+~u', ' ', $message);
  751. fwrite($file, sprintf('%s<error severity="%s" line="%s" message="%s" source="ApiGen.Documentation.Documentation"/>%s', "\t\t", $severity, $line, htmlspecialchars($message), "\n"));
  752. }
  753. fwrite($file, sprintf('%s</file>%s', "\t", "\n"));
  754. }
  755. fwrite($file, sprintf('</checkstyle>%s', "\n"));
  756. fclose($file);
  757. $this->incrementProgressBar();
  758. return $this;
  759. }
  760. /**
  761. * Generates list of deprecated elements.
  762. *
  763. * @param \ApiGen\Template $template Template
  764. * @return \ApiGen\Generator
  765. * @throws \ApiGen\Exception If template is not set.
  766. */
  767. private function generateDeprecated(Template $template)
  768. {
  769. $this->prepareTemplate('deprecated');
  770. $deprecatedFilter = function($element) {
  771. return $element->isDeprecated();
  772. };
  773. $template->deprecatedMethods = array();
  774. $template->deprecatedConstants = array();
  775. $template->deprecatedProperties = array();
  776. foreach (array_reverse($this->getElementTypes()) as $type) {
  777. $template->{'deprecated' . ucfirst($type)} = array_filter(array_filter($this->$type, $this->getMainFilter()), $deprecatedFilter);
  778. if ('constants' === $type || 'functions' === $type) {
  779. continue;
  780. }
  781. foreach ($this->$type as $class) {
  782. if (!$class->isMain()) {
  783. continue;
  784. }
  785. if ($class->isDeprecated()) {
  786. continue;
  787. }
  788. $template->deprecatedMethods = array_merge($template->deprecatedMethods, array_values(array_filter($class->getOwnMethods(), $deprecatedFilter)));
  789. $template->deprecatedConstants = array_merge($template->deprecatedConstants, array_values(array_filter($class->getOwnConstants(), $deprecatedFilter)));
  790. $template->deprecatedProperties = array_merge($template->deprecatedProperties, array_values(array_filter($class->getOwnProperties(), $deprecatedFilter)));
  791. }
  792. }
  793. usort($template->deprecatedMethods, array($this, 'sortMethods'));
  794. usort($template->deprecatedConstants, array($this, 'sortConstants'));
  795. usort($template->deprecatedFunctions, array($this, 'sortFunctions'));
  796. usort($template->deprecatedProperties, array($this, 'sortProperties'));
  797. $template
  798. ->setFile($this->getTemplatePath('deprecated'))
  799. ->save($this->forceDir($this->getTemplateFileName('deprecated')));
  800. foreach ($this->getElementTypes() as $type) {
  801. unset($template->{'deprecated' . ucfirst($type)});
  802. }
  803. unset($template->deprecatedMethods);
  804. unset($template->deprecatedProperties);
  805. $this->incrementProgressBar();
  806. return $this;
  807. }
  808. /**
  809. * Generates list of tasks.
  810. *
  811. * @param \ApiGen\Template $template Template
  812. * @return \ApiGen\Generator
  813. * @throws \ApiGen\Exception If template is not set.
  814. */
  815. private function generateTodo(Template $template)
  816. {
  817. $this->prepareTemplate('todo');
  818. $todoFilter = function($element) {
  819. return $element->hasAnnotation('todo');
  820. };
  821. $template->todoMethods = array();
  822. $template->todoConstants = array();
  823. $template->todoProperties = array();
  824. foreach (array_reverse($this->getElementTypes()) as $type) {
  825. $template->{'todo' . ucfirst($type)} = array_filter(array_filter($this->$type, $this->getMainFilter()), $todoFilter);
  826. if ('constants' === $type || 'functions' === $type) {
  827. continue;
  828. }
  829. foreach ($this->$type as $class) {
  830. if (!$class->isMain()) {
  831. continue;
  832. }
  833. $template->todoMethods = array_merge($template->todoMethods, array_values(array_filter($class->getOwnMethods(), $todoFilter)));
  834. $template->todoConstants = array_merge($template->todoConstants, array_values(array_filter($class->getOwnConstants(), $todoFilter)));
  835. $template->todoProperties = array_merge($template->todoProperties, array_values(array_filter($class->getOwnProperties(), $todoFilter)));
  836. }
  837. }
  838. usort($template->todoMethods, array($this, 'sortMethods'));
  839. usort($template->todoConstants, array($this, 'sortConstants'));
  840. usort($template->todoFunctions, array($this, 'sortFunctions'));
  841. usort($template->todoProperties, array($this, 'sortProperties'));
  842. $template
  843. ->setFile($this->getTemplatePath('todo'))
  844. ->save($this->forceDir($this->getTemplateFileName('todo')));
  845. foreach ($this->getElementTypes() as $type) {
  846. unset($template->{'todo' . ucfirst($type)});
  847. }
  848. unset($template->todoMethods);
  849. unset($template->todoProperties);
  850. $this->incrementProgressBar();
  851. return $this;
  852. }
  853. /**
  854. * Generates classes/interfaces/traits/exceptions tree.
  855. *
  856. * @param \ApiGen\Template $template Template
  857. * @return \ApiGen\Generator
  858. * @throws \ApiGen\Exception If template is not set.
  859. */
  860. private function generateTree(Template $template)
  861. {
  862. $this->prepareTemplate('tree');
  863. $classTree = array();
  864. $interfaceTree = array();
  865. $traitTree = array();
  866. $exceptionTree = array();
  867. $processed = array();
  868. foreach ($this->parsedClasses as $className => $reflection) {
  869. if (!$reflection->isMain() || !$reflection->isDocumented() || isset($processed[$className])) {
  870. continue;
  871. }
  872. if (null === $reflection->getParentClassName()) {
  873. // No parent classes
  874. if ($reflection->isInterface()) {
  875. $t = &$interfaceTree;
  876. } elseif ($reflection->isTrait()) {
  877. $t = &$traitTree;
  878. } elseif ($reflection->isException()) {
  879. $t = &$exceptionTree;
  880. } else {
  881. $t = &$classTree;
  882. }
  883. } else {
  884. foreach (array_values(array_reverse($reflection->getParentClasses())) as $level => $parent) {
  885. if (0 === $level) {
  886. // The topmost parent decides about the reflection type
  887. if ($parent->isInterface()) {
  888. $t = &$interfaceTree;
  889. } elseif ($parent->isTrait()) {
  890. $t = &$traitTree;
  891. } elseif ($parent->isException()) {
  892. $t = &$exceptionTree;
  893. } else {
  894. $t = &$classTree;
  895. }
  896. }
  897. $parentName = $parent->getName();
  898. if (!isset($t[$parentName])) {
  899. $t[$parentName] = array();
  900. $processed[$parentName] = true;
  901. ksort($t, SORT_STRING);
  902. }
  903. $t = &$t[$parentName];
  904. }
  905. }
  906. $t[$className] = array();
  907. ksort($t, SORT_STRING);
  908. $processed[$className] = true;
  909. unset($t);
  910. }
  911. $template->classTree = new Tree($classTree, $this->parsedClasses);
  912. $template->interfaceTree = new Tree($interfaceTree, $this->parsedClasses);
  913. $template->traitTree = new Tree($traitTree, $this->parsedClasses);
  914. $template->exceptionTree = new Tree($exceptionTree, $this->parsedClasses);
  915. $template
  916. ->setFile($this->getTemplatePath('tree'))
  917. ->save($this->forceDir($this->getTemplateFileName('tree')));
  918. unset($template->classTree);
  919. unset($template->interfaceTree);
  920. unset($template->traitTree);
  921. unset($template->exceptionTree);
  922. $this->incrementProgressBar();
  923. return $this;
  924. }
  925. /**
  926. * Generates packages summary.
  927. *
  928. * @param \ApiGen\Template $template Template
  929. * @return \ApiGen\Generator
  930. * @throws \ApiGen\Exception If template is not set.
  931. */
  932. private function generatePackages(Template $template)
  933. {
  934. if (empty($this->packages)) {
  935. return $this;
  936. }
  937. $this->prepareTemplate('package');
  938. foreach ($this->packages as $packageName => $package) {
  939. $template->package = $packageName;
  940. $template->subpackages = array_filter($template->packages, function($subpackageName) use ($packageName) {
  941. return 0 === strpos($subpackageName, $packageName . '\\');
  942. });
  943. $template->classes = $package['classes'];
  944. $template->interfaces = $package['interfaces'];
  945. $template->traits = $package['traits'];
  946. $template->exceptions = $package['exceptions'];
  947. $template->constants = $package['constants'];
  948. $template->functions = $package['functions'];
  949. $template
  950. ->setFile($this->getTemplatePath('package'))
  951. ->save($this->config->destination . '/' . $template->getPackageUrl($packageName));
  952. $this->incrementProgressBar();
  953. }
  954. unset($template->subpackages);
  955. return $this;
  956. }
  957. /**
  958. * Generates namespaces summary.
  959. *
  960. * @param \ApiGen\Template $template Template
  961. * @return \ApiGen\Generator
  962. * @throws \ApiGen\Exception If template is not set.
  963. */
  964. private function generateNamespaces(Template $template)
  965. {
  966. if (empty($this->namespaces)) {
  967. return $this;
  968. }
  969. $this->prepareTemplate('namespace');
  970. foreach ($this->namespaces as $namespaceName => $namespace) {
  971. $template->namespace = $namespaceName;
  972. $template->subnamespaces = array_filter($template->namespaces, function($subnamespaceName) use ($namespaceName) {
  973. return (bool) preg_match('~^' . preg_quote($namespaceName) . '\\\\[^\\\\]+$~', $subnamespaceName);
  974. });
  975. $template->classes = $namespace['classes'];
  976. $template->interfaces = $namespace['interfaces'];
  977. $template->traits = $namespace['traits'];
  978. $template->exceptions = $namespace['exceptions'];
  979. $template->constants = $namespace['constants'];
  980. $template->functions = $namespace['functions'];
  981. $template
  982. ->setFile($this->getTemplatePath('namespace'))
  983. ->save($this->config->destination . '/' . $template->getNamespaceUrl($namespaceName));
  984. $this->incrementProgressBar();
  985. }
  986. unset($template->subnamespaces);
  987. return $this;
  988. }
  989. /**
  990. * Generate classes, interfaces, traits, exceptions, constants and functions files.
  991. *
  992. * @param Template $template Template
  993. * @return \ApiGen\Generator
  994. * @throws \ApiGen\Exception If template is not set.
  995. */
  996. private function generateElements(Template $template)
  997. {
  998. if (!empty($this->classes) || !empty($this->interfaces) || !empty($this->traits) || !empty($this->exceptions)) {
  999. $this->prepareTemplate('class');
  1000. }
  1001. if (!empty($this->constants)) {
  1002. $this->prepareTemplate('constant');
  1003. }
  1004. if (!empty($this->functions)) {
  1005. $this->prepareTemplate('function');
  1006. }
  1007. if ($this->config->sourceCode) {
  1008. $this->prepareTemplate('source');
  1009. $fshl = new FSHL\Highlighter(new FSHL\Output\Html(), FSHL\Highlighter::OPTION_TAB_INDENT | FSHL\Highlighter::OPTION_LINE_COUNTER);
  1010. $fshl->setLexer(new FSHL\Lexer\Php());
  1011. }
  1012. $template->package = null;
  1013. $template->namespace = null;
  1014. $template->classes = $this->classes;
  1015. $template->interfaces = $this->interfaces;
  1016. $template->traits = $this->traits;
  1017. $template->exceptions = $this->exceptions;
  1018. $template->constants = $this->constants;
  1019. $template->functions = $this->functions;
  1020. foreach ($this->getElementTypes() as $type) {
  1021. foreach ($this->$type as $element) {
  1022. if (!empty($this->packages)) {
  1023. $template->package = $packageName = $element->getPseudoPackageName();
  1024. $template->classes = $this->packages[$packageName]['classes'];
  1025. $template->interfaces = $this->packages[$packageName]['interfaces'];
  1026. $template->traits = $this->packages[$packageName]['traits'];
  1027. $template->exceptions = $this->packages[$packageName]['exceptions'];
  1028. $template->constants = $this->packages[$packageName]['constants'];
  1029. $template->functions = $this->packages[$packageName]['functions'];
  1030. } elseif (!empty($this->namespaces)) {
  1031. $template->namespace = $namespaceName = $element->getPseudoNamespaceName();
  1032. $template->classes = $this->namespaces[$namespaceName]['classes'];
  1033. $template->interfaces = $this->namespaces[$namespaceName]['interfaces'];
  1034. $template->traits = $this->namespaces[$namespaceName]['traits'];
  1035. $template->exceptions = $this->namespaces[$namespaceName]['exceptions'];
  1036. $template->constants = $this->namespaces[$namespaceName]['constants'];
  1037. $template->functions = $this->namespaces[$namespaceName]['functions'];
  1038. }
  1039. $template->fileName = null;
  1040. if ($element->isTokenized()) {
  1041. $template->fileName = $this->getRelativePath($element);
  1042. }
  1043. $template->class = null;
  1044. $template->constant = null;
  1045. $template->function = null;
  1046. if ($element instanceof ReflectionClass) {
  1047. // Class
  1048. $template->tree = array_merge(array_reverse($element->getParentClasses()), array($element));
  1049. $template->directSubClasses = $element->getDirectSubClasses();
  1050. uksort($template->directSubClasses, 'strcasecmp');
  1051. $template->indirectSubClasses = $element->getIndirectSubClasses();
  1052. uksort($template->indirectSubClasses, 'strcasecmp');
  1053. $template->directImplementers = $element->getDirectImplementers();
  1054. uksort($template->directImplementers, 'strcasecmp');
  1055. $template->indirectImplementers = $element->getIndirectImplementers();
  1056. uksort($template->indirectImplementers, 'strcasecmp');
  1057. $template->directUsers = $element->getDirectUsers();
  1058. uksort($template->directUsers, 'strcasecmp');
  1059. $template->indirectUsers = $element->getIndirectUsers();
  1060. uksort($template->indirectUsers, 'strcasecmp');
  1061. $template->ownMethods = $element->getOwnMethods();
  1062. $template->ownConstants = $element->getOwnConstants();
  1063. $template->ownProperties = $element->getOwnProperties();
  1064. $template->class = $element;
  1065. $template
  1066. ->setFile($this->getTemplatePath('class'))
  1067. ->save($this->config->destination . '/' . $template->getClassUrl($element));
  1068. } elseif ($element instanceof ReflectionConstant) {
  1069. // Constant
  1070. $template->constant = $element;
  1071. $template
  1072. ->setFile($this->getTemplatePath('constant'))
  1073. ->save($this->config->destination . '/' . $template->getConstantUrl($element));
  1074. } elseif ($element instanceof ReflectionFunction) {
  1075. // Function
  1076. $template->function = $element;
  1077. $template
  1078. ->setFile($this->getTemplatePath('function'))
  1079. ->save($this->config->destination . '/' . $template->getFunctionUrl($element));
  1080. }
  1081. $this->incrementProgressBar();
  1082. // Generate source codes
  1083. if ($this->config->sourceCode && $element->isTokenized()) {
  1084. $template->source = $fshl->highlight(file_get_contents($element->getFileName()));
  1085. $template
  1086. ->setFile($this->getTemplatePath('source'))
  1087. ->save($this->config->destination . '/' . $template->getSourceUrl($element, false));
  1088. $this->incrementProgressBar();
  1089. }
  1090. }
  1091. }
  1092. return $this;
  1093. }
  1094. /**
  1095. * Prints message if printing is enabled.
  1096. *
  1097. * @param string $message Output message
  1098. */
  1099. public function output($message)
  1100. {
  1101. if (!$this->config->quiet) {
  1102. echo $this->colorize($message);
  1103. }
  1104. }
  1105. /**
  1106. * Colorizes message or removes placeholders if OS doesn't support colors.
  1107. *
  1108. * @param string $message
  1109. * @return string
  1110. */
  1111. public function colorize($message)
  1112. {
  1113. static $placeholders = array(
  1114. '@header@' => "\x1b[1;34m",
  1115. '@count@' => "\x1b[1;34m",
  1116. '@option@' => "\x1b[0;36m",
  1117. '@value@' => "\x1b[0;32m",
  1118. '@error@' => "\x1b[0;31m",
  1119. '@c' => "\x1b[0m"
  1120. );
  1121. if (!$this->config->colors) {
  1122. $placeholders = array_fill_keys(array_keys($placeholders), '');
  1123. }
  1124. return strtr($message, $placeholders);
  1125. }
  1126. /**
  1127. * Returns header.
  1128. *
  1129. * @return string
  1130. */
  1131. public function getHeader()
  1132. {
  1133. $name = sprintf('%s %s', self::NAME, self::VERSION);
  1134. return sprintf("@header@%s@c\n%s\n", $name, str_repeat('-', strlen($name)));
  1135. }
  1136. /**
  1137. * Prepares the progressbar.
  1138. *
  1139. * @param integer $maximum Maximum progressbar value
  1140. */
  1141. private function prepareProgressBar($maximum = 1)
  1142. {
  1143. if (!$this->config->progressbar) {
  1144. return;
  1145. }
  1146. $this->progressbar['current'] = 0;
  1147. $this->progressbar['maximum'] = $maximum;
  1148. }
  1149. /**
  1150. * Increments the progressbar by one.
  1151. *
  1152. * @param integer $increment Progressbar increment
  1153. */
  1154. private function incrementProgressBar($increment = 1)
  1155. {
  1156. if (!$this->config->progressbar) {
  1157. return;
  1158. }
  1159. echo str_repeat(chr(0x08), $this->progressbar['width']);
  1160. $this->progressbar['current'] += $increment;
  1161. $percent = $this->progressbar['current'] / $this->progressbar['maximum'];
  1162. $progress = str_pad(str_pad('>', round($percent * $this->progressbar['bar']), '=', STR_PAD_LEFT), $this->progressbar['bar'], ' ', STR_PAD_RIGHT);
  1163. echo sprintf($this->progressbar['skeleton'], $progress, $percent * 100);
  1164. if ($this->progressbar['current'] === $this->progressbar['maximum']) {
  1165. echo "\n";
  1166. }
  1167. }
  1168. /**
  1169. * Checks if sitemap.xml is enabled.
  1170. *
  1171. * @return boolean
  1172. */
  1173. private function isSitemapEnabled()
  1174. {
  1175. return !empty($this->config->baseUrl) && $this->templateExists('sitemap', 'optional');
  1176. }
  1177. /**
  1178. * Checks if opensearch.xml is enabled.
  1179. *
  1180. * @return boolean
  1181. */
  1182. private function isOpensearchEnabled()
  1183. {
  1184. return !empty($this->config->googleCseId) && !empty($this->config->baseUrl) && $this->templateExists('opensearch', 'optional');
  1185. }
  1186. /**
  1187. * Checks if robots.txt is enabled.
  1188. *
  1189. * @return boolean
  1190. */
  1191. private function isRobotsEnabled()
  1192. {
  1193. return !empty($this->config->baseUrl) && $this->templateExists('robots', 'optional');
  1194. }
  1195. /**
  1196. * Sorts methods by FQN.
  1197. *
  1198. * @param \ApiGen\ReflectionMethod $one
  1199. * @param \ApiGen\ReflectionMethod $two
  1200. * @return integer
  1201. */
  1202. private function sortMethods(ReflectionMethod $one, ReflectionMethod $two)
  1203. {
  1204. return strcasecmp($one->getDeclaringClassName() . '::' . $one->getName(), $two->getDeclaringClassName() . '::' . $two->getName());
  1205. }
  1206. /**
  1207. * Sorts constants by FQN.
  1208. *
  1209. * @param \ApiGen\ReflectionConstant $one
  1210. * @param \ApiGen\ReflectionConstant $two
  1211. * @return integer
  1212. */
  1213. private function sortConstants(ReflectionConstant $one, ReflectionConstant $two)
  1214. {
  1215. return strcasecmp(($one->getDeclaringClassName() ?: $one->getNamespaceName()) . '\\' . $one->getName(), ($two->getDeclaringClassName() ?: $two->getNamespaceName()) . '\\' . $two->getName());
  1216. }
  1217. /**
  1218. * Sorts functions by FQN.
  1219. *
  1220. * @param \ApiGen\ReflectionFunction $one
  1221. * @param \ApiGen\ReflectionFunction $two
  1222. * @return integer
  1223. */
  1224. private function sortFunctions(ReflectionFunction $one, ReflectionFunction $two)
  1225. {
  1226. return strcasecmp($one->getNamespaceName() . '\\' . $one->getName(), $two->getNamespaceName() . '\\' . $two->getName());
  1227. }
  1228. /**
  1229. * Sorts functions by FQN.
  1230. *
  1231. * @param \ApiGen\ReflectionProperty $one
  1232. * @param \ApiGen\ReflectionProperty $two
  1233. * @return integer
  1234. */
  1235. private function sortProperties(ReflectionProperty $one, ReflectionProperty $two)
  1236. {
  1237. return strcasecmp($one->getDeclaringClassName() . '::' . $one->getName(), $two->getDeclaringClassName() . '::' . $two->getName());
  1238. }
  1239. /**
  1240. * Returns list of element types.
  1241. *
  1242. * @return array
  1243. */
  1244. private function getElementTypes()
  1245. {
  1246. static $types = array('classes', 'interfaces', 'traits', 'exceptions', 'constants', 'functions');
  1247. return $types;
  1248. }
  1249. /**
  1250. * Returns main filter.
  1251. *
  1252. * @return \Closure
  1253. */
  1254. private function getMainFilter()
  1255. {
  1256. return function($element) {
  1257. return $element->isMain();
  1258. };
  1259. }
  1260. /**
  1261. * Returns element relative path to the source directory.
  1262. *
  1263. * @param \ApiGen\ReflectionElement $element
  1264. * @return string
  1265. * @throws \ApiGen\Exception If relative path could not be determined.
  1266. */
  1267. private function getRelativePath(ReflectionElement $element)
  1268. {
  1269. $fileName = $element->getFileName();
  1270. if (isset($this->symlinks[$fileName])) {
  1271. $fileName = $this->symlinks[$fileName];
  1272. }
  1273. foreach ($this->config->source as $source) {
  1274. if (0 === strpos($fileName, $source)) {
  1275. return is_dir($source) ? str_replace('\\', '/', substr($fileName, strlen($source) + 1)) : basename($fileName);
  1276. }
  1277. }
  1278. throw new Exception(sprintf('Could not determine element %s relative path', $element->getName()));
  1279. }
  1280. /**
  1281. * Returns template directory.
  1282. *
  1283. * @return string
  1284. */
  1285. private function getTemplateDir()
  1286. {
  1287. return dirname($this->config->templateConfig);
  1288. }
  1289. /**
  1290. * Returns template path.
  1291. *
  1292. * @param string $name Template name
  1293. * @param string $type Template type
  1294. * @return string
  1295. */
  1296. private function getTemplatePath($name, $type = 'main')
  1297. {
  1298. return $this->getTemplateDir() . '/' . $this->config->template['templates'][$type][$name]['template'];
  1299. }
  1300. /**
  1301. * Returns template filename.
  1302. *
  1303. * @param string $name Template name
  1304. * @param string $type Template type
  1305. * @return string
  1306. */
  1307. private function getTemplateFileName($name, $type = 'main')
  1308. {
  1309. return $this->config->destination . '/' . $this->config->template['templates'][$type][$name]['filename'];
  1310. }
  1311. /**
  1312. * Checks if template exists.
  1313. *
  1314. * @param string $name Template name
  1315. * @param string $type Template type
  1316. * @return string
  1317. */
  1318. private function templateExists($name, $type = 'main')
  1319. {
  1320. return isset($this->config->template['templates'][$type][$name]);
  1321. }
  1322. /**
  1323. * Checks if template exists and creates dir.
  1324. *
  1325. * @param mixed $name
  1326. * @throws \ApiGen\Exception If template is not set.
  1327. */
  1328. private function prepareTemplate($name)
  1329. {
  1330. if (!$this->templateExists($name)) {
  1331. throw new Exception(sprintf('Template for %s is not set.', $name));
  1332. }
  1333. $this->forceDir($this->getTemplateFileName($name));
  1334. return $this;
  1335. }
  1336. /**
  1337. * Ensures a directory is created.
  1338. *
  1339. * @param string $path Directory path
  1340. * @return string
  1341. */
  1342. private function forceDir($path)
  1343. {
  1344. @mkdir(dirname($path), 0755, true);
  1345. return $path;
  1346. }
  1347. /**
  1348. * Deletes a directory.
  1349. *
  1350. * @param string $path Directory path
  1351. * @return boolean
  1352. */
  1353. private function deleteDir($path)
  1354. {
  1355. foreach (Nette\Utils\Finder::find('*')->from($path)->childFirst() as $item) {
  1356. if ($item->isDir()) {
  1357. if (!@rmdir($item)) {
  1358. return false;
  1359. }
  1360. } elseif ($item->isFile()) {
  1361. if (!@unlink($item)) {
  1362. return false;
  1363. }
  1364. }
  1365. }
  1366. if (!@rmdir($path)) {
  1367. return false;
  1368. }
  1369. return true;
  1370. }
  1371. }