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