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

/libs/Apigen/Generator.php

https://github.com/OndrejBrejla/apigen
PHP | 266 lines | 197 code | 29 blank | 40 comment | 10 complexity | 917ad935a503bd5bd2de4b6c79b3a7f5 MD5 | raw file
  1. <?php
  2. /**
  3. * API Generator.
  4. *
  5. * Copyright (c) 2010 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license", and/or
  8. * GPL license. For more information please see http://nette.org
  9. */
  10. namespace Apigen;
  11. use NetteX;
  12. /**
  13. * Generates a HTML API documentation based on model.
  14. * @author David Grudl
  15. */
  16. class Generator extends NetteX\Object
  17. {
  18. /** @var Model */
  19. private $model;
  20. public function __construct(Model $model)
  21. {
  22. $this->model = $model;
  23. }
  24. /**
  25. * Generates API documentation.
  26. * @param string output directory
  27. * @param array
  28. * @void
  29. */
  30. public function generate($output, $config)
  31. {
  32. if (!is_dir($output)) {
  33. throw new \Exception("Directory $output doesn't exist.");
  34. }
  35. // copy resources
  36. foreach ($config['resources'] as $source => $dest) {
  37. foreach ($iterator = NetteX\Finder::findFiles('*')->from($source)->getIterator() as $foo) {
  38. copy($iterator->getPathName(), self::forceDir("$output/$dest/" . $iterator->getSubPathName()));
  39. }
  40. }
  41. // categorize by namespaces
  42. $namespaces = array();
  43. $allClasses = array();
  44. foreach ($this->model->getClasses() as $class) {
  45. $namespaces[$class->isInternal() ? 'PHP' : $class->getNamespaceName()][$class->getShortName()] = $class;
  46. $allClasses[$class->getShortName()] = $class;
  47. }
  48. uksort($namespaces, 'strcasecmp');
  49. uksort($allClasses, 'strcasecmp');
  50. $template = $this->createTemplate();
  51. $template->fileRoot = $this->model->getDirectory();
  52. foreach ($config['variables'] as $key => $value) {
  53. $template->$key = $value;
  54. }
  55. // generate summary files
  56. $template->namespaces = array_keys($namespaces);
  57. $template->classes = $allClasses;
  58. foreach ($config['templates']['common'] as $dest => $source) {
  59. $template->setFile($source)->save(self::forceDir("$output/$dest"));
  60. }
  61. $generatedFiles = array();
  62. $fshl = new \fshlParser('HTML_UTF8', P_TAB_INDENT | P_LINE_COUNTER);
  63. foreach ($namespaces as $namespace => $classes) {
  64. // generate namespace summary
  65. uksort($classes, 'strcasecmp');
  66. $template->namespace = $namespace;
  67. $template->classes = $classes;
  68. $template->setFile($config['templates']['namespace'])->save(self::forceDir($output . '/' . $this->formatNamespaceLink($namespace)));
  69. // generate class & interface files
  70. foreach ($classes as $class) {
  71. $template->tree = array($class);
  72. while ($parent = $template->tree[0]->getParentClass()) {
  73. array_unshift($template->tree, $parent);
  74. }
  75. $template->subClasses = $this->model->getDirectSubClasses($class);
  76. uksort($template->subClasses, 'strcasecmp');
  77. $template->implementers = $this->model->getDirectImplementers($class);
  78. uksort($template->implementers, 'strcasecmp');
  79. $template->class = $class;
  80. $template->setFile($config['templates']['class'])->save(self::forceDir($output . '/' . $this->formatClassLink($class)));
  81. // generate source codes
  82. if (!$class->isInternal() && !isset($generatedFiles[$class->getFileName()])) {
  83. $file = $class->getFileName();
  84. $template->source = $fshl->highlightString('PHP', file_get_contents($file));
  85. $template->fileName = substr($file, strlen($this->model->getDirectory()) + 1);
  86. $template->setFile($config['templates']['source'])->save(self::forceDir($output . '/' . $this->formatSourceLink($class, FALSE)));
  87. $generatedFiles[$file] = TRUE;
  88. }
  89. }
  90. }
  91. }
  92. /** @return NetteX\Templates\FileTemplate */
  93. private function createTemplate()
  94. {
  95. $template = new NetteX\Templates\FileTemplate;
  96. $template->setCacheStorage(new NetteX\Caching\MemoryStorage);
  97. $latte = new NetteX\Templates\LatteFilter;
  98. $latte->handler->macros['try'] = '<?php try { ?>';
  99. $latte->handler->macros['/try'] = '<?php } catch (\Exception $e) {} ?>';
  100. $template->registerFilter($latte);
  101. // common operations
  102. $template->registerHelperLoader('NetteX\Templates\TemplateHelpers::loader');
  103. $template->registerHelper('ucfirst', 'ucfirst');
  104. $template->registerHelper('values', 'array_values');
  105. $template->registerHelper('map', function($arr, $callback) {
  106. return array_map(create_function('$value', $callback), $arr);
  107. });
  108. $template->registerHelper('replaceRE', 'NetteX\String::replace');
  109. $template->registerHelper('replaceNS', function($name, $namespace) { // remove current namespace
  110. return (strpos($name, $namespace . '\\') === 0 && strpos($name, '\\', strlen($namespace) + 1) === FALSE)
  111. ? substr($name, strlen($namespace) + 1) : $name;
  112. });
  113. $fshl = new \fshlParser('HTML_UTF8');
  114. $template->registerHelper('dump', function($val) use ($fshl) {
  115. return $fshl->highlightString('PHP', var_export($val, TRUE));
  116. });
  117. // links
  118. $template->registerHelper('namespaceLink', callbackX($this, 'formatNamespaceLink'));
  119. $template->registerHelper('classLink', callbackX($this, 'formatClassLink'));
  120. $template->registerHelper('sourceLink', callbackX($this, 'formatSourceLink'));
  121. // docblock
  122. $texy = new \TexyX;
  123. $texy->allowed['list/definition'] = FALSE;
  124. $texy->allowed['phrase/em-alt'] = FALSE;
  125. $texy->registerBlockPattern( // highlight <code>, <pre>
  126. function($parser, $matches, $name) use ($fshl) {
  127. $content = $matches[1] === 'code' ? $fshl->highlightString('PHP', $matches[2]) : htmlSpecialChars($matches[2]);
  128. $content = $parser->getTexy()->protect($content, \TexyX::CONTENT_BLOCK);
  129. return \TexyXHtml::el('pre', $content);
  130. },
  131. '#^<(code|pre)>\n(.+?)\n</\1>$#ms', // block patterns must be multiline and line-anchored
  132. 'codeBlockSyntax'
  133. );
  134. $template->registerHelper('docline', function($doc, $line = TRUE) use ($texy) {
  135. $doc = Model::extractDocBlock($doc);
  136. $doc = preg_replace('#\n.*#s', '', $doc); // leave only first line
  137. return $line ? $texy->processLine($doc) : $texy->process($doc);
  138. });
  139. $template->registerHelper('docblock', function($doc) use ($texy) {
  140. return $texy->process(Model::extractDocBlock($doc));
  141. });
  142. // types
  143. $model = $this->model;
  144. $template->registerHelper('getTypes', function($element, $position = NULL) use ($model) {
  145. $namespace = $element->getDeclaringClass()->getNamespaceName();
  146. $s = $position === NULL ? $element->getAnnotation($element instanceof \ReflectionProperty ? 'var' : 'return')
  147. : @$element->annotations['param'][$position];
  148. if (is_object($s)) {
  149. $s = get_class($s); // TODO
  150. }
  151. $s = preg_replace('#\s.*#', '', $s);
  152. $res = array();
  153. foreach (explode('|', $s) as $name) {
  154. $res[] = (object) array('name' => $name, 'class' => $model->resolveType($name, $namespace));
  155. }
  156. return $res;
  157. });
  158. $template->registerHelper('resolveType', callbackX($model, 'resolveType'));
  159. return $template;
  160. }
  161. /**
  162. * Generates link to namespace summary file.
  163. * @param string|ReflectionClass
  164. * @return string
  165. */
  166. public function formatNamespaceLink($class)
  167. {
  168. $namescape = $class instanceof \ReflectionClass ? $class->getNamespaceName() : $class;
  169. return 'namespace-' . ($namescape ? preg_replace('#[^a-z0-9_]#i', '.', $namescape) : 'none') . '.html';
  170. }
  171. /**
  172. * Generates link to class summary file.
  173. * @param string|ReflectionClass|ReflectionMethod|ReflectionProperty
  174. * @return string
  175. */
  176. public function formatClassLink($element)
  177. {
  178. $id = '';
  179. if (is_string($element)) {
  180. $class = $element;
  181. } elseif ($element instanceof \ReflectionClass) {
  182. $class = $element->getName();
  183. } else {
  184. $class = $element->getDeclaringClass()->getName();
  185. if ($element instanceof \ReflectionProperty) {
  186. $id = '#$' . $element->getName();
  187. } elseif ($element instanceof \ReflectionMethod) {
  188. $id = '#_' . $element->getName();
  189. }
  190. }
  191. return preg_replace('#[^a-z0-9_]#i', '.', $class) . '.html' . $id;
  192. }
  193. /**
  194. * Generates link to class source code file.
  195. * @param ReflectionClass|ReflectionMethod
  196. * @return string
  197. */
  198. public function formatSourceLink($element, $withLine = TRUE)
  199. {
  200. $class = $element instanceof \ReflectionClass ? $element : $element->getDeclaringClass();
  201. if ($class->isInternal()) {
  202. if ($element instanceof \ReflectionClass) {
  203. return strtolower('http://php.net/manual/class.' . $class->getName() . '.php');
  204. } else {
  205. return strtolower('http://php.net/manual/' . $class->getName() . '.' . strtr(ltrim($element->getName(), '_'), '_', '-') . '.php');
  206. }
  207. } else {
  208. $file = substr($element->getFileName(), strlen($this->model->getDirectory()) + 1);
  209. $line = $withLine ? ($element->getStartLine() - substr_count($element->getDocComment(), "\n") - 1) : NULL;
  210. return 'source-' . preg_replace('#[^a-z0-9_]#i', '.', $file) . '.html' . (isset($line) ? "#$line" : '');
  211. }
  212. }
  213. /**
  214. * Ensures directory is created.
  215. * @param string
  216. * @return string
  217. */
  218. public static function forceDir($path)
  219. {
  220. @mkdir(dirname($path), 0755, TRUE);
  221. return $path;
  222. }
  223. }