Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.00% covered (success)
97.00%
97 / 100
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReportsCommand
97.00% covered (success)
97.00%
97 / 100
66.67% covered (warning)
66.67%
2 / 3
22
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 configure
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
1
 execute
95.31% covered (success)
95.31%
61 / 64
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Console\Command;
21
22use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
23use FastForward\DevTools\Console\Input\HasCacheOption;
24use FastForward\DevTools\Console\Input\HasJsonOption;
25use FastForward\DevTools\Path\DevToolsPathResolver;
26use FastForward\DevTools\Process\ProcessBuilderInterface;
27use FastForward\DevTools\Process\ProcessQueueInterface;
28use FastForward\DevTools\Path\ManagedWorkspace;
29use Psr\Log\LoggerInterface;
30use Symfony\Component\Console\Attribute\AsCommand;
31use Symfony\Component\Console\Command\Command;
32use Symfony\Component\Console\Input\InputInterface;
33use Symfony\Component\Console\Input\InputOption;
34use Symfony\Component\Console\Output\BufferedOutput;
35use Symfony\Component\Console\Output\OutputInterface;
36
37/**
38 * Coordinates the generation of Fast Forward documentation frontpage and related reports.
39 * This class MUST NOT be overridden and SHALL securely combine docs and testing commands.
40 */
41#[AsCommand(
42    name: 'standards:reports',
43    description: 'Generates the frontpage for Fast Forward documentation.',
44    aliases: ['reports'],
45)]
46 class ReportsCommand extends Command
47{
48    use HasCacheOption;
49    use HasJsonOption;
50    use LogsCommandResults;
51
52    /**
53     * Initializes the command with required dependencies.
54     *
55     * @param ProcessBuilderInterface $processBuilder the builder instance used to construct execution processes
56     * @param ProcessQueueInterface $processQueue the execution queue mechanism for running sub-processes
57     * @param LoggerInterface $logger the output-aware logger
58     */
59    public function __construct(
60        private  ProcessBuilderInterface $processBuilder,
61        private  ProcessQueueInterface $processQueue,
62        private  LoggerInterface $logger,
63    ) {
64        parent::__construct();
65    }
66
67    /**
68     * @return void
69     */
70    protected function configure(): void
71    {
72        $this->setHelp(
73            'This command generates the frontpage for Fast Forward documentation, including links to API'
74            . ' documentation and test reports.'
75        );
76        $this
77            ->addJsonOption()
78            ->addCacheOption('Whether to enable cache writes in nested docs and tests commands.')
79            ->addCacheDirOption(
80                description: 'Base cache directory used for nested docs and tests command caches.',
81                default: ManagedWorkspace::getCacheDirectory('reports'),
82            )
83            ->addOption(
84                name: 'progress',
85                mode: InputOption::VALUE_NONE,
86                description: 'Whether to enable progress output from generated report commands.',
87            )
88            ->addOption(
89                name: 'target',
90                mode: InputOption::VALUE_OPTIONAL,
91                description: 'The target directory for the generated reports.',
92                default: ManagedWorkspace::getOutputDirectory(),
93            )
94            ->addOption(
95                name: 'coverage',
96                shortcut: 'c',
97                mode: InputOption::VALUE_OPTIONAL,
98                description: 'The target directory for the generated test coverage report.',
99                default: ManagedWorkspace::getOutputDirectory(ManagedWorkspace::COVERAGE),
100            )
101            ->addOption(
102                name: 'metrics',
103                mode: InputOption::VALUE_OPTIONAL,
104                description: 'Generate code metrics and optionally choose the HTML output directory.',
105                default: ManagedWorkspace::getOutputDirectory(ManagedWorkspace::METRICS),
106            );
107    }
108
109    /**
110     * Executes the generation logic for diverse reports.
111     *
112     * The method MUST run the underlying `docs` and `tests` commands. It SHALL process
113     * and generate the frontpage output file successfully.
114     *
115     * @param InputInterface $input the structured inputs holding specific arguments
116     * @param OutputInterface $output the designated output interface
117     *
118     * @return int the integer outcome from the base process execution
119     */
120    protected function execute(InputInterface $input, OutputInterface $output): int
121    {
122        $jsonOutput = $this->isJsonOutput($input);
123        $prettyJsonOutput = $this->isPrettyJsonOutput($input);
124        $progress = ! $jsonOutput && (bool) $input->getOption('progress');
125        $processOutput = $jsonOutput ? new BufferedOutput() : $output;
126        $cacheArgument = $this->resolveCacheArgument($input);
127        $cacheDirEnabled = '--no-cache' !== $cacheArgument;
128        $target = (string) $input->getOption('target');
129        $coveragePath = (string) $input->getOption('coverage');
130        $metricsPath = (string) $input->getOption('metrics');
131
132        $this->logger->info('Generating frontpage for Fast Forward documentation...', [
133            'input' => $input,
134        ]);
135
136        $docsBuilder = $this->processBuilder
137            ->withArgument('--target', $target);
138
139        $coverageBuilder = $this->processBuilder
140            ->withArgument('--coverage-summary')
141            ->withArgument('--coverage', $coveragePath);
142
143        $metricsBuilder = $this->processBuilder
144            ->withArgument('--junit', $coveragePath . '/junit.xml')
145            ->withArgument('--target', $metricsPath);
146
147        if (! $jsonOutput) {
148            $docsBuilder = $docsBuilder->withArgument('--ansi');
149            $coverageBuilder = $coverageBuilder->withArgument('--ansi');
150            $metricsBuilder = $metricsBuilder->withArgument('--ansi');
151        }
152
153        if (null !== $cacheArgument) {
154            $docsBuilder = $docsBuilder->withArgument($cacheArgument);
155        }
156
157        if ($cacheDirEnabled && null !== $docsCacheDir = $this->resolveCacheDirArgument($input, 'docs')) {
158            $docsBuilder = $docsBuilder->withArgument('--cache-dir', $docsCacheDir);
159        }
160
161        if ($progress) {
162            $docsBuilder = $docsBuilder->withArgument('--progress');
163        }
164
165        if ($jsonOutput) {
166            $docsBuilder = $docsBuilder->withArgument('--json');
167        }
168
169        if ($prettyJsonOutput) {
170            $docsBuilder = $docsBuilder->withArgument('--pretty-json');
171        }
172
173        $docs = $docsBuilder->build(DevToolsPathResolver::getBinaryCommand('docs'));
174
175        if (null !== $cacheArgument) {
176            $coverageBuilder = $coverageBuilder->withArgument($cacheArgument);
177        }
178
179        if ($cacheDirEnabled && null !== $testsCacheDir = $this->resolveCacheDirArgument($input, 'tests')) {
180            $coverageBuilder = $coverageBuilder->withArgument('--cache-dir', $testsCacheDir);
181        }
182
183        if ($progress) {
184            $coverageBuilder = $coverageBuilder->withArgument('--progress');
185        }
186
187        if ($jsonOutput) {
188            $coverageBuilder = $coverageBuilder->withArgument('--json');
189        }
190
191        if ($prettyJsonOutput) {
192            $coverageBuilder = $coverageBuilder->withArgument('--pretty-json');
193        }
194
195        $coverage = $coverageBuilder->build(DevToolsPathResolver::getBinaryCommand('tests'));
196
197        if ($progress) {
198            $metricsBuilder = $metricsBuilder->withArgument('--progress');
199        }
200
201        if ($jsonOutput) {
202            $metricsBuilder = $metricsBuilder->withArgument('--json');
203        }
204
205        if ($prettyJsonOutput) {
206            $metricsBuilder = $metricsBuilder->withArgument('--pretty-json');
207        }
208
209        $metrics = $metricsBuilder->build(DevToolsPathResolver::getBinaryCommand('metrics'));
210
211        $this->processQueue->add(process: $docs, detached: true, label: 'Generating API Docs Report');
212        $this->processQueue->add(process: $coverage, label: 'Generating Coverage Report');
213        $this->processQueue->add(process: $metrics, label: 'Generating Metrics Report');
214
215        $result = $this->processQueue->run($processOutput);
216
217        if (self::SUCCESS === $result) {
218            return $this->success('Documentation reports generated successfully.', $input, [
219                'output' => $processOutput,
220            ]);
221        }
222
223        return $this->failure('Documentation reports generation failed.', $input, [
224            'output' => $processOutput,
225        ]);
226    }
227}