Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.14% covered (success)
97.14%
102 / 105
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReportsCommand
97.14% covered (success)
97.14%
102 / 105
66.67% covered (warning)
66.67%
2 / 3
23
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.65% covered (success)
95.65%
66 / 69
0.00% covered (danger)
0.00%
0 / 1
21
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\Project\ProjectCapabilitiesResolverInterface;
27use FastForward\DevTools\Process\ProcessBuilderInterface;
28use FastForward\DevTools\Process\ProcessQueueInterface;
29use FastForward\DevTools\Path\ManagedWorkspace;
30use Psr\Log\LogLevel;
31use Symfony\Component\Console\Attribute\AsCommand;
32use Symfony\Component\Console\Command\Command;
33use Symfony\Component\Console\Input\InputInterface;
34use Symfony\Component\Console\Input\InputOption;
35use Symfony\Component\Console\Output\BufferedOutput;
36use Symfony\Component\Console\Output\OutputInterface;
37
38/**
39 * Coordinates documentation, coverage, and metrics report generation.
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 ProjectCapabilitiesResolverInterface $projectCapabilitiesResolver the resolver for project capability detection
58     */
59    public function __construct(
60        private  ProcessBuilderInterface $processBuilder,
61        private  ProcessQueueInterface $processQueue,
62        private  ProjectCapabilitiesResolverInterface $projectCapabilitiesResolver,
63    ) {
64        parent::__construct();
65    }
66
67    /**
68     * Configures the report generation options.
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` command and, when applicable,
113     * the `tests` command for coverage generation.
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->log('Generating frontpage for Fast Forward documentation...', $input);
133
134        $docsBuilder = $this->processBuilder
135            ->withArgument('--target', $target);
136
137        $coverageBuilder = $this->processBuilder
138            ->withArgument('--coverage-summary')
139            ->withArgument('--coverage', $coveragePath);
140
141        $metricsBuilder = $this->processBuilder
142            ->withArgument('--junit', $coveragePath . '/junit.xml')
143            ->withArgument('--target', $metricsPath);
144
145        if (! $jsonOutput) {
146            $docsBuilder = $docsBuilder->withArgument('--ansi');
147            $coverageBuilder = $coverageBuilder->withArgument('--ansi');
148            $metricsBuilder = $metricsBuilder->withArgument('--ansi');
149        }
150
151        if (null !== $cacheArgument) {
152            $docsBuilder = $docsBuilder->withArgument($cacheArgument);
153        }
154
155        if ($cacheDirEnabled && null !== $docsCacheDir = $this->resolveCacheDirArgument($input, 'docs')) {
156            $docsBuilder = $docsBuilder->withArgument('--cache-dir', $docsCacheDir);
157        }
158
159        if ($progress) {
160            $docsBuilder = $docsBuilder->withArgument('--progress');
161        }
162
163        if ($jsonOutput) {
164            $docsBuilder = $docsBuilder->withArgument('--json');
165        }
166
167        if ($prettyJsonOutput) {
168            $docsBuilder = $docsBuilder->withArgument('--pretty-json');
169        }
170
171        $docs = $docsBuilder->build([DevToolsPathResolver::getBinaryPath(), 'docs']);
172
173        if (null !== $cacheArgument) {
174            $coverageBuilder = $coverageBuilder->withArgument($cacheArgument);
175        }
176
177        if ($cacheDirEnabled && null !== $testsCacheDir = $this->resolveCacheDirArgument($input, 'tests')) {
178            $coverageBuilder = $coverageBuilder->withArgument('--cache-dir', $testsCacheDir);
179        }
180
181        if ($progress) {
182            $coverageBuilder = $coverageBuilder->withArgument('--progress');
183        }
184
185        if ($jsonOutput) {
186            $coverageBuilder = $coverageBuilder->withArgument('--json');
187        }
188
189        if ($prettyJsonOutput) {
190            $coverageBuilder = $coverageBuilder->withArgument('--pretty-json');
191        }
192
193        $projectCapabilities = $this->projectCapabilitiesResolver->resolve();
194        if ($projectCapabilities->canRunTests()) {
195            $coverage = $coverageBuilder->build([DevToolsPathResolver::getBinaryPath(), 'tests']);
196            $this->processQueue->add(process: $coverage, label: 'Generating Coverage Report');
197        } else {
198            $this->log(
199                'Skipping coverage report because no tests directory or PHP source files were detected.',
200                $input,
201                logLevel: LogLevel::WARNING,
202            );
203        }
204
205        if ($progress) {
206            $metricsBuilder = $metricsBuilder->withArgument('--progress');
207        }
208
209        if ($jsonOutput) {
210            $metricsBuilder = $metricsBuilder->withArgument('--json');
211        }
212
213        if ($prettyJsonOutput) {
214            $metricsBuilder = $metricsBuilder->withArgument('--pretty-json');
215        }
216
217        $metrics = $metricsBuilder->build([DevToolsPathResolver::getBinaryPath(), 'metrics']);
218
219        $this->processQueue->add(process: $docs, detached: true, label: 'Generating API Docs Report');
220        $this->processQueue->add(process: $metrics, label: 'Generating Metrics Report');
221
222        $result = $this->processQueue->run($processOutput);
223
224        if (self::SUCCESS === $result) {
225            return $this->success('Documentation reports generated successfully.', $input, [
226                'output' => $processOutput,
227            ]);
228        }
229
230        return $this->failure('Documentation reports generation failed.', $input, [
231            'output' => $processOutput,
232        ]);
233    }
234}