1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the puli/cli package. |
5
|
|
|
* |
6
|
|
|
* (c) Bernhard Schussek <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Puli\Cli\Handler; |
13
|
|
|
|
14
|
|
|
use Puli\Cli\Style\PuliTableStyle; |
15
|
|
|
use Puli\Cli\Util\ArgsUtil; |
16
|
|
|
use Puli\Manager\Api\Module\ModuleList; |
17
|
|
|
use Puli\Manager\Api\Repository\PathConflict; |
18
|
|
|
use Puli\Manager\Api\Repository\PathMapping; |
19
|
|
|
use Puli\Manager\Api\Repository\PathMappingState; |
20
|
|
|
use Puli\Manager\Api\Repository\RepositoryManager; |
21
|
|
|
use RuntimeException; |
22
|
|
|
use Webmozart\Console\Api\Args\Args; |
23
|
|
|
use Webmozart\Console\Api\IO\IO; |
24
|
|
|
use Webmozart\Console\UI\Component\Table; |
25
|
|
|
use Webmozart\Expression\Expr; |
26
|
|
|
use Webmozart\PathUtil\Path; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Handles the "puli map" command. |
30
|
|
|
* |
31
|
|
|
* @since 1.0 |
32
|
|
|
* |
33
|
|
|
* @author Bernhard Schussek <[email protected]> |
34
|
|
|
*/ |
35
|
|
|
class MapCommandHandler |
36
|
|
|
{ |
37
|
|
|
/** |
38
|
|
|
* @var RepositoryManager |
39
|
|
|
*/ |
40
|
|
|
private $repoManager; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var ModuleList |
44
|
|
|
*/ |
45
|
|
|
private $modules; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var string |
49
|
|
|
*/ |
50
|
|
|
private $currentPath = '/'; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Creates the handler. |
54
|
|
|
* |
55
|
|
|
* @param RepositoryManager $repoManager The repository manager |
56
|
|
|
* @param ModuleList $modules The loaded modules |
57
|
|
|
*/ |
58
|
24 |
|
public function __construct(RepositoryManager $repoManager, ModuleList $modules) |
59
|
|
|
{ |
60
|
24 |
|
$this->repoManager = $repoManager; |
61
|
24 |
|
$this->modules = $modules; |
62
|
24 |
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Handles the "puli map --list" command. |
66
|
|
|
* |
67
|
|
|
* @param Args $args The console arguments |
68
|
|
|
* @param IO $io The I/O |
69
|
|
|
* |
70
|
|
|
* @return int The status code |
71
|
|
|
*/ |
72
|
12 |
|
public function handleList(Args $args, IO $io) |
73
|
|
|
{ |
74
|
12 |
|
$moduleNames = ArgsUtil::getModuleNames($args, $this->modules); |
75
|
12 |
|
$states = $this->getPathMappingStates($args); |
76
|
|
|
|
77
|
12 |
|
$printState = count($states) > 1; |
78
|
12 |
|
$printModuleName = count($moduleNames) > 1; |
79
|
12 |
|
$printHeaders = $printState || $printModuleName; |
80
|
12 |
|
$printAdvice = true; |
81
|
12 |
|
$indentation = ($printState && $printModuleName) ? 8 |
82
|
12 |
|
: ($printState || $printModuleName ? 4 : 0); |
83
|
|
|
|
84
|
12 |
|
foreach ($states as $state) { |
85
|
12 |
|
$statePrinted = !$printState; |
86
|
|
|
|
87
|
12 |
|
if (PathMappingState::CONFLICT === $state) { |
88
|
7 |
|
$expr = Expr::method('getContainingModule', Expr::method('getName', Expr::in($moduleNames))) |
89
|
7 |
|
->andMethod('getState', Expr::same($state)); |
90
|
|
|
|
91
|
7 |
|
$mappings = $this->repoManager->findPathMappings($expr); |
92
|
|
|
|
93
|
7 |
|
if (!$mappings) { |
|
|
|
|
94
|
1 |
|
continue; |
95
|
|
|
} |
96
|
|
|
|
97
|
6 |
|
$printAdvice = false; |
98
|
|
|
|
99
|
6 |
|
if ($printState) { |
100
|
5 |
|
$this->printPathMappingStateHeader($io, $state); |
101
|
|
|
} |
102
|
|
|
|
103
|
6 |
|
$this->printConflictTable($io, $mappings, $printState ? 4 : 0); |
104
|
|
|
|
105
|
6 |
|
if ($printHeaders) { |
106
|
6 |
|
$io->writeLine(''); |
107
|
|
|
} |
108
|
|
|
|
109
|
6 |
|
continue; |
110
|
|
|
} |
111
|
|
|
|
112
|
11 |
|
foreach ($moduleNames as $moduleName) { |
113
|
11 |
|
$expr = Expr::method('getContainingModule', Expr::method('getName', Expr::same($moduleName))) |
114
|
11 |
|
->andMethod('getState', Expr::same($state)); |
115
|
|
|
|
116
|
11 |
|
$mappings = $this->repoManager->findPathMappings($expr); |
117
|
|
|
|
118
|
11 |
|
if (!$mappings) { |
|
|
|
|
119
|
1 |
|
continue; |
120
|
|
|
} |
121
|
|
|
|
122
|
10 |
|
$printAdvice = false; |
123
|
|
|
|
124
|
10 |
|
if (!$statePrinted) { |
125
|
6 |
|
$this->printPathMappingStateHeader($io, $state); |
126
|
6 |
|
$statePrinted = true; |
127
|
|
|
} |
128
|
|
|
|
129
|
10 |
View Code Duplication |
if ($printModuleName) { |
|
|
|
|
130
|
6 |
|
$prefix = $printState ? ' ' : ''; |
131
|
6 |
|
$io->writeLine(sprintf('%sModule: %s', $prefix, $moduleName)); |
132
|
6 |
|
$io->writeLine(''); |
133
|
|
|
} |
134
|
|
|
|
135
|
10 |
|
$this->printMappingTable($io, $mappings, $indentation, PathMappingState::ENABLED === $state); |
136
|
|
|
|
137
|
10 |
|
if ($printHeaders) { |
138
|
11 |
|
$io->writeLine(''); |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
12 |
|
if ($printAdvice) { |
144
|
1 |
|
$io->writeLine('No path mappings. Use "puli map <path> <file>" to map a Puli path to a file or directory.'); |
145
|
|
|
} |
146
|
|
|
|
147
|
12 |
|
return 0; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Handles the "puli map" command. |
152
|
|
|
* |
153
|
|
|
* @param Args $args The console arguments |
154
|
|
|
* |
155
|
|
|
* @return int The status code |
156
|
|
|
*/ |
157
|
3 |
View Code Duplication |
public function handleAdd(Args $args) |
|
|
|
|
158
|
|
|
{ |
159
|
3 |
|
$flags = $args->isOptionSet('force') |
160
|
1 |
|
? RepositoryManager::OVERRIDE | RepositoryManager::IGNORE_FILE_NOT_FOUND |
161
|
3 |
|
: 0; |
162
|
3 |
|
$repositoryPath = Path::makeAbsolute($args->getArgument('path'), $this->currentPath); |
163
|
3 |
|
$pathReferences = $args->getArgument('file'); |
164
|
|
|
|
165
|
3 |
|
$this->repoManager->addRootPathMapping(new PathMapping($repositoryPath, $pathReferences), $flags); |
166
|
|
|
|
167
|
3 |
|
return 0; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Handles the "puli map --update" command. |
172
|
|
|
* |
173
|
|
|
* @param Args $args The console arguments |
174
|
|
|
* |
175
|
|
|
* @return int The status code |
176
|
|
|
*/ |
177
|
6 |
|
public function handleUpdate(Args $args) |
178
|
|
|
{ |
179
|
6 |
|
$flags = $args->isOptionSet('force') |
180
|
1 |
|
? RepositoryManager::OVERRIDE | RepositoryManager::IGNORE_FILE_NOT_FOUND |
181
|
6 |
|
: RepositoryManager::OVERRIDE; |
182
|
6 |
|
$repositoryPath = Path::makeAbsolute($args->getArgument('path'), $this->currentPath); |
183
|
6 |
|
$mappingToUpdate = $this->repoManager->getRootPathMapping($repositoryPath); |
184
|
6 |
|
$pathReferences = array_flip($mappingToUpdate->getPathReferences()); |
185
|
|
|
|
186
|
6 |
|
foreach ($args->getOption('add') as $pathReference) { |
187
|
3 |
|
$pathReferences[$pathReference] = true; |
188
|
|
|
} |
189
|
|
|
|
190
|
6 |
|
foreach ($args->getOption('remove') as $pathReference) { |
191
|
2 |
|
unset($pathReferences[$pathReference]); |
192
|
|
|
} |
193
|
|
|
|
194
|
6 |
|
if (0 === count($pathReferences)) { |
195
|
1 |
|
$this->repoManager->removeRootPathMapping($repositoryPath); |
196
|
|
|
|
197
|
1 |
|
return 0; |
198
|
|
|
} |
199
|
|
|
|
200
|
5 |
|
$updatedMapping = new PathMapping($repositoryPath, array_keys($pathReferences)); |
201
|
|
|
|
202
|
5 |
|
if ($this->mappingsEqual($mappingToUpdate, $updatedMapping)) { |
203
|
1 |
|
throw new RuntimeException('Nothing to update.'); |
204
|
|
|
} |
205
|
|
|
|
206
|
4 |
|
$this->repoManager->addRootPathMapping($updatedMapping, $flags); |
207
|
|
|
|
208
|
4 |
|
return 0; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Handles the "puli map --delete" command. |
213
|
|
|
* |
214
|
|
|
* @param Args $args The console arguments |
215
|
|
|
* |
216
|
|
|
* @return int The status code |
217
|
|
|
*/ |
218
|
3 |
|
public function handleDelete(Args $args) |
219
|
|
|
{ |
220
|
3 |
|
$repositoryPath = Path::makeAbsolute($args->getArgument('path'), $this->currentPath); |
221
|
|
|
|
222
|
3 |
|
if (!$this->repoManager->hasRootPathMapping($repositoryPath)) { |
223
|
1 |
|
throw new RuntimeException(sprintf( |
224
|
1 |
|
'The path "%s" is not mapped in the module "%s".', |
225
|
|
|
$repositoryPath, |
226
|
1 |
|
$this->modules->getRootModuleName() |
227
|
|
|
)); |
228
|
|
|
} |
229
|
|
|
|
230
|
2 |
|
$this->repoManager->removeRootPathMapping($repositoryPath); |
231
|
|
|
|
232
|
2 |
|
return 0; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Prints a list of path mappings. |
237
|
|
|
* |
238
|
|
|
* @param IO $io The I/O |
239
|
|
|
* @param PathMapping[] $mappings The path mappings |
240
|
|
|
* @param int $indentation The number of spaces to indent the |
241
|
|
|
* output |
242
|
|
|
* @param bool $enabled Whether the path mappings are enabled. |
243
|
|
|
* If not, the output is printed in red |
244
|
|
|
*/ |
245
|
10 |
|
private function printMappingTable(IO $io, array $mappings, $indentation = 0, $enabled = true) |
246
|
|
|
{ |
247
|
10 |
|
$table = new Table(PuliTableStyle::borderless()); |
248
|
|
|
|
249
|
10 |
|
$table->setHeaderRow(array('Puli Path', 'Real Path(s)')); |
250
|
|
|
|
251
|
10 |
|
$pathTag = $enabled ? 'c1' : 'bad'; |
252
|
|
|
|
253
|
10 |
|
foreach ($mappings as $mapping) { |
254
|
10 |
|
if ($enabled) { |
255
|
9 |
|
$pathReferences = array(); |
256
|
|
|
|
257
|
9 |
|
foreach ($mapping->getPathReferences() as $pathReference) { |
258
|
|
|
// Underline referenced packages |
259
|
9 |
|
$pathReference = preg_replace('~^@([^:]+):~', '@<u>$1</u>:', $pathReference); |
260
|
|
|
|
261
|
|
|
// Highlight path parts |
262
|
9 |
|
$pathReference = preg_replace('~^(@([^:]+):)?(.*)$~', '$1<c2>$3</c2>', $pathReference); |
263
|
|
|
|
264
|
9 |
|
$pathReferences[] = $pathReference; |
265
|
|
|
} |
266
|
|
|
|
267
|
9 |
|
$pathReferences = implode(', ', $pathReferences); |
268
|
|
|
} else { |
269
|
7 |
|
$pathReferences = '<bad>'.implode(', ', $mapping->getPathReferences()).'</bad>'; |
270
|
|
|
} |
271
|
|
|
|
272
|
10 |
|
$table->addRow(array( |
273
|
10 |
|
sprintf('<%s>%s</%s>', $pathTag, $mapping->getRepositoryPath(), $pathTag), |
274
|
10 |
|
$pathReferences, |
275
|
|
|
)); |
276
|
|
|
} |
277
|
|
|
|
278
|
10 |
|
$table->render($io, $indentation); |
279
|
10 |
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Prints a list of conflicting path mappings. |
283
|
|
|
* |
284
|
|
|
* @param IO $io The I/O |
285
|
|
|
* @param PathMapping[] $mappings The path mappings |
286
|
|
|
* @param int $indentation The number of spaces to indent the |
287
|
|
|
* output |
288
|
|
|
*/ |
289
|
6 |
|
private function printConflictTable(IO $io, array $mappings, $indentation = 0) |
290
|
|
|
{ |
291
|
|
|
/** @var PathConflict[] $conflicts */ |
292
|
6 |
|
$conflicts = array(); |
293
|
6 |
|
$shortPrefix = str_repeat(' ', $indentation); |
294
|
6 |
|
$prefix = str_repeat(' ', $indentation + 4); |
295
|
6 |
|
$printNewline = false; |
296
|
|
|
|
297
|
6 |
|
foreach ($mappings as $mapping) { |
298
|
6 |
|
foreach ($mapping->getConflicts() as $conflict) { |
299
|
6 |
|
$conflicts[spl_object_hash($conflict)] = $conflict; |
300
|
|
|
} |
301
|
|
|
} |
302
|
|
|
|
303
|
6 |
|
foreach ($conflicts as $conflict) { |
304
|
6 |
|
if ($printNewline) { |
305
|
5 |
|
$io->writeLine(''); |
306
|
|
|
} |
307
|
|
|
|
308
|
6 |
|
$io->writeLine(sprintf('%sConflicting path: %s', $shortPrefix, $conflict->getRepositoryPath())); |
309
|
6 |
|
$io->writeLine(''); |
310
|
|
|
|
311
|
6 |
|
$table = new Table(PuliTableStyle::borderless()); |
312
|
|
|
|
313
|
6 |
|
$table->setHeaderRow(array('Module', 'Puli Path', 'Real Path(s)')); |
314
|
|
|
|
315
|
6 |
|
foreach ($conflict->getMappings() as $mapping) { |
316
|
6 |
|
$table->addRow(array( |
317
|
6 |
|
'<bad>'.$mapping->getContainingModule()->getName().'</bad>', |
318
|
6 |
|
'<bad>'.$mapping->getRepositoryPath().'</bad>', |
319
|
6 |
|
'<bad>'.implode(', ', $mapping->getPathReferences()).'</bad>', |
320
|
|
|
)); |
321
|
|
|
} |
322
|
|
|
|
323
|
6 |
|
$io->writeLine(sprintf('%sMapped by the following mappings:', $prefix)); |
324
|
6 |
|
$io->writeLine(''); |
325
|
|
|
|
326
|
6 |
|
$table->render($io, $indentation + 4); |
327
|
|
|
|
328
|
6 |
|
$printNewline = true; |
329
|
|
|
} |
330
|
6 |
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* Returns the path mapping states selected in the console arguments. |
334
|
|
|
* |
335
|
|
|
* @param Args $args The console arguments |
336
|
|
|
* |
337
|
|
|
* @return int[] The selected {@link PathMappingState} constants |
|
|
|
|
338
|
|
|
*/ |
339
|
12 |
|
private function getPathMappingStates(Args $args) |
340
|
|
|
{ |
341
|
|
|
$states = array( |
342
|
12 |
|
PathMappingState::ENABLED => 'enabled', |
343
|
12 |
|
PathMappingState::NOT_FOUND => 'not-found', |
344
|
12 |
|
PathMappingState::CONFLICT => 'conflict', |
345
|
|
|
); |
346
|
|
|
|
347
|
12 |
|
$states = array_filter($states, function ($option) use ($args) { |
348
|
12 |
|
return $args->isOptionSet($option); |
349
|
12 |
|
}); |
350
|
|
|
|
351
|
12 |
|
return array_keys($states) ?: PathMappingState::all(); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Prints the header for a path mapping state. |
356
|
|
|
* |
357
|
|
|
* @param IO $io The I/O |
358
|
|
|
* @param int $pathMappingState The {@link PathMappingState} constant |
359
|
|
|
*/ |
360
|
6 |
|
private function printPathMappingStateHeader(IO $io, $pathMappingState) |
361
|
|
|
{ |
362
|
|
View Code Duplication |
switch ($pathMappingState) { |
|
|
|
|
363
|
6 |
|
case PathMappingState::ENABLED: |
364
|
6 |
|
$io->writeLine('The following path mappings are currently enabled:'); |
365
|
6 |
|
$io->writeLine(''); |
366
|
|
|
|
367
|
6 |
|
return; |
368
|
6 |
|
case PathMappingState::NOT_FOUND: |
369
|
6 |
|
$io->writeLine('The target paths of the following path mappings were not found:'); |
370
|
6 |
|
$io->writeLine(''); |
371
|
|
|
|
372
|
6 |
|
return; |
373
|
5 |
|
case PathMappingState::CONFLICT: |
374
|
5 |
|
$io->writeLine('Some path mappings have conflicting paths:'); |
375
|
5 |
|
$io->writeLine(' (add the module names to the "override-order" key in puli.json to resolve)'); |
376
|
5 |
|
$io->writeLine(''); |
377
|
|
|
|
378
|
5 |
|
return; |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
382
|
5 |
|
private function mappingsEqual(PathMapping $mapping1, PathMapping $mapping2) |
383
|
|
|
{ |
384
|
5 |
|
return $mapping1->getRepositoryPath() === $mapping2->getRepositoryPath() && |
385
|
5 |
|
$mapping1->getPathReferences() === $mapping2->getPathReferences(); |
386
|
|
|
} |
387
|
|
|
} |
388
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.