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

/hphp/tools/docskel/base.php

http://github.com/facebook/hiphop-php
PHP | 232 lines | 213 code | 17 blank | 2 comment | 35 complexity | b57c2f19d25a581ccc3bb5fd444e8a3b MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0
  1. <?hh
  2. class HHVMDocExtension {
  3. protected bool $verbose = false;
  4. protected bool $parsed = false;
  5. protected array $classes = [];
  6. protected array $functions = [];
  7. protected function svnScandir(string $path): array {
  8. $ret = array('dirs' => [], 'files' => []);
  9. $entries = scandir($path);
  10. foreach ($entries as $entry) {
  11. if ($entry[0] == '.') continue;
  12. if (is_dir("$path/$entry")) {
  13. $ret['dirs'][$entry] = [
  14. 'name' => $entry,
  15. 'path' => "$path/$entry",
  16. ];
  17. } else if (pathinfo($entry, PATHINFO_EXTENSION) === 'xml') {
  18. $ret['files'][$entry] = [
  19. 'name' => $entry,
  20. 'path' => "$path/$entry",
  21. ];
  22. }
  23. }
  24. return $ret;
  25. }
  26. protected static function GetDescriptionDOM(DOMNode $d): string {
  27. $ret = '';
  28. for ($node = $d->firstChild; $node; $node = $node->nextSibling) {
  29. if ($node->nodeType == XML_TEXT_NODE) {
  30. $ret .= preg_replace('/\s+/s', ' ', (string)$node->nodeValue);
  31. continue;
  32. }
  33. switch (strtolower($node->nodeName)) {
  34. case 'function':
  35. $ret .= self::GetDescriptionDOM($node) . '()';
  36. break;
  37. case 'para':
  38. default:
  39. $ret .= self::GetDescriptionDOM($node);
  40. }
  41. }
  42. return $ret;
  43. }
  44. protected static function GetDescription(SimpleXMLElement $e): string {
  45. return trim(self::GetDescriptionDOM(dom_import_simplexml($e)));
  46. }
  47. protected function getXml(string $path): SimpleXMLElement {
  48. $xml = file_get_contents($path);
  49. $xml = preg_replace_callback('@&([a-zA-Z0-9_\.-]*);@s',
  50. function (array<string> $ents) {
  51. $ent = $ents[1];
  52. switch (strtolower($ent)) {
  53. case 'true':
  54. case 'false':
  55. case 'null':
  56. return strtoupper($ent);
  57. default:
  58. return '';
  59. }
  60. }, $xml);
  61. return simplexml_load_string($xml, "SimpleXMLElement",
  62. LIBXML_NOENT|LIBXML_NONET);
  63. }
  64. protected function parseFunction(SimpleXMLElement $sxe,
  65. bool $oop): array {
  66. $f = [];
  67. // UNSAFE: typechecker doesn't understand __get on $sxe
  68. foreach ($sxe->refsect1 as $sect) {
  69. $attr = $sect->attributes();
  70. if ($attr->role == "description") {
  71. foreach($sect->methodsynopsis as $synopsis) {
  72. $fname = (string)$synopsis->methodname;
  73. $colon = strpos($fname, '::');
  74. if ($colon === false) {
  75. if ($oop) { $f['alias'] = $fname; continue; }
  76. $f['name'] = $fname;
  77. } else {
  78. if (!$oop) { $f['alias'] = $fname; continue; }
  79. $f['class'] = substr($fname, 0, $colon);
  80. $f['name'] = substr($fname, $colon + 2);
  81. }
  82. $f['return']['type'] = (string)$synopsis->type;
  83. $modifiers = [];
  84. foreach ($synopsis->modifier as $m) {
  85. $modifiers[] = (string)$m;
  86. }
  87. $f['modifiers'] = $modifiers;
  88. $f['args'] = [];
  89. foreach ($synopsis->methodparam as $param) {
  90. $pn = trim((string)$param->parameter);
  91. $pa = $param->parameter->attributes();
  92. $prole = (string)$pa->role;
  93. if (empty($pn)) continue;
  94. $arg = [
  95. 'type' => strtolower((string)$param->type),
  96. 'name' => $pn,
  97. 'reference' => ($prole == 'reference'),
  98. ];
  99. if (isset($param->initializer)) {
  100. $arg['default'] = (string)$param->initializer;
  101. }
  102. $f['args'][strtolower($pn)] = $arg;
  103. }
  104. }
  105. } elseif ($attr->role == "parameters") {
  106. foreach ($sect->para->variablelist->varlistentry as $entry) {
  107. $name = trim((string)$entry->term->parameter);
  108. if (empty($f['args'][strtolower($name)])) continue;
  109. $f['args'][strtolower($name)]['desc'] =
  110. self::GetDescription($entry->listitem);
  111. }
  112. } elseif ($attr->role == "returnvalues") {
  113. $f['return']['desc'] = self::GetDescription($sect);
  114. }
  115. }
  116. if (!empty($f['name'])) {
  117. $f['desc'] = (string)$sxe->refnamediv->refpurpose;
  118. if (empty($f['class'])) {
  119. $this->functions[strtolower($f['name'])] = $f;
  120. } else {
  121. $this->classes[strtolower($f['class'])]
  122. ['functions'][strtolower($f['name'])] = $f;
  123. }
  124. }
  125. }
  126. protected function parseFunctions(string $path): void {
  127. $dirinfo = $this->svnScandir($path);
  128. foreach($dirinfo['files'] as $file) {
  129. $xml = $this->getXml($file['path']);
  130. $this->parseFunction($xml, true);
  131. $this->parseFunction($xml, false);
  132. }
  133. }
  134. protected function parseClass(string $path): void {
  135. $sxe = $this->getXml($path);
  136. // UNSAFE: typechecker doesn't understand __get on $sxe
  137. foreach ($sxe->partintro->section as $sect) {
  138. $xmlattrs = $sect->attributes('xml', true);
  139. if (substr($xmlattrs->id, -6) == '.intro') {
  140. $name = substr($xmlattrs->id, 0, -6);
  141. $this->classes[strtolower($name)]['intro'] =
  142. self::GetDescription($sect);
  143. } elseif (substr($xmlattrs->id, -9) == '.synopsis' &&
  144. !empty($sect->classsynopsis)) {
  145. $name = substr($xmlattrs->id, 0, -9);
  146. $synopsis = $sect->classsynopsis;
  147. $this->classes[strtolower($name)]['name'] = ucfirst($name);
  148. foreach($synopsis->ooclass as $ooclass) {
  149. $modifier = (string)$ooclass->modifier;
  150. $cn = (string)$ooclass->classname;
  151. if (empty($modifier)) {
  152. $this->classes[strtolower($name)]['name'] = $cn;
  153. } elseif ($modifier == 'extends') {
  154. $this->classes[strtolower($name)]['extends'] = $cn;
  155. } elseif ($modifier == 'implements') {
  156. $this->classes[strtolower($name)]['implements'][] = $cn;
  157. }
  158. }
  159. } elseif (substr($xmlattrs->id, -6) == '.props' &&
  160. !empty($sect->variablelist)) {
  161. $name = substr($xmlattrs->id, 0, -6);
  162. foreach ($sect->variablelist->varlistentry as $field) {
  163. $propname = (string)$field->term->varname;
  164. $this->classes[strtolower($name)]['props'][strtolower($propname)] = [
  165. 'name' => $propname,
  166. 'desc' => self::GetDescription($field->listitem),
  167. ];
  168. }
  169. }
  170. }
  171. }
  172. protected function fixupClasses(): void {
  173. foreach ($this->classes as &$class) {
  174. if (!empty($class['functions']) &&
  175. empty($class['name'])) {
  176. foreach($class['functions'] as $func) {
  177. $class['name'] = $func['class'];
  178. break;
  179. }
  180. }
  181. }
  182. }
  183. protected function parse(): this {
  184. if ($this->parsed) return $this;
  185. $url = $this->root.
  186. '/en/reference/'.
  187. urlencode($this->name);
  188. $dirinfo = $this->svnScandir($url);
  189. foreach($dirinfo['files'] as $file) {
  190. $this->parseClass($file['path']);
  191. }
  192. foreach ($dirinfo['dirs'] as $dir) {
  193. $this->parseFunctions($dir['path']);
  194. }
  195. $this->fixupClasses();
  196. $this->parsed = true;
  197. return $this;
  198. }
  199. public function __construct(protected string $name,
  200. protected string $root):
  201. void {
  202. if (!file_exists($this->root.'/en/reference') ||
  203. !is_dir($this->root.'/en/reference')) {
  204. throw new Exception("{$this->root} does not appear to be ".
  205. "a phpdoc checkout. See http://php.net/git.php ".
  206. "for instructions on how to checkout phpdoc-en.");
  207. }
  208. }
  209. public function getFunctions(): array { return $this->parse()->functions; }
  210. public function getClasses(): array { return $this->parse()->classes; }
  211. public function setVerbose(bool $verbose): this {
  212. $this->verbose = $verbose;
  213. return $this;
  214. }
  215. }