PageRenderTime 78ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/ApiGen/Generator.php

https://github.com/cakephp/cakephp-api-docs
PHP | 1719 lines | 1366 code | 97 blank | 256 comment | 47 complexity | 8da8bf127545fed59ced90ea912f73f0 MD5 | raw file
Possible License(s): BSD-3-Clause, JSON

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * ApiGen 3.0dev - API documentation generator for PHP 5.3+
  4. *
  5. * Copyright (c) 2010-2011 David Grudl (http://davidgrudl.com)
  6. * Copyright (c) 2011-2012 Jaroslav Hanslík (https://github.com/kukulich)
  7. * Copyright (c) 2011-2012 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 ApiGen\Config\Configuration;
  14. use InvalidArgumentException;
  15. use Nette;
  16. use RuntimeException;
  17. use TokenReflection\Broker;
  18. /**
  19. * Generates a HTML API documentation.
  20. */
  21. class Generator extends Object implements IGenerator
  22. {
  23. /**
  24. * Configuration.
  25. *
  26. * @var \ApiGen\Config
  27. */
  28. private $config;
  29. /**
  30. * Charset convertor.
  31. *
  32. * @var \ApiGen\CharsetConvertor
  33. */
  34. private $charsetConvertor;
  35. /**
  36. * Source code highlighter.
  37. *
  38. * @var \ApiGen\ISourceCodeHighlighter
  39. */
  40. private $highlighter;
  41. /**
  42. * List of parsed classes.
  43. *
  44. * @var \ArrayObject
  45. */
  46. private $parsedClasses = null;
  47. /**
  48. * List of parsed constants.
  49. *
  50. * @var \ArrayObject
  51. */
  52. private $parsedConstants = null;
  53. /**
  54. * List of parsed functions.
  55. *
  56. * @var \ArrayObject
  57. */
  58. private $parsedFunctions = null;
  59. /**
  60. * List of packages.
  61. *
  62. * @var array
  63. */
  64. private $packages = array();
  65. /**
  66. * List of namespaces.
  67. *
  68. * @var array
  69. */
  70. private $namespaces = array();
  71. /**
  72. * List of classes.
  73. *
  74. * @var array
  75. */
  76. private $classes = array();
  77. /**
  78. * List of interfaces.
  79. *
  80. * @var array
  81. */
  82. private $interfaces = array();
  83. /**
  84. * List of traits.
  85. *
  86. * @var array
  87. */
  88. private $traits = array();
  89. /**
  90. * List of exceptions.
  91. *
  92. * @var array
  93. */
  94. private $exceptions = array();
  95. /**
  96. * List of constants.
  97. *
  98. * @var array
  99. */
  100. private $constants = array();
  101. /**
  102. * List of functions.
  103. *
  104. * @var array
  105. */
  106. private $functions = array();
  107. /**
  108. * List of symlinks.
  109. *
  110. * @var array
  111. */
  112. private $symlinks = array();
  113. /**
  114. * Sets dependencies.
  115. *
  116. * @param \ApiGen\Config\Configuration $config
  117. * @param \ApiGen\CharsetConvertor $charsetConvertor
  118. * @param \ApiGen\ISourceCodeHighlighter $highlighter
  119. */
  120. public function __construct(Configuration $config, CharsetConvertor $charsetConvertor, ISourceCodeHighlighter $highlighter)
  121. {
  122. $this->config = $config;
  123. $this->charsetConvertor = $charsetConvertor;
  124. $this->highlighter = $highlighter;
  125. $this->parsedClasses = new \ArrayObject();
  126. $this->parsedConstants = new \ArrayObject();
  127. $this->parsedFunctions = new \ArrayObject();
  128. }
  129. /**
  130. * Scans and parses PHP files.
  131. *
  132. * @return array
  133. * @throws \RuntimeException If no PHP files have been found.
  134. */
  135. public function parse()
  136. {
  137. $files = array();
  138. $flags = \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO | \RecursiveDirectoryIterator::SKIP_DOTS;
  139. if (defined('\\RecursiveDirectoryIterator::FOLLOW_SYMLINKS')) {
  140. // Available from PHP 5.3.1
  141. $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
  142. }
  143. foreach ($this->config->source as $source) {
  144. $entries = array();
  145. if (is_dir($source)) {
  146. foreach (new \RecursiveIteratorIterator(new SourceFilesFilterIterator(new \RecursiveDirectoryIterator($source, $flags), $this->config->exclude)) as $entry) {
  147. if (!$entry->isFile()) {
  148. continue;
  149. }
  150. $entries[] = $entry;
  151. }
  152. } elseif ($this->isPhar($source)) {
  153. if (!extension_loaded('phar')) {
  154. throw new RuntimeException('Phar extension is not loaded');
  155. }
  156. foreach (new \RecursiveIteratorIterator(new \Phar($source, $flags)) as $entry) {
  157. if (!$entry->isFile()) {
  158. continue;
  159. }
  160. $entries[] = $entry;
  161. }
  162. } else {
  163. $entries[] = new \SplFileInfo($source);
  164. }
  165. $regexp = '~\\.' . implode('|', $this->config->extensions->toArray()) . '$~i';
  166. foreach ($entries as $entry) {
  167. if (!preg_match($regexp, $entry->getFilename())) {
  168. continue;
  169. }
  170. $pathName = $this->normalizePath($entry->getPathName());
  171. $files[$pathName] = $entry->getSize();
  172. if (false !== $entry->getRealPath() && $pathName !== $entry->getRealPath()) {
  173. $this->symlinks[$entry->getRealPath()] = $pathName;
  174. }
  175. }
  176. }
  177. if (empty($files)) {
  178. throw new RuntimeException('No PHP files found');
  179. }
  180. $this->fireEvent('parseStart', array_sum($files));
  181. $broker = new Broker(new Backend($this, !empty($this->config->report)), Broker::OPTION_DEFAULT & ~(Broker::OPTION_PARSE_FUNCTION_BODY | Broker::OPTION_SAVE_TOKEN_STREAM));
  182. $errors = array();
  183. foreach ($files as $filePath => $size) {
  184. $content = $this->charsetConvertor->convertFile($filePath);
  185. $content = preg_replace('~\{\{\{(.+?)\}\}\}~s', '<code>\\1</code>', $content);
  186. try {
  187. $broker->processString($content, $filePath);
  188. } catch (\Exception $e) {
  189. $errors[] = $e;
  190. }
  191. $this->fireEvent('parseProgress', $size);
  192. }
  193. // Classes
  194. $this->parsedClasses->exchangeArray($broker->getClasses(Backend::TOKENIZED_CLASSES | Backend::INTERNAL_CLASSES | Backend::NONEXISTENT_CLASSES));
  195. $this->parsedClasses->uksort('strcasecmp');
  196. // Constants
  197. $this->parsedConstants->exchangeArray($broker->getConstants());
  198. $this->parsedConstants->uksort('strcasecmp');
  199. // Functions
  200. $this->parsedFunctions->exchangeArray($broker->getFunctions());
  201. $this->parsedFunctions->uksort('strcasecmp');
  202. $documentedCounter = function($count, $element) {
  203. return $count += (int) $element->isDocumented();
  204. };
  205. return (object) array(
  206. 'classes' => count($broker->getClasses(Backend::TOKENIZED_CLASSES)),
  207. 'constants' => count($this->parsedConstants),
  208. 'functions' => count($this->parsedFunctions),
  209. 'internalClasses' => count($broker->getClasses(Backend::INTERNAL_CLASSES)),
  210. 'documentedClasses' => array_reduce($broker->getClasses(Backend::TOKENIZED_CLASSES), $documentedCounter),
  211. 'documentedConstants' => array_reduce($this->parsedConstants->getArrayCopy(), $documentedCounter),
  212. 'documentedFunctions' => array_reduce($this->parsedFunctions->getArrayCopy(), $documentedCounter),
  213. 'documentedInternalClasses' => array_reduce($broker->getClasses(Backend::INTERNAL_CLASSES), $documentedCounter),
  214. 'errors' => $errors
  215. );
  216. }
  217. /**
  218. * Returns configuration.
  219. *
  220. * @return mixed
  221. */
  222. public function getConfig()
  223. {
  224. return $this->config;
  225. }
  226. /**
  227. * Returns parsed class list.
  228. *
  229. * @return \ArrayObject
  230. */
  231. public function getParsedClasses()
  232. {
  233. return $this->parsedClasses;
  234. }
  235. /**
  236. * Returns parsed constant list.
  237. *
  238. * @return \ArrayObject
  239. */
  240. public function getParsedConstants()
  241. {
  242. return $this->parsedConstants;
  243. }
  244. /**
  245. * Returns parsed function list.
  246. *
  247. * @return \ArrayObject
  248. */
  249. public function getParsedFunctions()
  250. {
  251. return $this->parsedFunctions;
  252. }
  253. /**
  254. * Wipes out the destination directory.
  255. *
  256. * @return boolean
  257. */
  258. public function wipeOutDestination()
  259. {
  260. foreach ($this->getGeneratedFiles() as $path) {
  261. if (is_file($path) && !@unlink($path)) {
  262. return false;
  263. }
  264. }
  265. $archive = $this->getArchivePath();
  266. if (is_file($archive) && !@unlink($archive)) {
  267. return false;
  268. }
  269. return true;
  270. }
  271. /**
  272. * Generates API documentation.
  273. *
  274. * @throws \RuntimeException If destination directory is not writable.
  275. */
  276. public function generate()
  277. {
  278. @mkdir($this->config->destination, 0755, true);
  279. if (!is_dir($this->config->destination) || !is_writable($this->config->destination)) {
  280. throw new RuntimeException(sprintf('Directory "%s" isn\'t writable', $this->config->destination));
  281. }
  282. // Copy resources
  283. foreach ($this->config->template->resources as $resourceSource => $resourceDestination) {
  284. // File
  285. $resourcePath = $this->getTemplateDir() . DIRECTORY_SEPARATOR . $resourceSource;
  286. if (is_file($resourcePath)) {
  287. copy($resourcePath, $this->forceDir($this->config->destination . DIRECTORY_SEPARATOR . $resourceDestination));
  288. continue;
  289. }
  290. // Dir
  291. $iterator = Nette\Utils\Finder::findFiles('*')->from($resourcePath)->getIterator();
  292. foreach ($iterator as $item) {
  293. copy($item->getPathName(), $this->forceDir($this->config->destination . DIRECTORY_SEPARATOR . $resourceDestination . DIRECTORY_SEPARATOR . $iterator->getSubPathName()));
  294. }
  295. }
  296. // Categorize by packages and namespaces
  297. $this->categorize();
  298. // Prepare progressbar & stuff
  299. $steps = count($this->packages)
  300. + count($this->namespaces)
  301. + count($this->classes)
  302. + count($this->interfaces)
  303. + count($this->traits)
  304. + count($this->exceptions)
  305. + count($this->constants)
  306. + count($this->functions)
  307. + count($this->config->template->templates->common)
  308. + (int) !empty($this->config->report)
  309. + (int) $this->config->tree
  310. + (int) $this->config->deprecated
  311. + (int) $this->config->todo
  312. + (int) $this->config->download
  313. + (int) $this->isSitemapEnabled()
  314. + (int) $this->isOpensearchEnabled()
  315. + (int) $this->isRobotsEnabled();
  316. if ($this->config->sourceCode) {
  317. $tokenizedFilter = function(Reflection\ReflectionClass $class) {
  318. return $class->isTokenized();
  319. };
  320. $steps += count(array_filter($this->classes, $tokenizedFilter))
  321. + count(array_filter($this->interfaces, $tokenizedFilter))
  322. + count(array_filter($this->traits, $tokenizedFilter))
  323. + count(array_filter($this->exceptions, $tokenizedFilter))
  324. + count($this->constants)
  325. + count($this->functions);
  326. unset($tokenizedFilter);
  327. }
  328. $this->fireEvent('generateStart', $steps);
  329. // Prepare template
  330. $tmp = $this->config->destination . DIRECTORY_SEPARATOR . uniqid();
  331. $this->deleteDir($tmp);
  332. @mkdir($tmp, 0755, true);
  333. $template = new Template($this, $this->highlighter);
  334. $template->setCacheStorage(new Nette\Caching\Storages\PhpFileStorage($tmp));
  335. $template->generator = Environment::getApplicationName();
  336. $template->version = Environment::getApplicationVersion();
  337. $template->config = $this->config;
  338. $this->registerCustomTemplateMacros($template);
  339. // Common files
  340. $this->generateCommon($template);
  341. // Optional files
  342. $this->generateOptional($template);
  343. // List of poorly documented elements
  344. if (!empty($this->config->report)) {
  345. $this->generateReport();
  346. }
  347. // List of deprecated elements
  348. if ($this->config->deprecated) {
  349. $this->generateDeprecated($template);
  350. }
  351. // List of tasks
  352. if ($this->config->todo) {
  353. $this->generateTodo($template);
  354. }
  355. // Classes/interfaces/traits/exceptions tree
  356. if ($this->config->tree) {
  357. $this->generateTree($template);
  358. }
  359. // Generate packages summary
  360. $this->generatePackages($template);
  361. // Generate namespaces summary
  362. $this->generateNamespaces($template);
  363. // Generate classes, interfaces, traits, exceptions, constants and functions files
  364. $this->generateElements($template);
  365. // Generate ZIP archive
  366. if ($this->config->download) {
  367. $this->generateArchive();
  368. }
  369. // Delete temporary directory
  370. $this->deleteDir($tmp);
  371. }
  372. /**
  373. * Loads template-specific macro and helper libraries.
  374. *
  375. * @param \ApiGen\Template $template Template instance
  376. */
  377. private function registerCustomTemplateMacros(Template $template)
  378. {
  379. $latte = new Nette\Latte\Engine();
  380. if (!empty($this->config->template['options']['extensions'])) {
  381. $this->output("Loading custom template macro and helper libraries\n");
  382. $broker = new Broker(new Broker\Backend\Memory(), 0);
  383. $baseDir = dirname($this->config->template['config']);
  384. foreach ((array) $this->config->template['options']['extensions'] as $fileName) {
  385. $pathName = $baseDir . DIRECTORY_SEPARATOR . $fileName;
  386. if (is_file($pathName)) {
  387. try {
  388. $reflectionFile = $broker->processFile($pathName, true);
  389. foreach ($reflectionFile->getNamespaces() as $namespace) {
  390. foreach ($namespace->getClasses() as $class) {
  391. if ($class->isSubclassOf('ApiGen\\MacroSet')) {
  392. // Macro set
  393. include $pathName;
  394. call_user_func(array($class->getName(), 'install'), $latte->compiler);
  395. $this->output(sprintf(" %s (macro set)\n", $class->getName()));
  396. } elseif ($class->implementsInterface('ApiGen\\IHelperSet')) {
  397. // Helpers set
  398. include $pathName;
  399. $className = $class->getName();
  400. $template->registerHelperLoader(callback(new $className($template), 'loader'));
  401. $this->output(sprintf(" %s (helper set)\n", $class->getName()));
  402. }
  403. }
  404. }
  405. } catch (\Exception $e) {
  406. throw new \Exception(sprintf('Could not load macros and helpers from file "%s"', $pathName), 0, $e);
  407. }
  408. } else {
  409. throw new \Exception(sprintf('Helper file "%s" does not exist.', $pathName));
  410. }
  411. }
  412. }
  413. $template->registerFilter($latte);
  414. }
  415. /**
  416. * Categorizes by packages and namespaces.
  417. *
  418. * @return \ApiGen\Generator
  419. */
  420. private function categorize()
  421. {
  422. foreach (array('classes', 'constants', 'functions') as $type) {
  423. foreach ($this->{'parsed' . ucfirst($type)} as $elementName => $element) {
  424. if (!$element->isDocumented()) {
  425. continue;
  426. }
  427. $packageName = $element->getPseudoPackageName();
  428. $namespaceName = $element->getPseudoNamespaceName();
  429. if ($element instanceof Reflection\ReflectionConstant) {
  430. $this->constants[$elementName] = $element;
  431. $this->packages[$packageName]['constants'][$elementName] = $element;
  432. $this->namespaces[$namespaceName]['constants'][$element->getShortName()] = $element;
  433. } elseif ($element instanceof Reflection\ReflectionFunction) {
  434. $this->functions[$elementName] = $element;
  435. $this->packages[$packageName]['functions'][$elementName] = $element;
  436. $this->namespaces[$namespaceName]['functions'][$element->getShortName()] = $element;
  437. } elseif ($element->isInterface()) {
  438. $this->interfaces[$elementName] = $element;
  439. $this->packages[$packageName]['interfaces'][$elementName] = $element;
  440. $this->namespaces[$namespaceName]['interfaces'][$element->getShortName()] = $element;
  441. } elseif ($element->isTrait()) {
  442. $this->traits[$elementName] = $element;
  443. $this->packages[$packageName]['traits'][$elementName] = $element;
  444. $this->namespaces[$namespaceName]['traits'][$element->getShortName()] = $element;
  445. } elseif ($element->isException()) {
  446. $this->exceptions[$elementName] = $element;
  447. $this->packages[$packageName]['exceptions'][$elementName] = $element;
  448. $this->namespaces[$namespaceName]['exceptions'][$element->getShortName()] = $element;
  449. } else {
  450. $this->classes[$elementName] = $element;
  451. $this->packages[$packageName]['classes'][$elementName] = $element;
  452. $this->namespaces[$namespaceName]['classes'][$element->getShortName()] = $element;
  453. }
  454. }
  455. }
  456. // Select only packages or namespaces
  457. $userPackagesCount = count(array_diff(array_keys($this->packages), array('PHP', 'None')));
  458. $userNamespacesCount = count(array_diff(array_keys($this->namespaces), array('PHP', 'None')));
  459. $namespacesEnabled = ('auto' === $this->config->groups && ($userNamespacesCount > 0 || 0 === $userPackagesCount)) || 'namespaces' === $this->config->groups;
  460. $packagesEnabled = ('auto' === $this->config->groups && !$namespacesEnabled) || 'packages' === $this->config->groups;
  461. if ($namespacesEnabled) {
  462. $this->packages = array();
  463. $this->namespaces = $this->sortGroups($this->namespaces);
  464. } elseif ($packagesEnabled) {
  465. $this->namespaces = array();
  466. $this->packages = $this->sortGroups($this->packages);
  467. } else {
  468. $this->namespaces = array();
  469. $this->packages = array();
  470. }
  471. return $this;
  472. }
  473. /**
  474. * Sorts and filters groups.
  475. *
  476. * @param array $groups
  477. * @return array
  478. */
  479. private function sortGroups(array $groups)
  480. {
  481. // Don't generate only 'None' groups
  482. if (1 === count($groups) && isset($groups['None'])) {
  483. return array();
  484. }
  485. $emptyList = array('classes' => array(), 'interfaces' => array(), 'traits' => array(), 'exceptions' => array(), 'constants' => array(), 'functions' => array());
  486. $groupNames = array_keys($groups);
  487. $lowerGroupNames = array_flip(array_map(function($y) {
  488. return strtolower($y);
  489. }, $groupNames));
  490. foreach ($groupNames as $groupName) {
  491. // Add missing parent groups
  492. $parent = '';
  493. foreach (explode('\\', $groupName) as $part) {
  494. $parent = ltrim($parent . '\\' . $part, '\\');
  495. if (!isset($lowerGroupNames[strtolower($parent)])) {
  496. $groups[$parent] = $emptyList;
  497. }
  498. }
  499. // Add missing element types
  500. foreach ($this->getElementTypes() as $type) {
  501. if (!isset($groups[$groupName][$type])) {
  502. $groups[$groupName][$type] = array();
  503. }
  504. }
  505. }
  506. $main = $this->config->main;
  507. uksort($groups, function($one, $two) use ($main) {
  508. // \ as separator has to be first
  509. $one = str_replace('\\', ' ', $one);
  510. $two = str_replace('\\', ' ', $two);
  511. if ($main) {
  512. if (0 === strpos($one, $main) && 0 !== strpos($two, $main)) {
  513. return -1;
  514. } elseif (0 !== strpos($one, $main) && 0 === strpos($two, $main)) {
  515. return 1;
  516. }
  517. }
  518. return strcasecmp($one, $two);
  519. });
  520. return $groups;
  521. }
  522. /**
  523. * Generates common files.
  524. *
  525. * @param \ApiGen\Template $template Template
  526. * @return \ApiGen\Generator
  527. */
  528. private function generateCommon(Template $template)
  529. {
  530. $template->namespace = null;
  531. $template->namespaces = array_keys($this->namespaces);
  532. $template->package = null;
  533. $template->packages = array_keys($this->packages);
  534. $template->class = null;
  535. $template->classes = array_filter($this->classes, $this->getMainFilter());
  536. $template->interfaces = array_filter($this->interfaces, $this->getMainFilter());
  537. $template->traits = array_filter($this->traits, $this->getMainFilter());
  538. $template->exceptions = array_filter($this->exceptions, $this->getMainFilter());
  539. $template->constant = null;
  540. $template->constants = array_filter($this->constants, $this->getMainFilter());
  541. $template->function = null;
  542. $template->functions = array_filter($this->functions, $this->getMainFilter());
  543. $template->archive = basename($this->getArchivePath());
  544. // Elements for autocomplete
  545. $elements = array();
  546. $autocomplete = array_flip($this->config->autocomplete->toArray());
  547. foreach ($this->getElementTypes() as $type) {
  548. foreach ($this->$type as $element) {
  549. if ($element instanceof Reflection\ReflectionClass) {
  550. if (isset($autocomplete['classes'])) {
  551. $elements[] = array('c', $element->getPrettyName());
  552. }
  553. if (isset($autocomplete['methods'])) {
  554. foreach ($element->getOwnMethods() as $method) {
  555. $elements[] = array('m', $method->getPrettyName());
  556. }
  557. foreach ($element->getOwnMagicMethods() as $method) {
  558. $elements[] = array('mm', $method->getPrettyName());
  559. }
  560. }
  561. if (isset($autocomplete['properties'])) {
  562. foreach ($element->getOwnProperties() as $property) {
  563. $elements[] = array('p', $property->getPrettyName());
  564. }
  565. foreach ($element->getOwnMagicProperties() as $property) {
  566. $elements[] = array('mp', $property->getPrettyName());
  567. }
  568. }
  569. if (isset($autocomplete['classconstants'])) {
  570. foreach ($element->getOwnConstants() as $constant) {
  571. $elements[] = array('cc', $constant->getPrettyName());
  572. }
  573. }
  574. } elseif ($element instanceof Reflection\ReflectionConstant && isset($autocomplete['constants'])) {
  575. $elements[] = array('co', $element->getPrettyName());
  576. } elseif ($element instanceof Reflection\ReflectionFunction && isset($autocomplete['functions'])) {
  577. $elements[] = array('f', $element->getPrettyName());
  578. }
  579. }
  580. }
  581. usort($elements, function($one, $two) {
  582. return strcasecmp($one[1], $two[1]);
  583. });
  584. $template->elements = $elements;
  585. foreach ($this->config->template->templates->common as $source => $destination) {
  586. $template
  587. ->setFile($this->getTemplateDir() . DIRECTORY_SEPARATOR . $source)
  588. ->save($this->forceDir($this->config->destination . DIRECTORY_SEPARATOR . $destination));
  589. $this->fireEvent('generateProgress', 1);
  590. }
  591. unset($template->elements);
  592. return $this;
  593. }
  594. /**
  595. * Generates optional files.
  596. *
  597. * @param \ApiGen\Template $template Template
  598. * @return \ApiGen\Generator
  599. */
  600. private function generateOptional(Template $template)
  601. {
  602. if ($this->isSitemapEnabled()) {
  603. $template
  604. ->setFile($this->getTemplatePath('sitemap', 'optional'))
  605. ->save($this->forceDir($this->getTemplateFileName('sitemap', 'optional')));
  606. $this->fireEvent('generateProgress', 1);
  607. }
  608. if ($this->isOpensearchEnabled()) {
  609. $template
  610. ->setFile($this->getTemplatePath('opensearch', 'optional'))
  611. ->save($this->forceDir($this->getTemplateFileName('opensearch', 'optional')));
  612. $this->fireEvent('generateProgress', 1);
  613. }
  614. if ($this->isRobotsEnabled()) {
  615. $template
  616. ->setFile($this->getTemplatePath('robots', 'optional'))
  617. ->save($this->forceDir($this->getTemplateFileName('robots', 'optional')));
  618. $this->fireEvent('generateProgress', 1);
  619. }
  620. return $this;
  621. }
  622. /**
  623. * Generates list of poorly documented elements.
  624. *
  625. * @return \ApiGen\Generator
  626. */
  627. private function generateReport()
  628. {
  629. $elements = array();
  630. foreach ($this->getElementTypes() as $type) {
  631. $elements = array_merge($elements, $this->$type);
  632. }
  633. $report = new Checkstyle\Report($elements);
  634. $report
  635. ->addCheck(new Checkstyle\DescriptionCheck())
  636. ->addCheck(new Checkstyle\FunctionCheck())
  637. ->addCheck(new Checkstyle\DataTypeCheck());
  638. $report->make($this->config->report);
  639. $this->fireEvent('generateProgress', 1);
  640. return $this;
  641. }
  642. /**
  643. * Generates list of deprecated elements.
  644. *
  645. * @param \ApiGen\Template $template Template
  646. * @return \ApiGen\Generator
  647. * @throws \RuntimeException If template is not set.
  648. */
  649. private function generateDeprecated(Template $template)
  650. {
  651. $this->prepareTemplate('deprecated');
  652. $deprecatedFilter = function($element) {
  653. return $element->isDeprecated();
  654. };
  655. $template->deprecatedMethods = array();
  656. $template->deprecatedConstants = array();
  657. $template->deprecatedProperties = array();
  658. foreach (array_reverse($this->getElementTypes()) as $type) {
  659. $template->{'deprecated' . ucfirst($type)} = array_filter(array_filter($this->$type, $this->getMainFilter()), $deprecatedFilter);
  660. if ('constants' === $type || 'functions' === $type) {
  661. continue;
  662. }
  663. foreach ($this->$type as $class) {
  664. if (!$class->isMain()) {
  665. continue;
  666. }
  667. if ($class->isDeprecated()) {
  668. continue;
  669. }
  670. $template->deprecatedMethods = array_merge($template->deprecatedMethods, array_values(array_filter($class->getOwnMethods(), $deprecatedFilter)));
  671. $template->deprecatedConstants = array_merge($template->deprecatedConstants, array_values(array_filter($class->getOwnConstants(), $deprecatedFilter)));
  672. $template->deprecatedProperties = array_merge($template->deprecatedProperties, array_values(array_filter($class->getOwnProperties(), $deprecatedFilter)));
  673. }
  674. }
  675. usort($template->deprecatedMethods, array($this, 'sortMethods'));
  676. usort($template->deprecatedConstants, array($this, 'sortConstants'));
  677. usort($template->deprecatedFunctions, array($this, 'sortFunctions'));
  678. usort($template->deprecatedProperties, array($this, 'sortProperties'));
  679. $template
  680. ->setFile($this->getTemplatePath('deprecated'))
  681. ->save($this->forceDir($this->getTemplateFileName('deprecated')));
  682. foreach ($this->getElementTypes() as $type) {
  683. unset($template->{'deprecated' . ucfirst($type)});
  684. }
  685. unset($template->deprecatedMethods);
  686. unset($template->deprecatedProperties);
  687. $this->fireEvent('generateProgress', 1);
  688. return $this;
  689. }
  690. /**
  691. * Generates list of tasks.
  692. *
  693. * @param \ApiGen\Template $template Template
  694. * @return \ApiGen\Generator
  695. * @throws \RuntimeException If template is not set.
  696. */
  697. private function generateTodo(Template $template)
  698. {
  699. $this->prepareTemplate('todo');
  700. $todoFilter = function($element) {
  701. return $element->hasAnnotation('todo');
  702. };
  703. $template->todoMethods = array();
  704. $template->todoConstants = array();
  705. $template->todoProperties = array();
  706. foreach (array_reverse($this->getElementTypes()) as $type) {
  707. $template->{'todo' . ucfirst($type)} = array_filter(array_filter($this->$type, $this->getMainFilter()), $todoFilter);
  708. if ('constants' === $type || 'functions' === $type) {
  709. continue;
  710. }
  711. foreach ($this->$type as $class) {
  712. if (!$class->isMain()) {
  713. continue;
  714. }
  715. $template->todoMethods = array_merge($template->todoMethods, array_values(array_filter($class->getOwnMethods(), $todoFilter)));
  716. $template->todoConstants = array_merge($template->todoConstants, array_values(array_filter($class->getOwnConstants(), $todoFilter)));
  717. $template->todoProperties = array_merge($template->todoProperties, array_values(array_filter($class->getOwnProperties(), $todoFilter)));
  718. }
  719. }
  720. usort($template->todoMethods, array($this, 'sortMethods'));
  721. usort($template->todoConstants, array($this, 'sortConstants'));
  722. usort($template->todoFunctions, array($this, 'sortFunctions'));
  723. usort($template->todoProperties, array($this, 'sortProperties'));
  724. $template
  725. ->setFile($this->getTemplatePath('todo'))
  726. ->save($this->forceDir($this->getTemplateFileName('todo')));
  727. foreach ($this->getElementTypes() as $type) {
  728. unset($template->{'todo' . ucfirst($type)});
  729. }
  730. unset($template->todoMethods);
  731. unset($template->todoProperties);
  732. $this->fireEvent('generateProgress', 1);
  733. return $this;
  734. }
  735. /**
  736. * Generates classes/interfaces/traits/exceptions tree.
  737. *
  738. * @param \ApiGen\Template $template Template
  739. * @return \ApiGen\Generator
  740. * @throws \RuntimeException If template is not set.
  741. */
  742. private function generateTree(Template $template)
  743. {
  744. $this->prepareTemplate('tree');
  745. $classTree = array();
  746. $interfaceTree = array();
  747. $traitTree = array();
  748. $exceptionTree = array();
  749. $processed = array();
  750. foreach ($this->parsedClasses as $className => $reflection) {
  751. if (!$reflection->isMain() || !$reflection->isDocumented() || isset($processed[$className])) {
  752. continue;
  753. }
  754. if (null === $reflection->getParentClassName()) {
  755. // No parent classes
  756. if ($reflection->isInterface()) {
  757. $t = &$interfaceTree;
  758. } elseif ($reflection->isTrait()) {
  759. $t = &$traitTree;
  760. } elseif ($reflection->isException()) {
  761. $t = &$exceptionTree;
  762. } else {
  763. $t = &$classTree;
  764. }
  765. } else {
  766. foreach (array_values(array_reverse($reflection->getParentClasses())) as $level => $parent) {
  767. if (0 === $level) {
  768. // The topmost parent decides about the reflection type
  769. if ($parent->isInterface()) {
  770. $t = &$interfaceTree;
  771. } elseif ($parent->isTrait()) {
  772. $t = &$traitTree;
  773. } elseif ($parent->isException()) {
  774. $t = &$exceptionTree;
  775. } else {
  776. $t = &$classTree;
  777. }
  778. }
  779. $parentName = $parent->getName();
  780. if (!isset($t[$parentName])) {
  781. $t[$parentName] = array();
  782. $processed[$parentName] = true;
  783. ksort($t, SORT_STRING);
  784. }
  785. $t = &$t[$parentName];
  786. }
  787. }
  788. $t[$className] = array();
  789. ksort($t, SORT_STRING);
  790. $processed[$className] = true;
  791. unset($t);
  792. }
  793. $template->classTree = new Tree($classTree, $this->parsedClasses);
  794. $template->interfaceTree = new Tree($interfaceTree, $this->parsedClasses);
  795. $template->traitTree = new Tree($traitTree, $this->parsedClasses);
  796. $template->exceptionTree = new Tree($exceptionTree, $this->parsedClasses);
  797. $template
  798. ->setFile($this->getTemplatePath('tree'))
  799. ->save($this->forceDir($this->getTemplateFileName('tree')));
  800. unset($template->classTree);
  801. unset($template->interfaceTree);
  802. unset($template->traitTree);
  803. unset($template->exceptionTree);
  804. $this->fireEvent('generateProgress', 1);
  805. return $this;
  806. }
  807. /**
  808. * Generates packages summary.
  809. *
  810. * @param \ApiGen\Template $template Template
  811. * @return \ApiGen\Generator
  812. * @throws \RuntimeException If template is not set.
  813. */
  814. private function generatePackages(Template $template)
  815. {
  816. if (empty($this->packages)) {
  817. return $this;
  818. }
  819. $this->prepareTemplate('package');
  820. $template->namespace = null;
  821. foreach ($this->packages as $packageName => $package) {
  822. $template->package = $packageName;
  823. $template->subpackages = array_filter($template->packages, function($subpackageName) use ($packageName) {
  824. return (bool) preg_match('~^' . preg_quote($packageName) . '\\\\[^\\\\]+$~', $subpackageName);
  825. });
  826. $template->classes = $package['classes'];
  827. $template->interfaces = $package['interfaces'];
  828. $template->traits = $package['traits'];
  829. $template->exceptions = $package['exceptions'];
  830. $template->constants = $package['constants'];
  831. $template->functions = $package['functions'];
  832. $template
  833. ->setFile($this->getTemplatePath('package'))
  834. ->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getPackageUrl($packageName));
  835. $this->fireEvent('generateProgress', 1);
  836. }
  837. unset($template->subpackages);
  838. return $this;
  839. }
  840. /**
  841. * Generates namespaces summary.
  842. *
  843. * @param \ApiGen\Template $template Template
  844. * @return \ApiGen\Generator
  845. * @throws \RuntimeException If template is not set.
  846. */
  847. private function generateNamespaces(Template $template)
  848. {
  849. if (empty($this->namespaces)) {
  850. return $this;
  851. }
  852. $this->prepareTemplate('namespace');
  853. $template->package = null;
  854. foreach ($this->namespaces as $namespaceName => $namespace) {
  855. $template->namespace = $namespaceName;
  856. $template->subnamespaces = array_filter($template->namespaces, function($subnamespaceName) use ($namespaceName) {
  857. return (bool) preg_match('~^' . preg_quote($namespaceName) . '\\\\[^\\\\]+$~', $subnamespaceName);
  858. });
  859. $template->classes = $namespace['classes'];
  860. $template->interfaces = $namespace['interfaces'];
  861. $template->traits = $namespace['traits'];
  862. $template->exceptions = $namespace['exceptions'];
  863. $template->constants = $namespace['constants'];
  864. $template->functions = $namespace['functions'];
  865. $template
  866. ->setFile($this->getTemplatePath('namespace'))
  867. ->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getNamespaceUrl($namespaceName));
  868. $this->fireEvent('generateProgress', 1);
  869. }
  870. unset($template->subnamespaces);
  871. return $this;
  872. }
  873. /**
  874. * Generate classes, interfaces, traits, exceptions, constants and functions files.
  875. *
  876. * @param Template $template Template
  877. * @return \ApiGen\Generator
  878. * @throws \RuntimeException If template is not set.
  879. */
  880. private function generateElements(Template $template)
  881. {
  882. if (!empty($this->classes) || !empty($this->interfaces) || !empty($this->traits) || !empty($this->exceptions)) {
  883. $this->prepareTemplate('class');
  884. }
  885. if (!empty($this->constants)) {
  886. $this->prepareTemplate('constant');
  887. }
  888. if (!empty($this->functions)) {
  889. $this->prepareTemplate('function');
  890. }
  891. if ($this->config->sourceCode) {
  892. $this->prepareTemplate('source');
  893. }
  894. // Add @usedby annotation
  895. foreach ($this->getElementTypes() as $type) {
  896. foreach ($this->$type as $parentElement) {
  897. $elements = array($parentElement);
  898. if ($parentElement instanceof Reflection\ReflectionClass) {
  899. $elements = array_merge(
  900. $elements,
  901. array_values($parentElement->getOwnMethods()),
  902. array_values($parentElement->getOwnConstants()),
  903. array_values($parentElement->getOwnProperties())
  904. );
  905. }
  906. foreach ($elements as $element) {
  907. $uses = $element->getAnnotation('uses');
  908. if (null === $uses) {
  909. continue;
  910. }
  911. foreach ($uses as $value) {
  912. list($link, $description) = preg_split('~\s+|$~', $value, 2);
  913. $resolved = $this->resolveElement($link, $element);
  914. if (null !== $resolved) {
  915. $resolved->addAnnotation('usedby', $element->getPrettyName() . ' ' . $description);
  916. }
  917. }
  918. }
  919. }
  920. }
  921. $template->package = null;
  922. $template->namespace = null;
  923. $template->classes = $this->classes;
  924. $template->interfaces = $this->interfaces;
  925. $template->traits = $this->traits;
  926. $template->exceptions = $this->exceptions;
  927. $template->constants = $this->constants;
  928. $template->functions = $this->functions;
  929. foreach ($this->getElementTypes() as $type) {
  930. foreach ($this->$type as $element) {
  931. if (!empty($this->namespaces)) {
  932. $template->namespace = $namespaceName = $element->getPseudoNamespaceName();
  933. $template->classes = $this->namespaces[$namespaceName]['classes'];
  934. $template->interfaces = $this->namespaces[$namespaceName]['interfaces'];
  935. $template->traits = $this->namespaces[$namespaceName]['traits'];
  936. $template->exceptions = $this->namespaces[$namespaceName]['exceptions'];
  937. $template->constants = $this->namespaces[$namespaceName]['constants'];
  938. $template->functions = $this->namespaces[$namespaceName]['functions'];
  939. } elseif (!empty($this->packages)) {
  940. $template->package = $packageName = $element->getPseudoPackageName();
  941. $template->classes = $this->packages[$packageName]['classes'];
  942. $template->interfaces = $this->packages[$packageName]['interfaces'];
  943. $template->traits = $this->packages[$packageName]['traits'];
  944. $template->exceptions = $this->packages[$packageName]['exceptions'];
  945. $template->constants = $this->packages[$packageName]['constants'];
  946. $template->functions = $this->packages[$packageName]['functions'];
  947. }
  948. $template->class = null;
  949. $template->constant = null;
  950. $template->function = null;
  951. if ($element instanceof Reflection\ReflectionClass) {
  952. // Class
  953. $template->tree = array_merge(array_reverse($element->getParentClasses()), array($element));
  954. $template->directSubClasses = $element->getDirectSubClasses();
  955. uksort($template->directSubClasses, 'strcasecmp');
  956. $template->indirectSubClasses = $element->getIndirectSubClasses();
  957. uksort($template->indirectSubClasses, 'strcasecmp');
  958. $template->directImplementers = $element->getDirectImplementers();
  959. uksort($template->directImplementers, 'strcasecmp');
  960. $template->indirectImplementers = $element->getIndirectImplementers();
  961. uksort($template->indirectImplementers, 'strcasecmp');
  962. $template->directUsers = $element->getDirectUsers();
  963. uksort($template->directUsers, 'strcasecmp');
  964. $template->indirectUsers = $element->getIndirectUsers();
  965. uksort($template->indirectUsers, 'strcasecmp');
  966. $template->class = $element;
  967. $template
  968. ->setFile($this->getTemplatePath('class'))
  969. ->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getClassUrl($element));
  970. } elseif ($element instanceof Reflection\ReflectionConstant) {
  971. // Constant
  972. $template->constant = $element;
  973. $template
  974. ->setFile($this->getTemplatePath('constant'))
  975. ->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getConstantUrl($element));
  976. } elseif ($element instanceof Reflection\ReflectionFunction) {
  977. // Function
  978. $template->function = $element;
  979. $template
  980. ->setFile($this->getTemplatePath('function'))
  981. ->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getFunctionUrl($element));
  982. }
  983. $this->fireEvent('generateProgress', 1);
  984. // Generate source codes
  985. if ($this->config->sourceCode && $element->isTokenized()) {
  986. $template->fileName = $this->getRelativePath($element->getFileName());
  987. $template->source = $this->highlighter->highlight($this->charsetConvertor->convertFile($element->getFileName()), true);
  988. $template
  989. ->setFile($this->getTemplatePath('source'))
  990. ->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getSourceUrl($element, false));
  991. $this->fireEvent('generateProgress', 1);
  992. }
  993. }
  994. }
  995. return $this;
  996. }
  997. /**
  998. * Creates ZIP archive.
  999. *
  1000. * @return \ApiGen\Generator
  1001. * @throws \RuntimeException If something went wrong.
  1002. */
  1003. private function generateArchive()
  1004. {
  1005. if (!extension_loaded('zip')) {
  1006. throw new RuntimeException('Extension zip is not loaded');
  1007. }
  1008. $archive = new \ZipArchive();
  1009. if (true !== $archive->open($this->getArchivePath(), \ZipArchive::CREATE)) {
  1010. throw new RuntimeException('Could not open ZIP archive');
  1011. }
  1012. $archive->setArchiveComment(trim(sprintf('%s API documentation generated by %s %s on %s', $this->config->title, Environment::getApplicationName(), Environment::getApplicationVersion(), date('Y-m-d H:i:s'))));
  1013. $directory = Nette\Utils\Strings::webalize(trim(sprintf('%s API documentation', $this->config->title)), null, false);
  1014. $destinationLength = strlen($this->config->destination);
  1015. foreach ($this->getGeneratedFiles() as $file) {
  1016. if (is_file($file)) {
  1017. $archive->addFile($file, $directory . DIRECTORY_SEPARATOR . substr($file, $destinationLength + 1));
  1018. }
  1019. }
  1020. if (false === $archive->close()) {
  1021. throw new RuntimeException('Could not save ZIP archive');
  1022. }
  1023. $this->fireEvent('generateProgress', 1);
  1024. return $this;
  1025. }
  1026. /**
  1027. * Tries to resolve string as class, interface or exception name.
  1028. *
  1029. * @param string $className Class name description
  1030. * @param string $namespace Namespace name
  1031. * @return \ApiGen\Reflection\ReflectionClass
  1032. */
  1033. public function getClass($className, $namespace = '')
  1034. {
  1035. if (isset($this->parsedClasses[$namespace . '\\' . $className])) {
  1036. $class = $this->parsedClasses[$namespace . '\\' . $className];
  1037. } elseif (isset($this->parsedClasses[ltrim($className, '\\')])) {
  1038. $class = $this->parsedClasses[ltrim($className, '\\')];
  1039. } else {
  1040. return null;
  1041. }
  1042. // Class is not "documented"
  1043. if (!$class->isDocumented()) {
  1044. return null;
  1045. }
  1046. return $class;
  1047. }
  1048. /**
  1049. * Tries to resolve type as constant name.
  1050. *
  1051. * @param string $constantName Constant name
  1052. * @param string $namespace Namespace name
  1053. * @return \ApiGen\Reflection\ReflectionConstant
  1054. */
  1055. public function getConstant($constantName, $namespace = '')
  1056. {
  1057. if (isset($this->parsedConstants[$namespace . '\\' . $constantName])) {
  1058. $constant = $this->parsedConstants[$namespace . '\\' . $constantName];
  1059. } elseif (isset($this->parsedConstants[ltrim($constantName, '\\')])) {
  1060. $constant = $this->parsedConstants[ltrim($constantName, '\\')];
  1061. } else {
  1062. return null;
  1063. }
  1064. // Constant is not "documented"
  1065. if (!$constant->isDocumented()) {
  1066. return null;
  1067. }
  1068. return $constant;
  1069. }
  1070. /**
  1071. * Tries to resolve type as function name.
  1072. *
  1073. * @param string $functionName Function name
  1074. * @param string $namespace Namespace name
  1075. * @return \ApiGen\Reflection\ReflectionFunction
  1076. */
  1077. public function getFunction($functionName, $namespace = '')
  1078. {
  1079. if (isset($this->parsedFunctions[$namespace . '\\' . $functionName])) {
  1080. $function = $this->parsedFunctions[$namespace . '\\' . $functionName];
  1081. } elseif (isset($this->parsedFunctions[ltrim($functionName, '\\')])) {
  1082. $function = $this->parsedFunctions[ltrim($functionName, '\\')];
  1083. } else {
  1084. return null;
  1085. }
  1086. // Function is not "documented"
  1087. if (!$function->isDocumented()) {
  1088. return null;
  1089. }
  1090. return $function;
  1091. }
  1092. /**
  1093. * Tries to parse a definition of a class/method/property/constant/function and returns the appropriate instance if successful.
  1094. *
  1095. * @param string $definition Definition
  1096. * @param \ApiGen\ReflectionElement|\ApiGen\ReflectionParameter $context Link context
  1097. * @param string $expectedName Expected element name
  1098. * @return \ApiGen\Reflection\ReflectionElement|null
  1099. */
  1100. public function resolveElement($definition, $context, &$expectedName = null)
  1101. {
  1102. // No simple type resolving
  1103. static $types = array(
  1104. 'boolean' => 1, 'integer' => 1, 'float' => 1, 'string' => 1,
  1105. 'array' => 1, 'object' => 1, 'resource' => 1, 'callback' => 1,
  1106. 'callable' => 1, 'null' => 1, 'false' => 1, 'true' => 1, 'mixed' => 1
  1107. );
  1108. if (empty($definition) || isset($types[$definition])) {
  1109. return null;
  1110. }
  1111. $originalContext = $context;
  1112. if ($context instanceof Reflection\ReflectionParameter && null === $context->getDeclaringClassName()) {
  1113. // Parameter of function in namespace or global space
  1114. $context = $this->getFunction($context->getDeclaringFunctionName());
  1115. } elseif ($context instanceof Reflection\ReflectionMethod || $context instanceof Reflection\ReflectionParameter
  1116. || ($context instanceof Reflection\ReflectionConstant && null !== $context->getDeclaringClassName())
  1117. || $context instanceof Reflection\ReflectionProperty
  1118. ) {
  1119. // Member of a class
  1120. $context = $this->getClass($context->getDeclaringClassName());
  1121. }
  1122. if (null === $context) {
  1123. return null;
  1124. }
  1125. // self, $this references
  1126. if ('self' === $definition || '$this' === $definition) {
  1127. return $context instanceof ReflectionClass ? $context : null;
  1128. }
  1129. $definitionBase = substr($definition, 0, strcspn($definition, '\\:'));
  1130. $namespaceAliases = $context->getNamespaceAliases();
  1131. if (!empty($definitionBase) && isset($namespaceAliases[$definitionBase]) && $definition !== ($className = \TokenReflection\Resolver::resolveClassFQN($definition, $namespaceAliases, $context->getNamespaceName()))) {
  1132. // Aliased class
  1133. $expectedName = $className;
  1134. if (false === strpos($className, ':')) {
  1135. return $this->getClass($className, $context->getNamespaceName());
  1136. } else {
  1137. $definition = $className;
  1138. }
  1139. } elseif ($class = $this->getClass($definition, $context->getNamespaceName())) {
  1140. // Class
  1141. return $class;
  1142. } elseif ($constant = $this->getConstant($definition, $context->getNamespaceName())) {
  1143. // Constant
  1144. return $constant;
  1145. } elseif (($function = $this->getFunction($definition, $context->getNamespaceName()))
  1146. || ('()' === substr($definition, -2) && ($function = $this->getFunction(substr($definition, 0, -2), $context->getNamespaceName())))
  1147. ) {
  1148. // Function
  1149. return $function;
  1150. }
  1151. if (($pos = strpos($definition, '::')) || ($pos = strpos($definition, '->'))) {
  1152. // Class::something or Class->something
  1153. if (0 === strpos($definition, 'parent::') && ($parentClassName = $context->getParentClassName())) {
  1154. $context = $this->getClass($parentClassName);
  1155. } elseif (0 !== strpos($definition, 'self::')) {
  1156. $class = $this->getClass(substr($definition, 0, $pos), $context->getNamespaceName());
  1157. if (null === $class) {
  1158. $class = $this->getClass(\TokenReflection\Resolver::resolveClassFQN(substr($definition, 0, $pos), $context->getNamespaceAliases(), $context->getNamespaceName()));
  1159. }
  1160. $context = $class;
  1161. }
  1162. $definition = substr($definition, $pos + 2);
  1163. } elseif ($originalContext instanceof Reflection\ReflectionParameter) {
  1164. return null;
  1165. }
  1166. // No usable context
  1167. if (null === $context || $context instanceof Reflection\ReflectionConstant || $context instanceof Reflection\ReflectionFunction) {
  1168. return null;
  1169. }
  1170. if ($context->hasProperty($definition)) {
  1171. // Class property
  1172. return $context->getProperty($definition);
  1173. } elseif ('$' === $definition{0} && $context->hasProperty(substr($definition, 1))) {
  1174. // Class $property
  1175. return $context->getProperty(substr($definition, 1));
  1176. } elseif ($context->hasMethod($definition)) {
  1177. // Class method
  1178. return $context->getMethod($definition);
  1179. } elseif ('()' === substr($definition, -2) && $context->hasMethod(substr($definition, 0, -2))) {
  1180. // Class method()
  1181. return $context->getMethod(substr($definition, 0, -2));
  1182. } elseif ($context->hasConstant($definition)) {
  1183. // Class constant
  1184. return $context->getConstant($definition);
  1185. }
  1186. return null;
  1187. }
  1188. /**
  1189. * Removes phar:// from the path.
  1190. *
  1191. * @param string $path Path
  1192. * @return string
  1193. */
  1194. public function unPharPath($path)
  1195. {
  1196. if (0 === strpos($path, 'phar://')) {
  1197. $path = substr($path, 7);
  1198. }
  1199. return $path;
  1200. }
  1201. /**
  1202. * Adds phar:// to the path.
  1203. *
  1204. * @param string $path Path
  1205. * @return string
  1206. */
  1207. private function pharPath($path)
  1208. {
  1209. return 'phar://' . $path;
  1210. }
  1211. /**
  1212. * Checks if given path is a phar.
  1213. *
  1214. * @param string $path
  1215. * @return boolean
  1216. */
  1217. private function isPhar($path)
  1218. {
  1219. return (bool) preg_match('~\\.phar(?:\\.zip|\\.tar|(?:(?:\\.tar)?(?:\\.gz|\\.bz2))|$)~i', $path);
  1220. }
  1221. /**
  1222. * Normalizes directory separators in given path.
  1223. *
  1224. * @param string $path Path
  1225. * @return string
  1226. */
  1227. private function normalizePath($path)
  1228. {
  1229. $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
  1230. $path = str_replace('phar:\\\\', 'phar://', $path);
  1231. return $path;
  1232. }
  1233. /**
  1234. * Checks if sitemap.xml is enabled.
  1235. *
  1236. * @return boolean
  1237. */
  1238. private function isSitemapEnabled()
  1239. {
  1240. return !empty($this->config->baseUrl) && $this->templateExists('sitemap', 'optional');
  1241. }
  1242. /**
  1243. * Checks if opensearch.xml is enabled.
  1244. *
  1245. * @return boolean
  1246. */
  1247. private function isOpensearchEnabled()
  1248. {
  1249. return !empty($this->config->googleCseId) && !empty($this->config->baseUrl) && $this->templateExists('opensearch', 'optional');
  1250. }
  1251. /**
  1252. * Checks if robots.txt is enabled.
  1253. *
  1254. * @return boolean
  1255. */
  1256. private function isRobotsEnabled()
  1257. {
  1258. return !empty($this->config->baseUrl) && $this->templateExists('robots', 'optional');
  1259. }
  1260. /**
  1261. * Sorts methods by FQN.
  1262. *
  1263. * @param \ApiGen\Reflection\ReflectionMethod $one
  1264. * @param \ApiGen\Reflection\ReflectionMethod $two
  1265. * @return integer
  1266. */
  1267. private function sortMethods(Reflection\ReflectionMethod $one, Reflection\ReflectionMethod $two)
  1268. {
  1269. return strcasecmp($one->getDeclaringClassName() . '::' . $one->getName(), $two->getDeclaringClassName() . '::' . $two->getName());
  1270. }
  1271. /**
  1272. * Sorts constants by FQN.
  1273. *
  1274. * @param \ApiGen\Reflection\ReflectionConstant $one
  1275. * @param \ApiGen\Reflection\ReflectionConstant $two
  1276. * @return integer
  1277. */
  1278. private function sortConstants(Reflection\ReflectionConstant $one, Reflection\ReflectionConstant $two)
  1279. {
  1280. return strcasecmp(($one->getDeclaringClassName() ?: $one->getNamespaceName()) . '\\' . $one->getName(), ($two->getDeclaringClassName() ?: $two->getNamespaceName()) . '\\' . $two->getName());
  1281. }
  1282. /**
  1283. * Sorts functions by FQN.
  1284. *
  1285. * @param \ApiGen\Reflection\ReflectionFunction $one
  1286. * @param \ApiGen\Reflection\ReflectionFunction $two
  1287. * @return integer
  1288. */
  1289. private function sortFunctions(Reflection\ReflectionFunction $one, Reflection\ReflectionFunction $two)
  1290. {
  1291. return strcasecmp($one->getNamespaceName() . '\\' . $one->getName(), $two->getNamespaceName() . '\\' . $two->getName());
  1292. }
  1293. /**
  1294. * Sorts functions by FQN.
  1295. *
  1296. * @param \ApiGen\Reflection\ReflectionProperty $one
  1297. * @param \ApiGen\Reflection\ReflectionProperty $two
  1298. * @return integer
  1299. */
  1300. private function sortProperties(Reflection\ReflectionProperty $one, Reflection\ReflectionProperty $two)
  1301. {
  1302. return strcasecmp($one->getDeclaringClassName() . '::' . $one->getName(), $two->getDeclaringClassName() . '::' . $two->getName());
  1303. }
  1304. /**
  1305. * Returns list of element types.
  1306. *
  1307. * @return array
  1308. */
  1309. private function getElementTypes()
  1310. {
  1311. static $types = array('classes', 'interfaces', 'traits', 'exceptions', 'constants', 'functions');
  1312. return $types;
  1313. }
  1314. /**
  1315. * Returns main filter.
  1316. *
  1317. * @return \Closure
  1318. */
  1319. private function getMainFilter()
  1320. {
  1321. return function($element) {
  1322. return $element->isMain();
  1323. };
  1324. }
  1325. /**
  1326. * Returns ZIP archive path.
  1327. *
  1328. * @return string
  1329. */
  1330. private function getArchivePath()
  1331. {
  1332. $name = trim(sprintf('%s API documentation', $this->config->title));
  1333. return $this->config->destination . DIRECTORY_SEPARATOR . Nette\Utils\Strings::webalize($name) . '.zip';
  1334. }
  1335. /**
  1336. * Returns filename relative path to the source directory.
  1337. *
  1338. * @param string $fileName
  1339. * @return string
  1340. * @throws \InvalidArgumentException If relative path could not be determined.
  1341. */
  1342. public function getRelativePath($fileName)
  1343. {
  1344. if (isset($this->symlinks[$fileName])) {
  1345. $fileName = $this->symlinks[$fileName];
  1346. }
  1347. foreach ($this->config->source as $source) {
  1348. if ($this->isPhar($source)) {
  1349. $source = $this->pharPath($source);
  1350. }
  1351. if (0 === strpos($fileName, $source)) {
  1352. return is_dir($source) ? str_replace('\\', '/', substr($fileName, strlen($source) + 1)) : basename($fileName);
  1353. }
  1354. }
  1355. throw new InvalidArgumentException(sprintf('Could not determine "%s" relative path', $fileName));
  1356. }
  1357. /**
  1358. * Returns template directory.
  1359. *
  1360. * @return string
  1361. */
  1362. private function getTemplateDir()
  1363. {
  1364. return dirname($this->config->templateConfig);
  1365. }
  1366. /**
  1367. * Returns template path.
  1368. *
  1369. * @param string $name Template name
  1370. * @param string $type Template type
  1371. * @return string
  1372. */
  1373. private function getTemplatePath($name, $type = 'main')
  1374. {
  1375. return $this->getTemplateDir() . DIRECTORY_SEPARATOR . $this->config->template->templates->$type->$name->template;
  1376. }
  1377. /**
  1378. * Returns template filename.
  1379. *
  1380. * @param string $name Template name
  1381. * @param string $type Template type
  1382. * @return string
  1383. */
  1384. private function getTemplateFileName($name, $type = 'main')
  1385. {
  1386. return $this->config->destination . DIRECTORY_SEPARATOR . $this->config->template->templates->$type->$name->filename;
  1387. }
  1388. /**
  1389. * Checks if template exists.
  1390. *
  1391. * @param string $name Template name
  1392. * @param string $type Template type
  1393. * @return string
  1394. */
  1395. private function templateExists($name, $type = 'main')
  1396. {
  1397. return isset($this->config->template->templates->$type->$name);
  1398. }
  1399. /**
  1400. * Checks if template exists and creates dir.
  1401. *
  1402. * @param string $name
  1403. * @throws \RuntimeException If template is not set.
  1404. */
  1405. private function prepareTemplate($name)
  1406. {
  1407. if (!$this->templateExists($name)) {
  1408. throw new RuntimeException(sprintf('Template for "%s" is not set', $name));
  1409. }
  1410. $this->forceDir($this->getTemplateFileName($name));
  1411. return $this;
  1412. }
  1413. /**
  1414. * Returns list of all generated files.
  1415. *
  1416. * @return array
  1417. */
  1418. private function getGeneratedFiles()
  1419. {
  1420. $files = array();
  1421. // Resources
  1422. foreach ($this->config->template->resources as $item) {
  1423. $path = $this->getTemplateDir() . DIRECTORY_SEPARATOR . $item;
  1424. if (is_dir($path)) {
  1425. $iterator = Nette\Utils\Finder::findFiles('*')->from($path)->getIterator();
  1426. foreach ($iterator as $innerItem) {
  1427. $files[] = $this->config->destination . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
  1428. }
  1429. …

Large files files are truncated, but you can click here to view the full file