252 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			PHP
		
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			PHP
		
	
	
| #!/usr/bin/env php
 | |
| <?php
 | |
| use OpenApi\Logger;
 | |
| use OpenApi\Analysis;
 | |
| use const OpenApi\UNDEFINED;
 | |
| use const OpenApi\COLOR_RED;
 | |
| use const OpenApi\COLOR_YELLOW;
 | |
| use const OpenApi\COLOR_STOP;
 | |
| 
 | |
| error_reporting(E_ALL);
 | |
| // Possible options and their default values.
 | |
| 
 | |
| $options = [
 | |
|     'output' => false,
 | |
|     'format' => 'auto',
 | |
|     'exclude' => [],
 | |
|     'pattern' => '*.php',
 | |
|     'bootstrap' => false,
 | |
|     'help' => false,
 | |
|     'debug' => false,
 | |
|     'processor' => [],
 | |
| ];
 | |
| $aliases = [
 | |
|     'o' => 'output',
 | |
|     'e' => 'exclude',
 | |
|     'n' => 'pattern',
 | |
|     'b' => 'bootstrap',
 | |
|     'h' => 'help',
 | |
|     'd' => 'debug',
 | |
|     'p' => 'processor',
 | |
|     'f' => 'format'
 | |
| ];
 | |
| $needsArgument = [
 | |
|     'output',
 | |
|     'format',
 | |
|     'exclude',
 | |
|     'pattern',
 | |
|     'bootstrap',
 | |
|     'processor',
 | |
| ];
 | |
| $paths = [];
 | |
| $error = false;
 | |
| define('OpenApi\COLOR_RED', "\033[31m");
 | |
| define('OpenApi\COLOR_YELLOW', "\033[33m");
 | |
| define('OpenApi\COLOR_STOP', "\033[0m");
 | |
| 
 | |
| try {
 | |
|     // Parse cli arguments
 | |
|     for ($i = 1; $i < $argc; $i++) {
 | |
|         $arg = $argv[$i];
 | |
|         if (substr($arg, 0, 2) === '--') { // longopt
 | |
|             $option = substr($arg, 2);
 | |
|         } elseif ($arg[0] === '-') { // shortopt
 | |
|             if (array_key_exists(substr($arg, 1), $aliases)) {
 | |
|                 $option = $aliases[$arg[1]];
 | |
|             } else {
 | |
|                 throw new Exception('Unknown option: "' . $arg . '"');
 | |
|             }
 | |
|         } else {
 | |
|             $paths[] = $arg;
 | |
|             continue;
 | |
|         }
 | |
|         if (array_key_exists($option, $options) === false) {
 | |
|             throw new Exception('Unknown option: "' . $arg . '"');
 | |
|         }
 | |
|         if (in_array($option, $needsArgument)) {
 | |
|             if (empty($argv[$i + 1]) || $argv[$i + 1][0] === '-') {
 | |
|                 throw new Exception('Missing argument for "' . $arg . '"');
 | |
|             }
 | |
|             if (is_array($options[$option])) {
 | |
|                 $options[$option][] = $argv[$i + 1];
 | |
|             } else {
 | |
|                 $options[$option] = $argv[$i + 1];
 | |
|             }
 | |
|             $i++;
 | |
|         } else {
 | |
|             $options[$option] = true;
 | |
|         }
 | |
|     }
 | |
| } catch (Exception $e) {
 | |
|     $error = $e->getMessage();
 | |
| }
 | |
| 
 | |
| if (!$error && $options['bootstrap']) {
 | |
|     if (is_readable($options['bootstrap']) === false) {
 | |
|         $error = 'Invalid `--bootstrap` value: "'.$options['bootstrap'].'"';
 | |
|     } else {
 | |
|         require_once($options['bootstrap']);
 | |
|     }
 | |
| }
 | |
| if (count($paths) === 0) {
 | |
|     $error = 'Specify at least one path.';
 | |
| }
 | |
| if ($options['help'] === false && $error) {
 | |
|     error_log('');
 | |
|     error_log(COLOR_RED.'Error: '.$error.COLOR_STOP);
 | |
|     $options['help'] = true; // Show help
 | |
| }
 | |
| if ($options['help']) {
 | |
|     $help = <<<EOF
 | |
| 
 | |
| Usage: openapi [--option value] [/path/to/project ...]
 | |
| 
 | |
| Options:
 | |
|   --output (-o)     Path to store the generated documentation.
 | |
|                     ex: --output openapi.yaml
 | |
|   --exclude (-e)    Exclude path(s).
 | |
|                     ex: --exclude vendor,library/Zend
 | |
|   --pattern (-n)    Pattern of files to scan.
 | |
|                     ex: --pattern "*.php" or --pattern "/\.(phps|php)$/"
 | |
|   --bootstrap (-b)  Bootstrap a php file for defining constants, etc.
 | |
|                     ex: --bootstrap config/constants.php
 | |
|   --processor       Register an additional processor.
 | |
|   --format          Force yaml or json.
 | |
|   --debug           Show additional error information.
 | |
|   --help (-h)       Display this help message.
 | |
| 
 | |
| 
 | |
| EOF;
 | |
|     error_log($help);
 | |
|     exit(1);
 | |
| }
 | |
| 
 | |
| if (class_exists(Logger::class) === false) {
 | |
|     if (file_exists(__DIR__.'/../vendor/autoload.php')) {  // cloned / dev environment?
 | |
|         require_once(__DIR__.'/../vendor/autoload.php');
 | |
|     } else {
 | |
|         require_once(realpath(__DIR__.'/../../../').'/autoload.php');
 | |
|     }
 | |
| }
 | |
