Skip to content

Commit c427421

Browse files
committed
Command to inspect a recipe
1 parent 0b6ec56 commit c427421

File tree

2 files changed

+168
-24
lines changed

2 files changed

+168
-24
lines changed

src/Command/RecipesCommand.php

Lines changed: 167 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace Symfony\Flex\Command;
1313

1414
use Composer\Command\BaseCommand;
15-
use Composer\Factory;
15+
use Symfony\Component\Console\Input\InputArgument;
1616
use Symfony\Component\Console\Input\InputInterface;
1717
use Symfony\Component\Console\Output\OutputInterface;
1818
use Symfony\Flex\InformationOperation;
@@ -24,33 +24,46 @@ class RecipesCommand extends BaseCommand
2424
/** @var \Symfony\Flex\Flex */
2525
private $flex;
2626

27-
public function __construct(/* cannot be type-hinted */ $flex)
27+
/** @var Lock */
28+
private $symfonyLock;
29+
30+
public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock)
2831
{
2932
$this->flex = $flex;
33+
$this->symfonyLock = $symfonyLock;
3034

3135
parent::__construct();
3236
}
3337

3438
protected function configure()
3539
{
36-
$this->setName('symfony:recipes:show')
37-
->setAliases(['symfony:recipes'])
40+
$this->setName('symfony:recipes')
41+
->setAliases(['recipes'])
3842
->setDescription('Shows information about all available recipes.')
43+
->setDefinition([
44+
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect, if not provided all packages are.'),
45+
])
3946
;
4047
}
4148

