stub file generation
[mdref/mdref] / mdref / Structure.php
1 <?php
2
3 namespace mdref;
4
5 /**
6 * Structure of an entry
7 */
8 class Structure {
9 const OF_OTHER = "other";
10 const OF_NAMESPACE = "ns";
11 const OF_CLASS = "class";
12 const OF_FUNC = "func";
13
14 private $type;
15 private $struct;
16 private $entry;
17
18 function __construct(Entry $entry) {
19 $this->entry = $entry;
20
21 if ($entry->isRoot() || $entry->isNsClass()) {
22 if ($entry->isRoot()) {
23 $this->type = self::OF_NAMESPACE;
24 $this->getStructureOfRoot();
25 } elseif (!strncmp($entry->getTitle(), "namespace", strlen("namespace"))) {
26 $this->type = self::OF_NAMESPACE;
27 $this->getStructureOfNs();
28 } else {
29 $this->type = self::OF_CLASS;
30 $this->getStructureOfClass();
31 }
32 } elseif ($entry->isFunction()) {
33 $this->type = self::OF_FUNC;
34 $this->getStructureOfFunc();
35 } else {
36 $this->type = self::OF_OTHER;
37 }
38 }
39
40 static function of(Entry $entry) : StructureOf {
41 return (new static($entry))->getStruct();
42 }
43
44 function getStruct() : StructureOf {
45 return $this->struct;
46 }
47
48 function format() {
49 $this->struct->format();
50 }
51
52 private function getStructureOfFunc() : StructureOfFunc {
53 return $this->struct = new StructureOfFunc([
54 "ns" => $this->entry->getParent()->getNsName(),
55 "name" => $this->entry->getEntryName(),
56 "desc" => $this->entry->getFullDescription(),
57 "returns" => $this->getReturns(),
58 "params" => $this->getParams(),
59 "throws" => $this->getThrows()
60 ]);
61 }
62
63 private function getStructureOfClass() : StructureOfClass {
64 return $this->struct = new StructureOfClass([
65 "ns" => $this->entry->getParent()->getNsName(),
66 "name" => $this->prepareClassName(),
67 "desc" => $this->entry->getFullDescription(),
68 "consts" => $this->getConstants(),
69 "props" => $this->getProperties(),
70 "funcs" => $this->getFunctions(),
71 "classes" => $this->getClasses(),
72 ]);
73 }
74
75 private function getStructureOfNs() : StructureOfNs {
76 return $this->struct = new StructureOfNs([
77 "name" => $this->entry->getNsName(),
78 "desc" => $this->entry->getFullDescription(),
79 "consts" => $this->getConstants(),
80 "classes" => $this->getClasses(),
81 ]);
82 }
83
84 private function getStructureOfRoot() : StructureOfRoot {
85 return $this->struct = new StructureOfRoot([
86 "name" => $this->entry->getName(),
87 "desc" => $this->entry->getFile()->readIntro(),
88 "consts" => $this->getConstants(),
89 "classes" => $this->getClasses()
90 ]);
91 }
92
93 private function getSection(string $section) : string {
94 return $this->entry->getFile()->readSection($section);
95 }
96
97 private function prepareClassName() {
98 return preg_replace_callback_array([
99 '/(?P<type>class|interface|trait)\s+([\\\\\w]+\\\)?(?P<name>\w+)\s*/' => function($match) {
100 return $match["type"] . " " . $match["name"] . " ";
101 },
102 '/(?P<op>extends|implements)\s+(?P<names>[\\w]+(?:(?:,\s*[\\\\\w]+)*))/' => function ($match) {
103 return $match["op"] . " " . preg_replace('/\b(?<!\\\)(\w)/', '\\\\\\1', $match["names"]);
104 }
105 ], $this->entry->getTitle());
106 }
107
108 private function splitList(string $pattern, string $text) : array {
109 $text = trim($text);
110 if (strlen($text) && !preg_match("/^None/", $text)) {
111 if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) {
112 return $matches;
113 }
114 }
115 return [];
116 }
117
118 private function getConstantValue(string $name) {
119 $ns = $this->entry->getNsName();
120 if (defined("\\$ns::$name")) {
121 return constant("\\$ns::$name");
122 }
123 if (defined("\\$ns\\$name")) {
124 return constant("\\$ns\\$name");
125 }
126 return null;
127 }
128
129 private function getConstants() : array {
130 static $pattern = '/
131 \*\s+
132 (?<name>\w+)
133 (?:\s*=\s*(?P<value>.+))?
134 (?P<desc>(?:\s*\n\s*[^\*\n#].*)*)
135 /x';
136
137 $structs = [];
138 $consts = $this->splitList($pattern, $this->getSection("Constants"));
139 foreach ($consts as $const) {
140 if (!isset($const["value"]) || !strlen($const["value"])) {
141 $const["value"] = $this->getConstantValue($const["name"]);
142 }
143 $structs[$const["name"]] = new StructureOfConst($const);
144 }
145 return $structs;
146 }
147
148 private function getProperties() : array {
149 static $pattern = '/
150 \*\s+
151 (?P<modifiers>\w+\s+)*
152 (?P<type>[\\\\\w]+)\s+
153 (?<name>\$\w+)
154 (?:\s*=\s*(?P<defval>.+))?
155 (?P<desc>(?:\s*\n\s*[^\*].*)*)
156 /x';
157
158 $structs = [];
159 $props = $this->splitList($pattern, $this->getSection("Properties"));
160 foreach ($props as $prop) {
161 $structs[$prop["name"]] = new StructureOfVar($prop);
162 }
163 return $structs;
164 }
165
166 private function getFunctions() : array {
167 $structs = [];
168 foreach ($this->entry as $sub) {
169 if ($sub->isFunction()) {
170 $structs[$sub->getEntryName()] = static::of($sub);
171 }
172 }
173 return $structs;
174 }
175
176 private function getClasses() : array {
177 $structs = [];
178 foreach ($this->entry as $sub) {
179 if ($sub->isNsClass()) {
180 $structs[$sub->getEntryName()] = static::of($sub);
181 }
182 }
183 return $structs;
184 }
185
186 private function getParams() : array {
187 static $pattern = '/
188 \*\s+
189 (?P<modifiers>\w+\s+)*
190 (?P<type>[\\\\\w]+)\s+
191 (?<name>\$\w+)
192 (?:\s*=\s*(?P<defval>.+))?
193 (?P<desc>(?:\s*[^*]*\n(?!\n)\s*[^\*].*)*)
194 /x';
195
196 $structs = [];
197 $params = $this->splitList($pattern, $this->getSection("Params"));
198 foreach ($params as $param) {
199 $structs[$param["name"]] = new StructureOfVar($param);
200 }
201 return $structs;
202 }
203
204 private function getReturns() : array {
205 static $pattern = '/
206 \*\s+
207 (?<type>[\\\\\w]+)
208 \s*,?\s*
209 (?P<desc>(?:.|\n(?!\s*\*))*)
210 /x';
211
212 $returns = $this->splitList($pattern, $this->getSection("Returns"));
213 $retdesc = "";
214 foreach ($returns as list(, $type, $desc)) {
215 if (strlen($retdesc)) {
216 $retdesc .= "\n\t or $type $desc";
217 } else {
218 $retdesc = $desc;
219 }
220 }
221 return [implode("|", array_unique(array_column($returns, "type"))), $retdesc];
222 }
223
224 private function getThrows() : array {
225 static $pattern = '/
226 \*\s+
227 (?P<exception>[\\\\\w]+)\s*
228 /x';
229
230 $throws = $this->splitList($pattern, $this->getSection("Throws"));
231 return array_column($throws, "exception");
232 }
233 }
234
235 abstract class StructureOf {
236 function __construct(array $props = []) {
237 foreach ($props as $key => $val) {
238 if (is_int($key)) {
239 continue;
240 }
241 if (!property_exists(static::class, $key)) {
242 throw new \UnexpectedValueException(
243 sprintf("Property %s::\$%s does not exist", static::class, $key)
244 );
245 }
246 if ($key === "desc" || $key === "modifiers" || $key === "defval") {
247 $val = trim($val);
248 }
249 $this->$key = $val;
250 }
251 }
252
253 // abstract function format();
254
255 function formatDesc(int $level, array $tags = []) {
256 $indent = str_repeat("\t", $level);
257 $desc = trim($this->desc);
258 if (false !== stristr($desc, "deprecated in")) {
259 $tags[] = "@deprecated";
260 }
261 if ($tags) {
262 $desc .= "\n\n@" . implode("\n@", $tags);
263 }
264 $desc = preg_replace('/[\t ]*\n/',"\n$indent * ", $desc);
265 printf("%s/**\n%s * %s\n%s */\n", $indent, $indent, $desc, $indent);
266 }
267 }
268
269 class StructureOfRoot extends StructureOf {
270 public $name;
271 public $desc;
272 public $consts;
273 public $classes;
274
275 function format() {
276 $this->formatDesc(0);
277
278 foreach ($this->consts as $const) {
279 $const->format(0);
280 printf(";\n");
281 }
282
283 printf("namespace %s;\nuse %s;\n", $this->name, $this->name);
284 StructureOfNs::$last = $this->name;
285
286 foreach ($this->getClasses() as $class) {
287 $class->format();
288 }
289 }
290
291 function getClasses() {
292 yield from $this->classes;
293 foreach ($this->classes as $class) {
294 yield from $class->getClasses();
295 }
296 }
297 }
298 class StructureOfNs extends StructureOfRoot {
299 public $funcs;
300
301 public static $last;
302
303 function format() {
304 print $this->formatDesc(0);
305
306 if (strlen($this->name) && $this->name !== StructureOfNs::$last) {
307 StructureOfNs::$last = $this->name;
308 printf("namespace %s;\n", $this->name);
309 }
310 foreach ($this->consts as $const) {
311 $const->format(0);
312 printf(";\n");
313 }
314 }
315 }
316
317 class StructureOfClass extends StructureOfNs
318 {
319 public $ns;
320 public $props;
321
322 static $lastNs;
323
324 function format() {
325 if ($this->ns !== StructureOfNs::$last) {
326 printf("namespace %s;\n", $this->ns);
327 StructureOfNs::$last = $this->ns;
328 }
329
330 print $this->formatDesc(0);
331 printf("%s {\n", $this->name);
332
333 foreach ($this->consts as $const) {
334 $const->format(1);
335 printf(";\n");
336 }
337
338 foreach ($this->props as $prop) {
339 $prop->formatAsProp(1);
340 printf(";\n");
341 }
342
343 foreach ($this->funcs as $func) {
344 $func->format(1);
345 if (strncmp($this->name, "interface", strlen("interface"))) {
346 printf(" {}\n");
347 } else {
348 printf(";\n");
349 }
350 }
351
352 printf("}\n");
353 }
354 }
355
356 class StructureOfFunc extends StructureOf {
357 public $ns;
358 public $class;
359 public $name;
360 public $desc;
361 public $params;
362 public $returns;
363 public $throws;
364
365 function format(int $level) {
366 $tags = [];
367 foreach ($this->params as $param) {
368 $tags[] = "param {$param->type} {$param->name} {$param->desc}";
369 }
370 foreach ($this->throws as $throws) {
371 $tags[] = "throws $throws";
372 }
373 if ($this->name !== "__construct" && $this->returns[0]) {
374 $tags[] = "return {$this->returns[0]} {$this->returns[1]}";
375 }
376 $this->formatDesc(1, $tags);
377 printf("\tfunction %s(", $this->name);
378 $comma = "";
379 foreach ($this->params as $param) {
380 print $comma;
381 $param->formatAsParam($level);
382 $comma = ", ";
383 }
384 printf(")");
385 }
386 }
387
388 class StructureOfConst extends StructureOf {
389 public $name;
390 public $desc;
391 public $value;
392
393 function format(int $level) {
394 $indent = str_repeat("\t", $level);
395 $this->formatDesc($level);
396 printf("%sconst %s = ", $indent, $this->name);
397 var_export($this->value);
398 }
399 }
400
401 class StructureOfVar extends StructureOf {
402 public $name;
403 public $type;
404 public $desc;
405 public $modifiers;
406 public $defval;
407
408 function formatDefval() {
409 if (strlen($this->defval)) {
410 if (false && defined($this->defval)) {
411 printf(" = ");
412 var_export(constant($this->defval));
413 } else if (strlen($this->defval)) {
414 printf(" = %s", $this->defval);
415 }
416 } elseif ($this->modifiers) {
417 if (stristr($this->modifiers, "optional") !== false) {
418 printf(" = NULL");
419 }
420 }
421 }
422 function formatAsProp(int $level) {
423 $indent = str_repeat("\t", $level);
424 $this->formatDesc($level,
425 preg_split('/\s+/', $this->modifiers, -1, PREG_SPLIT_NO_EMPTY)
426 + [-1 => "var {$this->type}"]
427 );
428 printf("%s%s %s", $indent, $this->modifiers, $this->name);
429 $this->formatDefval();
430 }
431
432 function formatAsParam(int $level) {
433 printf("%s", $this->name);
434 $this->formatDefval();
435 }
436 }