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