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