| $errorTypes = [
 | |
|     E_ERROR => 'Error',
 | |
|     E_WARNING => 'Warning',
 | |
|     E_PARSE => 'Parser error',
 | |
|     E_NOTICE => 'Notice',
 | |
|     E_STRICT => 'Strict',
 | |
|     E_DEPRECATED => 'Deprecated',
 | |
|     E_CORE_ERROR => 'Error(Core)',
 | |
|     E_CORE_WARNING => 'Warning(Core)',
 | |
|     E_COMPILE_ERROR => 'Error(compile)',
 | |
|     E_COMPILE_WARNING => 'Warning(Compile)',
 | |
|     E_RECOVERABLE_ERROR => 'Error(Recoverable)',
 | |
|     E_USER_ERROR => 'Error',
 | |
|     E_USER_WARNING => 'Warning',
 | |
|     E_USER_NOTICE => 'Notice',
 | |
|     E_USER_DEPRECATED => 'Deprecated',
 | |
| ];
 | |
| set_error_handler(function ($errno, $errstr, $file, $line) use ($errorTypes, $options) {
 | |
|     if (!(error_reporting() & $errno)) {
 | |
|         return; // This error code is not included in error_reporting
 | |
|     }
 | |
|     $type = array_key_exists($errno, $errorTypes) ? $errorTypes[$errno] : 'Error';
 | |
|     $color = (substr($type, 0, 5) === 'Error') ? COLOR_RED: COLOR_YELLOW;
 | |
|     error_log(COLOR_RED.$type. ': '.$errstr.COLOR_STOP);
 | |
|     if ($options['debug']) {
 | |
|         error_log(' in '.$file.' on line '.$line);
 | |
|     }
 | |
|     if (substr($type, 0, 5) === 'Error') {
 | |
|         exit($errno);
 | |
|     }
 | |
| });
 | |
| set_exception_handler(function ($exception) use ($options) {
 | |
|     if ($options['debug']) {
 | |
|         error_log($exception);
 | |
|     } else {
 | |
|         error_log(COLOR_RED.'Exception: '.$exception->getMessage().COLOR_STOP);
 | |
|         // if ($options['debug']) {
 | |
|         //     error_log(' in '.$exception->getFile().' on line '.$exception->getLine());
 | |
|         // }
 | |
|     }
 | |
|     exit($exception->getCode() ?: 1);
 | |
| });
 | |
| $exit = 0;
 | |
| Logger::getInstance()->log = function ($entry, $type) use ($options, &$exit) {
 | |
|     $exit = 1;
 | |
|     if ($type === E_USER_NOTICE) {
 | |
|         $type = '';
 | |
|         $color = COLOR_YELLOW;
 | |
|     } else {
 | |
|         $type = 'Warning: ';
 | |
|         $color = COLOR_RED;
 | |
|     }
 | |
|     if ($entry instanceof Exception) {
 | |
|         error_log(COLOR_RED."Error: " . $entry->getMessage().COLOR_STOP);
 | |
|         if ($options['debug']) {
 | |
|             error_log('Stack trace:'.PHP_EOL.$entry->getTraceAsString());
 | |
|         }
 | |
|     } else {
 | |
|         error_log($color. $type . $entry.COLOR_STOP);
 | |
|         if ($options['debug']) {
 | |
|             // Show backtrace in debug mode
 | |
|             $e = (string)(new Exception('trace'));
 | |
|             $trace = explode("\n", substr($e, strpos($e, 'Stack trace:')));
 | |
|             foreach ($trace as $i => $entry) {
 | |
|                 if ($i === 0) {
 | |
|                     error_log($entry);
 | |
|                 }
 | |
|                 if ($i <= 3) {
 | |
|                     continue;
 | |
|                 }
 | |
|                 preg_match('/#([0-9]+) (.*)$/', $entry, $match);
 | |
|                 error_log('#' .($match[1] - 2).' '.$match[2]);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| };
 | |
| $exclude = null;
 | |
| if ($options['exclude']) {
 | |
|     $exclude = $options['exclude'];
 | |
|     if (strpos($exclude[0], ',') !== false) {
 | |
|         $exploded = explode(',', $exclude[0]);
 | |
|         error_log(COLOR_RED.'Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude '.$exploded[0].' --exclude '.$exploded[1]).COLOR_STOP;
 | |
|         $exclude[0] = array_shift($exploded);
 | |
|         $exclude = array_merge($exclude, $exploded);
 | |
|     }
 | |
| }
 | |
| 
 | |
| $pattern = "*.php";
 | |
| if ($options['pattern']) {
 | |
|     $pattern = $options['pattern'];
 | |
| }
 | |
| 
 | |
| foreach ($options["processor"] as $processor) {
 | |
|     $class = '\OpenApi\Processors\\'.$processor;
 | |
|     if (class_exists($class)) {
 | |
|         $processor = new $class();
 | |
|     } elseif (class_exists($processor)) {
 | |
|         $processor = new $processor();
 | |
|     }
 | |
|     Analysis::registerProcessor($processor);
 | |
| }
 | |
| 
 | |
| $openapi = OpenApi\Generator::scan(OpenApi\Util::finder($paths, $exclude, $pattern));
 | |
| 
 | |
| if ($exit !== 0) {
 | |
|     error_log('');
 | |
| }
 | |
| if ($options['output'] === false) {
 | |
|     if (strtolower($options['format']) === 'json') {
 | |
|         echo $openapi->toJson();
 | |
|     } else {
 | |
|         echo $openapi->toYaml();
 | |
|     }
 | |
|     echo "\n";
 | |
| } else {
 | |
|     if (is_dir($options['output'])) {
 | |
|         $options['output'] .= '/openapi.yaml';
 | |
|     }
 | |
|     $openapi->saveAs($options['output'], $options['format']);
 | |
| }
 | |
| exit($exit);
 |