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);
|