1<?php
2
3declare(strict_types=1);
4
5$baseDir   = dirname(dirname(__DIR__));
6$sourceDir = implode(DIRECTORY_SEPARATOR, [$baseDir, 'phalcon', '']);
7$outputDir = implode(DIRECTORY_SEPARATOR, [$baseDir, 'nikos', 'api', '']);
8
9if (!is_dir($outputDir)) {
10    mkdir($outputDir, 0777, true);
11}
12
13$iterator = new RecursiveIteratorIterator(
14    new RecursiveDirectoryIterator(
15        $sourceDir,
16        FilesystemIterator::SKIP_DOTS
17    ),
18    RecursiveIteratorIterator::CHILD_FIRST
19);
20
21$iteratorDocs = [];
22foreach ($iterator as $file) {
23    if (true === $file->isFile()) {
24        $iteratorDocs[] = str_replace($sourceDir, '', $file->getPathName());
25    }
26}
27
28asort($iteratorDocs);
29
30$documents = [];
31foreach ($iteratorDocs as $fileName) {
32    $split    = explode(DIRECTORY_SEPARATOR, $fileName);
33    $title    = str_replace('Phalcon\\', '', $fileName);
34    $key      = str_replace('.zep', '', $split[0]);
35
36    $documents[$key]['title']           = 'Phalcon\\' . $key;
37    $documents[$key]['docs'][implode('/', $split)] = $fileName;
38
39    if (strpos($documents[$key]['title'], 'Url') > 0) {
40        $documents[$key]['title'] = 'Phalcon\Url';
41    }
42
43    ksort($documents[$key]['docs']);
44}
45
46ksort($documents);
47
48foreach ($documents as $document) {
49    echo 'Processing: ' . $document['title'] . PHP_EOL;
50    $output = "---
51layout: default
52language: 'en'
53version: '4.0'
54title: '{$document['title']}'
55---
56";
57    foreach ($document['docs'] as $file) {
58        $link   = str_replace(['.zep', DIRECTORY_SEPARATOR], ['', '\\'], $file);
59        $href   = str_replace(['Phalcon\\', '\\'], ['', '-'], strtolower($link));
60        $output .= "
61* [Phalcon\\{$link}](#$href)";
62    }
63
64    $outputDoc = str_replace('\\', '_', $document['title']) . '.md';
65    foreach ($document['docs'] as $file) {
66        echo '    - ' . $file . PHP_EOL;
67        $github = str_replace('\\', '/', $file);
68        $href   = str_replace(['.zep', DIRECTORY_SEPARATOR], ['', '-'], strtolower($file));
69        $file   = $sourceDir . $file;
70
71        $data = processDocument($file);
72
73        $classComment = $data['comment'] ?? '';
74        $namespace    = $data['namespace'] ?? '';
75        $uses         = $data['uses'] ?? '';
76        $signature    = $data['signature'] ?? '';
77        $extends      = $data['extends'] ?? '';
78        $implements   = $data['implements'] ?? '';
79        $constants    = $data['constants'] ?? [];
80        $temp         = $data['properties'] ?? [];
81        $properties   = $temp['properties'] ?? [];
82        $shortcuts    = $temp['shortcuts'] ?? [];
83        $methods      = $data['methods'] ?? [];
84        $methods      = array_merge($shortcuts, $methods);
85        $methods      = orderMethods($methods);
86
87        $output .= "
88
89<h1 id=\"{$href}\">{$signature}</h1>
90
91[Source on GitHub](https://github.com/phalcon/cphalcon/blob/v{{ page.version }}.0/phalcon/{$github})
92";
93
94        if (!empty($namespace)) {
95            $output .= "
96| Namespace  | {$namespace} |";
97        }
98
99        if (!empty($uses)) {
100            $uses   = implode(', ', $uses);
101            $output .= "
102| Uses       | {$uses} |";
103        }
104
105        if (!empty($extends)) {
106            $output .= "
107| Extends    | {$extends} |";
108        }
109
110        if (!empty($implements)) {
111            $implements = implode(', ', $implements);
112            $output     .= "
113| Implements | {$implements} |";
114        }
115
116        $output .= "
117
118{$classComment}
119";
120
121        if (count($constants) > 0) {
122            $constants = implode(PHP_EOL, $constants);
123            $output    .= "
124## Constants
125```php
126{$constants}
127```
128";
129        }
130
131        if (count($properties) > 0) {
132            $properties = implode(PHP_EOL, $properties);
133            $output     .= "
134## Properties
135```php
136{$properties}
137```
138";
139        }
140
141        if (count($methods) > 0) {
142            $elements = [];
143            foreach ($methods as $method) {
144                // Ignore method params lines as they are already in signature
145                $methodComment = preg_replace('/\@(param|return)(.+)\n?/s', '', $method['comment']);
146                $methodComment = trim($methodComment, "\t\n");
147                $methodComment = preg_replace('/^\/\/$/', '', $methodComment);
148
149                $elements[] = '```php' . PHP_EOL
150                    . $method['signature'] . PHP_EOL
151                    . '```' . PHP_EOL
152                    . $methodComment . PHP_EOL . PHP_EOL;
153            }
154            $signature = implode(PHP_EOL, $elements);
155            $output    .= "
156## Methods
157
158{$signature}
159";
160        }
161    }
162
163    $outputDoc = strtolower(str_replace('.zep', '', $outputDoc));
164
165    file_put_contents(
166        $outputDir . $outputDoc,
167        $output
168    );
169}
170
171// API Json
172$api = [];
173foreach ($documents as $document) {
174    $api[] = [
175        'title' => $document['title'],
176        'docs'  => array_map(
177            function ($value) {
178                return str_replace(['.zep', DIRECTORY_SEPARATOR], ['', '\\'], $value);
179            },
180            array_values($document['docs'])
181        ),
182    ];
183}
184
185file_put_contents(
186    $outputDir . 'api.json',
187    json_encode($api, JSON_PRETTY_PRINT)
188);
189
190
191
192
193/**
194 * Read the file and parse it
195 */
196function processDocument(string $file): array
197{
198    $return   = [];
199    $contents = file_get_contents($file);
200    $parse    = zephir_parse_file($contents, '(eval code)');
201
202    foreach ($parse as $item) {
203        $type = $item['type'] ?? '';
204
205        if ('namespace' === $type) {
206            $return['namespace'] = $item['name'];
207
208            continue;
209        }
210
211        if ('comment' === $type) {
212            $return['comment'] = getDocblockMethod($item['value']);
213
214            continue;
215        }
216
217        if ('use' === $type) {
218            $uses    = $return['uses'] ?? [];
219            $aliases = $item['aliases'];
220            foreach ($aliases as $alias) {
221                $uses[] = $alias['name'];
222            }
223
224            $return['uses'] = $uses;
225        }
226
227        if ('class' === $type || 'interface' === $type) {
228            $signature = '';
229            if (1 === ($item['final'] ?? 0)) {
230                $signature .= ' Final';
231            }
232            if (1 === ($item['abstract'] ?? 0)) {
233                $signature .= ' Abstract';
234            }
235
236            $signature           .= ('class' === $type) ? ' Class ' : ' Interface ';
237            $signature           .= $return['namespace'] . '\\' . $item['name'];
238            $return['signature'] = ltrim($signature);
239            //$return['signature'] = ltrim(str_replace('Phalcon\\', '', $signature));
240
241            $return['extends'] = $item['extends'] ?? '';
242            if (true === is_array($return['extends'])) {
243                $return['extends'] = $return['extends'][0]['value'];
244            }
245
246            $implements = $item['implements'] ?? [];
247            if (count($implements) > 0) {
248                foreach ($implements as $implement) {
249                    $return['implements'][] = $implement['value'];
250                }
251            }
252
253            $definition           = $item['definition'] ?? [];
254            $return['constants']  = parseConstants($definition);
255            $return['properties'] = parseProperties($definition);
256            $return['methods']    = parseMethods($definition);
257        }
258    }
259
260    return $return;
261}
262
263function parseConstants(array $item): array
264{
265    $constants = $item['constants'] ?? [];
266    $return    = [];
267    foreach ($constants as $constant) {
268        if ('const' === $constant['type']) {
269            $signature = 'const ' . $constant['name'];
270            if (isset($constant['default']['value'])) {
271                $signature .= ' = ' . $constant['default']['value'];
272            }
273
274            $return[$constant['name']] = $signature . ';';
275        }
276    }
277
278    ksort($return);
279
280    return $return;
281}
282
283function parseMethods(array $item): array
284{
285    $methods = $item['methods'] ?? [];
286    $return  = [];
287
288    foreach ($methods as $method) {
289        $line = $method['docblock'] ?? '';
290        $line = getDocblockMethod($line);
291
292        $visibility = $method['visibility'] ?? [];
293        $signature  = implode(' ', $visibility);
294        $signature .= ' function ' . $method['name'] . '(';
295
296        $params  = $method['parameters'] ?? [];
297        $counter = 1;
298        $count   = count($params);
299        foreach ($params as $param) {
300            if ('parameter' === $param['type']) {
301                $cast = $param['cast'] ?? [];
302                if (count($cast) > 0) {
303                    $type = transformType($cast['type'], $cast['value']);
304                } else {
305                    $type = transformType($param['data-type']);
306                }
307                $signature .= ' ' . $type . ' $' . $param['name'];
308
309
310                // Default value
311                $retVal = $param['default']['type'] ?? '';
312                $retVal = transformType($retVal);
313                if (!empty($retVal)) {
314                    $signature .= ' = ' . $retVal;
315                }
316
317                if ($counter < $count) {
318                    $signature .= ',';
319                }
320
321                $counter++;
322            }
323        }
324
325        $signature .= ($count > 0 ? ' ' : '') . ')';
326
327        // Return
328        $retType = $method['return-type'] ?? [];
329        if (1 === ($retType['void'] ?? 0)) {
330            $signature .= ': void';
331        } else {
332            $list = $retType['list'] ?? [];
333            if (count($list) > 0) {
334                $retTypes = [];
335                foreach ($list as $li) {
336                    $cast = $li['cast'] ?? [];
337                    if (count($cast) > 0) {
338                        $rt = transformType($cast['type'], $cast['value']);
339                        if (1 === $li['collection']) {
340                            $rt .= '[]';
341                        }
342
343                        $retTypes[] = $rt;
344                    } else {
345                        $retTypes[] = transformType($li['data-type']);
346                    }
347                }
348
349                $signature .= ': ' . implode(' | ', $retTypes);
350            }
351        }
352
353        $return[$method['name']] = [
354            'comment'   => $line,
355            'signature' => ltrim($signature) . ';',
356        ];
357    }
358
359    return $return;
360}
361
362function parseProperties(array $item): array
363{
364    $properties = $item['properties'] ?? [];
365    $return     = [];
366    $sigReturn  = [];
367
368    foreach ($properties as $property) {
369        $line = $property['docblock'] ?? '';
370        $line = getDocblock($line);
371
372        $signature = '';
373
374        $visibility = $property['visibility'] ?? [];
375        foreach ($visibility as $vis) {
376            $signature .= ' ' . $vis;
377        }
378
379        $signature .= ' ' . $property['name'];
380
381        if (isset($property['default']['value'])) {
382            $signature .= ' = ' . $property['default']['value'];
383        }
384
385        $retVal = '';
386        $temp   = explode(PHP_EOL, $line);
387        foreach ($temp as $li) {
388            if (strpos($li, '@var') > 0) {
389                $retVal = str_replace(' * @var ', '', $li);
390
391                break;
392            }
393        }
394
395        $shortcuts = $property['shortcuts'] ?? [];
396        foreach ($shortcuts as $shortcut) {
397            $stub = $shortcut['name'];
398            if ('get' === $stub) {
399                $name             = 'get' . ucfirst($property['name']);
400                $sigReturn[$name] = [
401                    'comment'   => '',
402                    'signature' => 'public function ' . $name .
403                        '()' . (!empty($retVal) ? ': ' . $retVal : ''),
404                ];
405            } elseif ('set' === $stub) {
406                $name             = 'set' . ucfirst($property['name']);
407                $sigReturn[$name] = [
408                    'comment'   => '',
409                    'signature' => 'public function ' . $name .
410                        '( ' . (!empty($retVal) ? $retVal . ' ' : '') .
411                        '$' . $property['name'] .
412                        ' )',
413                ];
414            } elseif ('__toString' === $stub) {
415                $sigReturn['__toString'] = [
416                    'comment'   => '',
417                    'signature' => 'public function __toString(): string',
418                ];
419            }
420        }
421
422        $return[$property['name']] = $line . PHP_EOL .
423            ltrim($signature) . ';' . PHP_EOL;
424    }
425
426    return [
427        'properties' => $return,
428        'shortcuts'  => $sigReturn,
429    ];
430}
431
432function getDocblockMethod(string $source): string
433{
434    $doc = getDocblock($source);
435
436    return str_replace(
437        [
438            '/**' . PHP_EOL,
439            '/**',
440            ' */' . PHP_EOL,
441            ' */',
442            ' * ',
443            ' *',
444        ],
445        [
446            '',
447            '',
448            '',
449            '',
450            '',
451            '',
452        ],
453        $doc
454    );
455}
456
457function getDocblock(string $source): string
458{
459    $linesArray = [];
460    $lines      = explode("\n", trim($source));
461
462    foreach ($lines as $line) {
463        $linesArray[] = str_replace(
464            [
465                '    /*',
466                '     *',
467            ],
468            [
469                '/*',
470                ' *',
471            ],
472            $line
473        );
474    }
475
476    $doc = implode(PHP_EOL, $linesArray);
477
478    return '/' . $doc . '/';
479}
480
481function orderMethods(array $methods): array
482{
483    $public    = [];
484    $reserved  = [];
485    $protected = [];
486
487    foreach ($methods as $name => $method) {
488        if (substr($name, 0, 2) === '__') {
489            $reserved[$name] = $method;
490
491            continue;
492        }
493
494        if (strpos($method['signature'], 'public function') !== false ||
495            strpos($method['signature'], 'public static function') !== false) {
496            $public[$name] = $method;
497
498            continue;
499        }
500
501        if (strpos($method['signature'], 'protected function') !== false ||
502            strpos($method['signature'], 'protected static function') !== false) {
503            $protected[$name] = $method;
504
505            continue;
506        }
507    }
508
509    ksort($reserved);
510    ksort($public);
511    ksort($protected);
512
513    return array_merge($reserved, $public, $protected);
514}
515
516function transformType(string $type, string $value = ''): string
517{
518    switch ($type) {
519        case 'variable':
520            if (empty($value)) {
521                return 'mixed';
522            } else {
523                return $value;
524            }
525        case 'empty-array':
526            return '[]';
527        default:
528            return $type;
529    }
530}
531