PageRenderTime 44ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/ApiGen/Generator.php

https://bitbucket.org/gencer/apigen
PHP | 1718 lines | 1365 code | 97 blank | 256 comment | 47 complexity | 84e0e84929d197f40c975aafa7da72f2 MD5 | raw file
Possible License(s): BSD-3-Clause

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

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