Skip to content

Commit b2fde19

Browse files
committed
Refactor GenerateCommand and GenerateInput to improve bootstrap handling and integrate SymfonyStyle for enhanced user feedback
1 parent 72d3034 commit b2fde19

3 files changed

Lines changed: 88 additions & 55 deletions

File tree

bin/openapi

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,26 @@ if (class_exists(Generator::class) === false) {
1818
}
1919
}
2020

21-
$input = new ArgvInput();
21+
$input = new class extends ArgvInput
22+
{
23+
public function hasParameterOption(array|string $values, bool $onlyParams = false): bool
24+
{
25+
// Skip the built-in version option check
26+
// thus the command can use it for its own purpose
27+
if (['--version', '-V'] === $values) {
28+
return false;
29+
}
30+
31+
return parent::hasParameterOption($values, $onlyParams);
32+
}
33+
};
2234
$output = new ConsoleOutput();
2335
$logger = new ConsoleLogger($output);
2436

2537
$dispatcher = new EventDispatcher();
2638
$dispatcher->addSubscriber(new ErrorListener($logger));
2739

28-
$app = new Application('openapi');
40+
$app = new Application();
2941
$app->setDispatcher($dispatcher);
3042

3143
// Remove Symfony's built-in options that conflict with our command options:
@@ -36,7 +48,6 @@ $options = $definition->getOptions();
3648
unset($options['version'], $options['no-interaction']);
3749
$definition->setOptions($options);
3850

39-
$command = new GenerateCommand($logger);
40-
$app->addCommand($command);
41-
$app->setDefaultCommand('generate', true);
51+
$app->addCommand(new GenerateCommand($logger));
52+
$app->setDefaultCommand('openapi', true);
4253
$app->run($input, $output);

src/Console/GenerateCommand.php

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,60 +9,66 @@
99
use OpenApi\Analysers\AttributeAnnotationFactory;
1010
use OpenApi\Analysers\DocBlockAnnotationFactory;
1111
use OpenApi\Analysers\ReflectionAnalyser;
12+
use OpenApi\Annotations\OpenApi;
1213
use OpenApi\Generator;
1314
use OpenApi\SourceFinder;
1415
use Symfony\Component\Console\Attribute\AsCommand;
1516
use Symfony\Component\Console\Attribute\MapInput;
1617
use Symfony\Component\Console\Logger\ConsoleLogger;
18+
use Symfony\Component\Console\Style\SymfonyStyle;
1719

