Passed
Push — master ( 65f41a...2c251c )
by Allan
02:57 queued 10s
created

ListCommand::generateOutput()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 24
rs 9.8666
1
<?php
2
/**
3
 * Copyright © Vaimo Group. All rights reserved.
4
 * See LICENSE_VAIMO.txt for license details.
5
 */
6
namespace Vaimo\ComposerPatches\Composer\Commands;
7
8
use Composer\Composer;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Vaimo\ComposerPatches\Patch\Definition as PatchDefinition;
12
13
use Vaimo\ComposerPatches\Interfaces\ListResolverInterface as ListResolver;
14
use Symfony\Component\Console\Input\InputOption;
15
use Vaimo\ComposerPatches\Repository\PatchesApplier\ListResolvers;
16
use Vaimo\ComposerPatches\Config;
17
use Vaimo\ComposerPatches\Patch\DefinitionList\LoaderComponents;
18
19
class ListCommand extends \Composer\Command\BaseCommand
20
{
21
    protected function configure()
22
    {
23
        parent::configure();
24
25
        $this->setName('patch:list');
26
27
        $this->setDescription('List all registered and eligible (based on project config) patches');
28
29
        $this->addArgument(
30
            'targets',
31
            \Symfony\Component\Console\Input\InputArgument::IS_ARRAY,
32
            'Packages for the patcher to target',
33
            array()
34
        );
35
36
        $this->addOption(
37
            '--no-dev',
38
            null,
39
            InputOption::VALUE_NONE,
40
            'Disables installation of require-dev packages'
41
        );
42
43
        $this->addOption(
44
            '--filter',
45
            null,
46
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
47
            'Mark certain patches with MATCH in the output list'
48
        );
49
50
        $this->addOption(
51
            '--excluded',
52
            null,
53
            InputOption::VALUE_NONE,
54
            'Include patches that have been ruled out based on some constraint mismatch'
55
        );
56
57
        $this->addOption(
58
            '--with-excludes',
59
            null,
60
            InputOption::VALUE_NONE,
61
            'Alias for \'excluded\' argument'
62
        );
63
64
        $this->addOption(
65
            '--with-affected',
66
            null,
67
            InputOption::VALUE_NONE,
68
            'Mark patches that would get re-applied when changed/new patches are added (due to package reinstall)'
69
        );
70
        
71
        $this->addOption(
72
            '--brief',
73
            null,
74
            InputOption::VALUE_NONE,
75
            'Show more compact output of the list (remove description, owner , etc)'
76
        );
77
        
78
        $this->addOption(
79
            '--status',
80
            null,
81
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
82
            'Match specific statuses (changed, new, applied, removed)',
83
            array()
84
        );
85
86
        $this->addOption(
87
            '--from-source',
88
            null,
89
            InputOption::VALUE_NONE,
90
            'Use latest information from package configurations in vendor folder'
91
        );
92
    }
93
94
    protected function execute(InputInterface $input, OutputInterface $output)
95
    {
96
        $composer = $this->getComposer();
97
98
        $isDevMode = !$input->getOption('no-dev');
99
        $withExcluded = $input->getOption('excluded') || $input->getOption('with-excludes');
100
        $withAffected = $input->getOption('with-affected');
101
        $beBrief = $input->getOption('brief');
102
        
103
        $filters = array(
104
            PatchDefinition::SOURCE => $input->getOption('filter'),
105
            PatchDefinition::TARGETS => $input->getArgument('targets')
106
        );
107
108
        $statusFilters = array_map(
109
            'strtolower',
110
            array_filter((array)$input->getOption('status'))
111
        );
112
        
113
        $pluginConfig = $this->createConfigWithEnabledSources($composer);
114
        
115
        $filteredPool = $this->createLoaderPool();
116
        
117
        $listResolver = new ListResolvers\FilteredListResolver($filters);
118
        $loaderFactory = new \Vaimo\ComposerPatches\Factories\PatchesLoaderFactory($composer);
119
        
120
        $repository = $composer->getRepositoryManager()->getLocalRepository();
121
        
122
        $repoStateGenerator = $this->createStateGenerator($composer);
123
        $queueGenerator = $this->createQueueGenerator($composer, $listResolver);
124
        $filteredLoader = $loaderFactory->create($filteredPool, $pluginConfig, $isDevMode);
125
        
126
        $filteredPatches = $filteredLoader->loadFromPackagesRepository($repository);
127
        $repositoryState = $repoStateGenerator->generate($repository);
128
129
        $applyQueue = $queueGenerator->generateApplyQueue($filteredPatches, $repositoryState);
130
        $removeQueue = $queueGenerator->generateRemovalQueue($applyQueue, $repositoryState);
131
        $applyQueue = array_map('array_filter', $applyQueue);
132
133
        $patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils();
134
        $patchListUpdater = new \Vaimo\ComposerPatches\Patch\DefinitionList\Updater();
135
136
        $filteredPatches = $this->composerFilteredPatchesList(
137
            $filteredPatches,
138
            $applyQueue,
139
            $removeQueue,
140
            $withAffected,
141
            $filters,
142
            $statusFilters
143
        );
144
        
145
        $patches = array_filter($filteredPatches);
146
147
        $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils();
148
149
        $shouldAddExcludes = $withExcluded
150
            && (
151
                empty($statusFilters)
152
                || preg_match($filterUtils->composeRegex($statusFilters, '/'), 'excluded')
153
            );
154
        
155
        if ($shouldAddExcludes) {
156
            $unfilteredPool = $this->createUnfilteredPatchLoaderPool($composer);
157
158
            $unfilteredLoader = $loaderFactory->create($unfilteredPool, $pluginConfig, $isDevMode);
159
160
            $allPatches = $unfilteredLoader->loadFromPackagesRepository($repository);
161
            
162
            $patchesQueue = $listResolver->resolvePatchesQueue($allPatches);
163
            
164
            $excludedPatches = $patchListUpdater->updateStatuses(
165
                array_filter($patchListUtils->diffListsByPath($patchesQueue, $filteredPatches)),
166
                'excluded'
167
            );
168
169
            $patches = array_replace_recursive(
170
                $patches,
171
                $patchListUpdater->updateStatuses($excludedPatches, 'excluded')
172
            );
173
174
            array_walk($patches, function (array &$group) {
175
                ksort($group);
176
            }, $patches);
177
        }
178
        
179
        if ($beBrief) {
180
            $patches = $patchListUpdater->embedInfoToItems($patches, array(
181
                PatchDefinition::LABEL => false,
182
                PatchDefinition::OWNER => false
183
            ));
184
        }
185
        
186
        $this->generateOutput($output, $patches);
187
    }
188
    
189
    private function createUnfilteredPatchLoaderPool(\Composer\Composer $composer)
190
    {
191
        $packageInfoResolver = new \Vaimo\ComposerPatches\Package\InfoResolver(
192
            $composer->getInstallationManager(),
193
            $composer->getConfig()->get(\Vaimo\ComposerPatches\Composer\ConfigKeys::VENDOR_DIR)
194
        );
195
196
        return $this->createLoaderPool(array(
197
            'constraints' => false,
198
            'platform' => false,
199
            'local-exclude' => false,
200
            'root-patch' => false,
201
            'global-exclude' => false,
202
            'targets-resolver' => new LoaderComponents\TargetsResolverComponent($packageInfoResolver, true)
203
        ));
204
    }
205
    
206
    private function composerFilteredPatchesList($patches, $additions, $removals, $withAffected, $filters, $statuses)
207
    {
208
        $hasFilers = (bool)array_filter($filters);
209
210
        $listResolver = new ListResolvers\FilteredListResolver($filters);
211
        
212
        $patchListUtils = new \Vaimo\ComposerPatches\Utils\PatchListUtils();
213
        $patchListUpdater = new \Vaimo\ComposerPatches\Patch\DefinitionList\Updater();
214
215
        $filteredPatches = $patchListUtils->mergeLists($patches, $removals);
216
217
        if ($withAffected) {
218
            $additions = $patchListUpdater->embedInfoToItems(
219
                $additions,
220
                array(PatchDefinition::STATUS => 'affected'),
221
                true
222
            );
223
        }
224
225
        $filteredPatches = $patchListUtils->mergeLists(
226
            $filteredPatches,
227
            $patchListUtils->intersectListsByName($additions, $filteredPatches)
228
        );
229
230
        $filteredPatches = $patchListUpdater->embedInfoToItems(
231
            $filteredPatches,
232
            array(PatchDefinition::STATUS => 'applied'),
233
            true
234
        );
235
236
        if ($hasFilers) {
237
            $filteredPatches = $listResolver->resolvePatchesQueue($filteredPatches);
238
        }
239
        
240
        if (!empty($statuses)) {
241
            $filterUtils = new \Vaimo\ComposerPatches\Utils\FilterUtils();
242
243
            $filteredPatches = $patchListUtils->applyDefinitionKeyValueFilter(
244
                $filteredPatches,
245
                $filterUtils->composeRegex($statuses, '/'),
246
                PatchDefinition::STATUS
247
            );
248
        }
249
        
250
        return $filteredPatches;
251
    }
252
    
253
    private function createConfigWithEnabledSources(Composer $composer)
254
    {
255
        $configDefaults = new \Vaimo\ComposerPatches\Config\Defaults();
256
257
        $defaultValues = $configDefaults->getPatcherConfig();
258
259
        $sourceKeys = array_keys($defaultValues[Config::PATCHER_SOURCES]);
0 ignored issues
show
Bug introduced by
It seems like $defaultValues[Vaimo\Com...onfig::PATCHER_SOURCES] can also be of type boolean; however, parameter $input of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

259
        $sourceKeys = array_keys(/** @scrutinizer ignore-type */ $defaultValues[Config::PATCHER_SOURCES]);
Loading history...
260
261
        $pluginConfig = array(
262
            Config::PATCHER_SOURCES => array_fill_keys($sourceKeys, true)
263
        );
264
265
        $configFactory = new \Vaimo\ComposerPatches\Factories\ConfigFactory($composer);
266
        
267
        return $configFactory->create(array($pluginConfig));
268
    }
269
    
270
    private function createStateGenerator(Composer $composer)
271
    {
272
        $packageCollector = new \Vaimo\ComposerPatches\Package\Collector(
273
            array($composer->getPackage())
274
        );
275
276
        return new \Vaimo\ComposerPatches\Repository\StateGenerator(
277
            $packageCollector
278
        );
279
    }
280
    
281
    private function createQueueGenerator(Composer $composer, ListResolver $listResolver)
282
    {
283
        $stateAnalyserFactory = new \Vaimo\ComposerPatches\Factories\RepositoryStateAnalyserFactory(
284
            $composer
285
        );
286
        
287
        $changesListResolver = new ListResolvers\ChangesListResolver($listResolver);
288
289
        $stateAnalyser = $stateAnalyserFactory->create();
290
        
291
        return new \Vaimo\ComposerPatches\Repository\PatchesApplier\QueueGenerator(
292
            $changesListResolver,
293
            $stateAnalyser
294
        );
295
    }
296
    
297
    private function createLoaderPool(array $componentUpdates = array())
298
    {
299
        $componentPool = new \Vaimo\ComposerPatches\Patch\DefinitionList\Loader\ComponentPool(
300
            $this->getComposer(),
301
            $this->getIO()
302
        );
303
304
        foreach ($componentUpdates as $componentName => $replacement) {
305
            $componentPool->registerComponent($componentName, $replacement);
306
        }
307
        
308
        return $componentPool;
309
    }
310
    
311
    private function generateOutput(OutputInterface $output, array $list)
312
    {
313
        $statusConfig = new \Vaimo\ComposerPatches\Package\PatchApplier\StatusConfig();
314
315
        $statusDecorators = $statusConfig->getLabels();
316
        
317
        foreach ($list as $packageName => $patches) {
318
            $output->writeln(sprintf('<info>%s</info>', $packageName));
319
320
            foreach ($patches as $path => $info) {
321
                $patchInfoLabel = $this->createStatusLabel($path, $info, $statusDecorators);
322
                
323
                $output->writeln($patchInfoLabel);
324
325
                $descriptionLines = array_filter(
326
                    explode(PHP_EOL, $info[PatchDefinition::LABEL])
327
                );
328
                
329
                foreach ($descriptionLines as $line) {
330
                    $output->writeln(sprintf('    <comment>%s</comment>', $line));
331
                }
332
            }
333
334
            $output->writeln('');
335
        }
336
    }
337
    
338
    private function createStatusLabel($path, $info, array $statusDecorators)
339
    {
340
        $status = isset($info[PatchDefinition::STATUS])
341
            ? $info[PatchDefinition::STATUS]
342
            : 'unknown';
343
344
        $owner = $info[PatchDefinition::OWNER];
345
        
346
        $statusLabel = sprintf(' [%s]', $statusDecorators[$status]);
347
348
        if ($owner === PatchDefinition::OWNER_UNKNOWN) {
349
            return sprintf('  ~ %s%s', $path, $statusLabel);
350
        }
351
        
352
        if ($owner) {
353
            return sprintf('  ~ <info>%s</info>: %s%s', $owner, $path, $statusLabel);
354
        }
355
356
        return sprintf('%s%s', $path, $statusLabel);
357
    }
358
}
359