252 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
		
		
			
		
	
	
			252 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Plaintext
		
	
	
|  | #!/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); |