4249
protected function execute(InputInterface $input, OutputInterface $output)
4350
{
44-
$locker = $this->getComposer()->getLocker();
45-
$lockData = $locker->getLockData();
51+
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
4652

47-
// Merge all packages installed
48-
$packages = array_merge($lockData['packages'], $lockData['packages-dev']);
53+
// Inspect one or all packages
54+
$package = $input->getArgument('package');
55+
if (null !== $package) {
56+
$packages = [0 => ['name' => strtolower($package)]];
57+
} else {
58+
$locker = $this->getComposer()->getLocker();
59+
$lockData = $locker->getLockData();
4960

50-
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
61+
// Merge all packages installed
62+
$packages = array_merge($lockData['packages'], $lockData['packages-dev']);
63+
}
5164

5265
$operations = [];
53-
foreach ($packages as $key => $value) {
66+
foreach ($packages as $value) {
5467
if (null === $pkg = $installedRepo->findPackage($value['name'], '*')) {
5568
$this->getIO()->writeError(sprintf('<error>Package %s is not installed</error>', $value['name']));
5669

@@ -63,12 +76,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
6376
$recipes = $this->flex->fetchRecipes($operations);
6477
ksort($recipes);
6578

66-
if (\count($recipes) <= 0) {
79+
$nbRecipe = \count($recipes);
80+
if ($nbRecipe <= 0) {
6781
$this->getIO()->writeError('<error>No recipe found</error>');
6882

6983
return 1;
7084
}
7185

86+
// Display the information about a specific recipe
87+
if (1 === $nbRecipe) {
88+
$this->displayPackageInformation(current($recipes));
89+
90+
return 0;
91+
}
92+
93+
// display a resume of all packages
7294
$write = [
7395
'',
7496
'<bg=blue;fg=white> </>',
@@ -77,33 +99,155 @@ protected function execute(InputInterface $input, OutputInterface $output)
7799
'',
78100
];
79101

80-
$symfonyLock = new Lock(getenv('SYMFONY_LOCKFILE') ?: str_replace('composer.json', 'symfony.lock', Factory::getComposerFile()));
81-
82102
/** @var Recipe $recipe */
83103
foreach ($recipes as $name => $recipe) {
84-
$lockRef = $symfonyLock->get($name)['recipe']['ref'] ?? null;
104+
$lockRef = $this->symfonyLock->get($name)['recipe']['ref'] ?? null;
85105

86106
$additional = '';
87-
if ($recipe->isAuto()) {
88-
$additional = '<comment>(auto recipe)</comment>';
89-
} elseif (null === $lockRef && null !== $recipe->getRef()) {
90-
$additional = '<comment>(new recipe available)</comment>';
107+
if (null === $lockRef && null !== $recipe->getRef()) {
108+
$additional = '<comment>(recipe not installed)</comment>';
91109
} elseif ($recipe->getRef() !== $lockRef) {
92110
$additional = '<comment>(update available)</comment>';
93111
}
94112
$write[] = sprintf(' * %s %s', $name, $additional);
95113
}
96114

97-
// TODO : Uncomment the lines following the implemented features
98-
//$write[] = '';
99-
//$write[] = '<fg=blue>Run</>:';
100-
//$write[] = ' * composer symfony:recipes vendor/package to see details about a recipe.';
101-
//$write[] = ' * composer symfony:recipes:update vendor/package to update that recipe.';
115+
$write[] = '';
116+
$write[] = '<fg=blue>Run</>:';
117+
$write[] = ' * composer symfony:recipes vendor/package to see details about a recipe.';
118+
$write[] = ' * composer symfony:recipes:install vendor/package --force -v to update that recipe.';
102119
//$write[] = ' * composer symfony:recipes:blame to see a tree of all of the installed/updated files.';
103120
$write[] = '';
104121

105122
$this->getIO()->write($write);
106123

107124
return 0;
108125
}
126+
127+
private function displayPackageInformation(Recipe $recipe)
128+
{
129+
$recipeLock = $this->symfonyLock->get($recipe->getName());
130+
131+
$lockRef = $recipeLock['recipe']['ref'] ?? null;
132+
$lockFiles = $recipeLock['files'] ?? null;
133+
134+
$status = '<comment>up to date</comment>';
135+
if ($recipe->isAuto()) {
136+
$status = '<comment>auto-generated recipe</comment>';
137+
} elseif (null === $lockRef && null !== $recipe->getRef()) {
138+
$status = '<comment>recipe not installed</comment>';
139+
} elseif ($recipe->getRef() !== $lockRef) {
140+
$status = '<comment>update available</comment>';
141+
}
142+
143+
$io = $this->getIO();
144+
$io->write('<info>name</info> : '.$recipe->getName());
145+
$io->write('<info>version</info> : '.$recipeLock['version']);
146+
if (!$recipe->isAuto()) {
147+
$io->write('<info>repo</info> : '.sprintf('https://%s/tree/master/%s/%s', $recipeLock['recipe']['repo'], $recipe->getName(), $recipeLock['version']));
148+
}
149+
$io->write('<info>status</info> : '.$status);
150+
151+
if (null !== $lockFiles) {
152+
$io->write('<info>files</info> : ');
153+
$io->write('');
154+
155+
$tree = $this->generateFilesTree($lockFiles);
156+
157+
$this->displayFilesTree($tree);
158+
}
159+
160+
$io->write([
161+
'',
162+
'Update this recipe by running composer:',
163+
sprintf('<info>symfony:recipes:install %s --force -v</info>', $recipe->getName()),
164+
]);
165+
}
166+
167+
private function generateFilesTree(array $files): array
168+
{
169+
$tree = [];
170+
foreach ($files as $file) {
171+
$path = explode('/', $file);
172+
173+
$tree = array_merge_recursive($tree, $this->addNode($path));
174+
}
175+
176+
return $tree;
177+
}
178+
179+
private function addNode(array $node): array
180+
{
181+
$current = array_shift($node);
182+
183+
$subTree = [];
184+
if (null !== $current) {
185+
$subTree[$current] = $this->addNode($node);
186+
}
187+
188+
return $subTree;
189+
}
190+
191+
/**
192+
* Note : We do not display file modification information with Configurator like ComposerScripts, Container, DockerComposer, Dockerfile, Env, Gitignore and Makefile.
193+
*/
194+
private function displayFilesTree(array $tree)
195+
{
196+
$endKey = array_key_last($tree);
197+
foreach ($tree as $dir => $files) {
198+
$treeBar = '';
199+
$total = \count($files);
200+
if (0 === $total || $endKey === $dir) {
201+
$treeBar = '';
202+
}
203+
204+
$info = sprintf(
205+
'%s──%s',
206+
$treeBar,
207+
$dir
208+
);
209+
$this->writeTreeLine($info);
210+
211+
$treeBar = str_replace('', ' ', $treeBar);
212+
213+
$this->displayTree($files, $treeBar);
214+
}
215+
}
216+
217+
private function displayTree(array $tree, $previousTreeBar = '', $level = 1)
218+
{
219+
$previousTreeBar = str_replace('', '', $previousTreeBar);
220+
$treeBar = $previousTreeBar.'';
221+
222+
$i = 0;
223+
$total = \count($tree);
224+
225+
foreach ($tree as $dir => $files) {
226+
++$i;
227+
if ($i === $total) {
228+
$treeBar = $previousTreeBar.'';
229+
}
230+
231+
$info = sprintf(
232+
'%s──%s',
233+
$treeBar,
234+
$dir
235+
);
236+
$this->writeTreeLine($info);
237+
238+
$treeBar = str_replace('', ' ', $treeBar);
239+
240+
$this->displayTree($files, $treeBar, $level + 1);
241+
}
242+
}
243+
244+
private function writeTreeLine($line)
245+
{
246+
$io = $this->getIO();
247+
if (!$io->isDecorated()) {
248+
$line = str_replace(['', '', '──', ''], ['`-', '|-', '-', '|'], $line);
249+
}
250+
251+
$io->write($line);
252+
}
109253
}

src/Flex.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
243243
$app->add(new Command\UpdateCommand($resolver));
244244
$app->add(new Command\RemoveCommand($resolver));
245245
$app->add(new Command\UnpackCommand($resolver));
246-
$app->add(new Command\RecipesCommand($this));
246+
$app->add(new Command\RecipesCommand($this, $this->lock));
247247
$app->add(new Command\SyncRecipesCommand($this, $this->options->get('root-dir')));
248248
$app->add(new Command\GenerateIdCommand($this));
249249
$app->add(new Command\DumpEnvCommand($this->config, $this->options));

0 commit comments

Comments
 (0)