Passed
Pull Request — master (#51)
by Dominik
02:38
created

ReportCommand   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 225
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 27
eloc 115
c 4
b 0
f 0
dl 0
loc 225
ccs 0
cts 133
cp 0
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A groupDependenciesByLicense() 0 14 3
A configure() 0 47 1
B outputFormattedLicenses() 0 41 6
A outputFormatPackages() 0 16 4
A getDefaultName() 0 3 1
A getDefaultDescription() 0 3 1
A execute() 0 24 3
A filterLicenses() 0 18 5
A lookUpLicenses() 0 13 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Dominikb\ComposerLicenseChecker;
6
7
use Symfony\Component\Console\Attribute\AsCommand;
8
use Dominikb\ComposerLicenseChecker\Contracts\DependencyLoaderAware;
9
use Dominikb\ComposerLicenseChecker\Contracts\LicenseLookupAware;
10
use Dominikb\ComposerLicenseChecker\Traits\DependencyLoaderAwareTrait;
11
use Dominikb\ComposerLicenseChecker\Traits\LicenseLookupAwareTrait;
12
use Symfony\Component\Cache\Adapter\NullAdapter;
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Helper\Table;
15
use Symfony\Component\Console\Input\InputDefinition;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
class ReportCommand extends Command implements LicenseLookupAware, DependencyLoaderAware
21
{
22
    use LicenseLookupAwareTrait, DependencyLoaderAwareTrait;
23
24
    protected function configure()
25
    {
26
        $this->setDefinition(new InputDefinition([
27
            new InputOption(
28
                'project-path',
29
                'p',
30
                InputOption::VALUE_OPTIONAL,
31
                'Path to directory of composer.json file',
32
                realpath('.')
33
            ),
34
            new InputOption(
35
                'composer',
36
                'c',
37
                InputOption::VALUE_OPTIONAL,
38
                'Path to composer executable',
39
                'composer'
40
            ),
41
            new InputOption(
42
                'no-dev',
43
                null,
44
                InputOption::VALUE_OPTIONAL,
45
                'Do not include dev dependencies',
46
                'false'
47
            ),
48
            new InputOption(
49
                'no-cache',
50
                null,
51
                InputOption::VALUE_NONE,
52
                'Disables caching of license lookups'
53
            ),
54
            new InputOption(
55
                'show-packages',
56
                null,
57
                InputOption::VALUE_NONE,
58
                'Shows the packages for each license.'
59
            ),
60
            new InputOption(
61
                'grouped',
62
                null,
63
                InputOption::VALUE_NONE,
64
                'Display the packages grouped. Only valid with the \'show-packages\' option.'
65
            ),
66
            new InputOption(
67
                'filter',
68
                null,
69
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
70
                'Filter for specific licences.'
71
            ),
72
        ]));
73
    }
74
75
    public static function getDefaultName(): ?string
76
    {
77
        return 'report';
78
    }
79
80
    public static function getDefaultDescription(): ?string
81
    {
82
        return 'Generate a report of all licenses used in the project';
83
    }
84
85
    protected function execute(InputInterface $input, OutputInterface $output): int
86
    {
87
        if ($input->getOption('grouped') && ! $input->getOption('show-packages')) {
88
            throw new \InvalidArgumentException('The option "grouped" is only allowed with "show-packages" option');
89
        }
90
91
        $dependencies = $this->dependencyLoader->loadDependencies(
0 ignored issues
show
Bug introduced by
The method loadDependencies() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

91
        /** @scrutinizer ignore-call */ 
92
        $dependencies = $this->dependencyLoader->loadDependencies(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
92
            $input->getOption('composer'),
93
            $input->getOption('project-path'),
94
            ($input->getOption('no-dev') ?? 'true') === 'true'
95
        );
96
97
        $dependencies = $this->filterLicenses($dependencies, $input->getOption('filter'));
98
99
        $groupedByName = $this->groupDependenciesByLicense($dependencies);
100
101
        $shouldCache = ! $input->getOption('no-cache');
102
103
        $licenses = $this->lookUpLicenses(array_keys($groupedByName), $output, $shouldCache);
104
105
        /* @var License $license */
106
        $this->outputFormattedLicenses($output, $input, $licenses, $groupedByName);
107
108
        return self::SUCCESS;
109
    }
110
111
    /**
112
     * @param  Dependency[]  $dependencies
113
     * @return array
114
     */
115
    private function groupDependenciesByLicense(array $dependencies): array
116
    {
117
        $grouped = [];
118
119
        foreach ($dependencies as $dependency) {
120
            [$license] = $dependency->getLicenses();
121
122
            if (! isset($grouped[$license])) {
123
                $grouped[$license] = [];
124
            }
125
            $grouped[$license][] = $dependency;
126
        }
127
128
        return $grouped;
129
    }
130
131
    /**
132
     * @param  Dependency[]  $dependencies
133
     * @param  string[]  $filters
134
     * @return array
135
     */
136
    private function filterLicenses(array $dependencies, array $filters): array
137
    {
138
        if ($filters === []) {
139
            return $dependencies;
140
        }
141
142
        $validLicences = [];
143
144
        foreach ($dependencies as $dependency) {
145
            foreach ($dependency->getLicenses() as $license) {
146
                if (in_array(strtolower($license), array_map('strtolower', $filters))) {
147
                    $validLicences[] = $dependency;
148
                    continue 2;
149
                }
150
            }
151
        }
152
153
        return $validLicences;
154
    }
155
156
    private function lookUpLicenses(array $licenses, OutputInterface $output, $useCache = true): array
157
    {
158
        if (! $useCache) {
159
            $this->licenseLookup->setCache(new NullAdapter);
0 ignored issues
show
Bug introduced by
The method setCache() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

159
            $this->licenseLookup->/** @scrutinizer ignore-call */ 
160
                                  setCache(new NullAdapter);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
160
        }
161
162
        $lookedUp = [];
163
        foreach ($licenses as $license) {
164
            $output->writeln("Looking up $license ...");
165
            $lookedUp[$license] = $this->licenseLookup->lookUp($license);
166
        }
167
168
        return $lookedUp;
169
    }
170
171
    /**
172
     * @param  OutputInterface  $output
173
     * @param  InputInterface  $input
174
     * @param  License[]  $licenses
175
     * @param  array  $groupedByName
176
     */
177
    protected function outputFormattedLicenses(OutputInterface $output, InputInterface $input, array $licenses, array $groupedByName): void
178
    {
179
        foreach ($licenses as $license) {
180
            $dependencies = $groupedByName[$license->getShortName()];
181
182
            $usageCount = count($dependencies);
183
            $headline = sprintf(PHP_EOL.'Count %d - %s (%s)', $usageCount, $license->getShortName(),
184
                $license->getSource());
185
            $output->writeln($headline);
186
            $licenseTable = new Table($output);
187
            $licenseTable->setHeaders(['CAN', 'CAN NOT', 'MUST']);
188
189
            $can = $license->getCan();
190
            $cannot = $license->getCannot();
191
            $must = $license->getMust();
192
            $columnWidth = max(count($can), count($cannot), count($must));
193
194
            $can = array_pad($can, $columnWidth, null);
195
            $cannot = array_pad($cannot, $columnWidth, null);
196
            $must = array_pad($must, $columnWidth, null);
197
198
            $inlineHeading = function ($key) {
199
                return is_string($key) ? $key : '';
200
            };
201
202
            $can = array_map_keys($can, $inlineHeading);
203
            $cannot = array_map_keys($cannot, $inlineHeading);
204
            $must = array_map_keys($must, $inlineHeading);
205
206
            for ($i = 0; $i < $columnWidth; $i++) {
207
                $licenseTable->addRow([
208
                    'CAN' => $can[$i],
209
                    'CANNOT' => $cannot[$i],
210
                    'MUST' => $must[$i],
211
                ]);
212
            }
213
            $licenseTable->render();
214
215
            if ($input->getOption('show-packages') || $output->isVerbose()) {
216
                $output->writeln('');
217
                $output->writeln($this->outputFormatPackages($input, $dependencies));
218
            }
219
        }
220
    }
221
222
    /**
223
     * Generates a output string for the 'show-packages' option.
224
     *
225
     * @param  InputInterface  $input
226
     * @param  array  $dependencies
227
     * @return string
228
     */
229
    protected function outputFormatPackages(InputInterface $input, array $dependencies): string
230
    {
231
        $packages = [];
232
        if ($input->getOption('grouped')) {
233
            foreach ($dependencies as $dependency) {
234
                $packages[] = $dependency->getName();
235
            }
236
237
            return 'packages: '.implode(', ', $packages);
238
        }
239
240
        foreach ($dependencies as $dependency) {
241
            $packages[] = sprintf('%s (%s)', $dependency->getName(), $dependency->getVersion());
242
        }
243
244
        return implode(PHP_EOL, $packages);
245
    }
246
}
247