1820
#[AsCommand(
19-
name: 'generate',
21+
name: 'openapi',
2022
description: 'Generate OpenAPI documentation',
2123
)]
22-
final class GenerateCommand
24+
class GenerateCommand
2325
{
2426
public function __construct(
2527
private ConsoleLogger $logger,
2628
) {
2729
}
2830

29-
public function __invoke(#[MapInput] GenerateInput $input): int
31+
public function __invoke(#[MapInput] GenerateInput $input, SymfonyStyle $io): int
3032
{
31-
// Bootstrap
32-
foreach ($input->bootstrap as $bootstrapPattern) {
33-
$filenames = glob($bootstrapPattern);
34-
if (false === $filenames) {
35-
$this->logger->error('Invalid `--bootstrap` value: "' . $bootstrapPattern . '"');
36-
37-
return 1;
38-
}
39-
foreach ($filenames as $filename) {
40-
if ($input->debug) {
41-
$this->logger->debug('Bootstrapping: ' . $filename);
42-
}
43-
require_once($filename);
33+
foreach ($input->getBootstrapFilenames() as $filename) {
34+
if ($io->isVerbose()) {
35+
$io->info('Bootstrapping: ' . $filename);
4436
}
37+
38+
require_once($filename);
4539
}
4640

47-
// Defaults
4841
if ($input->defaults) {
49-
$this->logger->info('Default config');
50-
$this->logger->info(json_encode((new Generator())->getDefaultConfig(), JSON_PRETTY_PRINT));
42+
$io->title('Default config');
43+
$io->writeln(json_encode((new Generator())->getDefaultConfig(), JSON_PRETTY_PRINT));
5144

52-
return 1;
45+
return 0;
5346
}
5447

55-
// Exclude with comma-deprecation check
56-
$exclude = $input->exclude ?: null;
57-
if ($exclude && str_contains((string) $exclude[0], ',')) {
58-
$exploded = explode(',', (string) $exclude[0]);
59-
$this->logger->error('Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude ' . $exploded[0] . ' --exclude ' . $exploded[1]);
60-
$exclude[0] = array_shift($exploded);
61-
$exclude = array_merge($exclude, $exploded);
48+
$openapi = $this->generate($input);
49+
50+
if (!$input->output) {
51+
if (strtolower($input->format) === 'json') {
52+
echo $openapi->toJson();
53+
} else {
54+
echo $openapi->toYaml();
55+
}
56+
echo "\n";
57+
} else {
58+
$outputPath = $input->output;
59+
if (is_dir($outputPath)) {
60+
$outputPath .= '/openapi.yaml';
61+
}
62+
$openapi->saveAs($outputPath, $input->format);
6263
}
6364

64-
// Generator
65+
return $this->logger->hasErrored() ? 1 : 0;
66+
}
67+
68+
private function generate(GenerateInput $input): OpenApi
69+
{
6570
$generator = new Generator($this->logger);
71+
6672
foreach ($input->addProcessor as $processor) {
6773
$class = '\OpenApi\Processors\\' . ucfirst((string) $processor);
6874
if (class_exists($class)) {
@@ -72,43 +78,45 @@ public function __invoke(#[MapInput] GenerateInput $input): int
7278
}
7379
$generator->getProcessorPipeline()->add($processor);
7480
}
81+
7582
foreach ($input->removeProcessor as $processor) {
7683
$class = class_exists($processor)
77-
? $class
84+
? $processor
7885
: '\OpenApi\Processors\\' . ucfirst((string) $processor);
7986
$generator->getProcessorPipeline()->remove($class);
8087
}
8188

82-
// Analyser
8389
$analyser = new ReflectionAnalyser([
8490
new AttributeAnnotationFactory(),
8591
new DocBlockAnnotationFactory(),
8692
]);
8793
$analyser->setGenerator($generator);
8894

89-
// Generate
90-
$openapi = $generator
95+
return $generator
9196
->setVersion($input->version)
9297
->setConfig($input->config)
9398
->setAnalyser($analyser)
94-
->generate(new SourceFinder($input->paths, $exclude, $input->pattern));
99+
->generate(new SourceFinder($input->paths, $this->normalizeExcludePaths($input->exclude), $input->pattern));
100+
}
95101

96-
// Output
97-
if (!$input->output) {
98-
if (strtolower($input->format) === 'json') {
99-
echo $openapi->toJson();
100-
} else {
101-
echo $openapi->toYaml();
102-
}
103-
echo "\n";
104-
} else {
105-
$outputPath = $input->output;
106-
if (is_dir($outputPath)) {
107-
$outputPath .= '/openapi.yaml';
108-
}
109-
$openapi->saveAs($outputPath, $input->format);
102+
/**
103+
* @param list<string> $exclude
104+
*
105+
* @return list<string>
106+
*/
107+
private function normalizeExcludePaths(array $exclude): array
108+
{
109+
if (!$exclude) {
110+
return [];
110111
}
111112

112-
return $this->logger->hasErrored() ? 1 : 0;
113+
if (str_contains($exclude[0], ',')) {
114+
$exploded = explode(',', $exclude[0]);
115+
$this->logger->error('Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude ' . $exploded[0] . ' --exclude ' . $exploded[1]);
116+
$exclude[0] = array_shift($exploded);
117+
$exclude = array_merge($exclude, $exploded);
118+
}
119+
120+
return $exclude;
113121
}
114122
}

src/Console/GenerateInput.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use OpenApi\Annotations\OpenApi;
1010
use Symfony\Component\Console\Attribute\Argument;
1111
use Symfony\Component\Console\Attribute\Option;
12+
use Symfony\Component\Console\Exception\InvalidArgumentException;
1213

1314
class GenerateInput
1415
{
@@ -45,6 +46,19 @@ class GenerateInput
4546
#[Option('The OpenAPI version')]
4647
public string $version = OpenApi::DEFAULT_VERSION;
4748

48-
#[Option('Show additional error information', shortcut: 'd')]
49-
public bool $debug = false;
49+
/**
50+
* @return iterable<string>
51+
*/
52+
public function getBootstrapFilenames(): iterable
53+
{
54+
foreach ($this->bootstrap as $bootstrapPattern) {
55+
$filenames = glob($bootstrapPattern);
56+
57+
if (!$filenames) {
58+
throw new InvalidArgumentException('Invalid `--bootstrap` value: "' . $bootstrapPattern . '"');
59+
}
60+
61+
yield from $filenames;
62+
}
63+
}
5064
}

0 commit comments

Comments
 (0)