13
13
14
14
use Composer \Command \BaseCommand ;
15
15
use Composer \Factory ;
16
+ use Symfony \Component \Console \Input \InputArgument ;
16
17
use Symfony \Component \Console \Input \InputInterface ;
17
18
use Symfony \Component \Console \Output \OutputInterface ;
18
19
use Symfony \Flex \InformationOperation ;
@@ -24,30 +25,42 @@ class RecipesCommand extends BaseCommand
24
25
/** @var \Symfony\Flex\Flex */
25
26
private $ flex ;
26
27
28
+ /** @var Lock */
29
+ private $ symfonyLock ;
30
+
27
31
public function __construct (/* cannot be type-hinted */ $ flex )
28
32
{
29
33
$ this ->flex = $ flex ;
34
+ $ this ->symfonyLock = new Lock (getenv ('SYMFONY_LOCKFILE ' ) ?: str_replace ('composer.json ' , 'symfony.lock ' , Factory::getComposerFile ()));
30
35
31
36
parent ::__construct ();
32
37
}
33
38
34
39
protected function configure ()
35
40
{
36
- $ this ->setName ('symfony:recipes:show ' )
37
- ->setAliases (['symfony:recipes ' ])
41
+ $ this ->setName ('symfony:recipes ' )
38
42
->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
+ ])
39
46
;
40
47
}
41
48
42
49
protected function execute (InputInterface $ input , OutputInterface $ output )
43
50
{
44
- $ locker = $ this ->getComposer ()->getLocker ();
45
- $ lockData = $ locker ->getLockData ();
51
+ $ installedRepo = $ this ->getComposer ()->getRepositoryManager ()->getLocalRepository ();
46
52
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 ();
49
60
50
- $ installedRepo = $ this ->getComposer ()->getRepositoryManager ()->getLocalRepository ();
61
+ // Merge all packages installed
62
+ $ packages = array_merge ($ lockData ['packages ' ], $ lockData ['packages-dev ' ]);
63
+ }
51
64
52
65
$ operations = [];
53
66
foreach ($ packages as $ key => $ value ) {
@@ -63,12 +76,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
63
76
$ recipes = $ this ->flex ->fetchRecipes ($ operations );
64
77
ksort ($ recipes );
65
78
66
- if (\count ($ recipes ) <= 0 ) {
79
+ $ nbRecipe = \count ($ recipes );
80
+ if ($ nbRecipe <= 0 ) {
67
81
$ this ->getIO ()->writeError ('<error>No recipe found</error> ' );
68
82
69
83
return 1 ;
70
84
}
71
85
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
72
94
$ write = [
73
95
'' ,
74
96
'<bg=blue;fg=white> </> ' ,
@@ -77,27 +99,23 @@ protected function execute(InputInterface $input, OutputInterface $output)
77
99
'' ,
78
100
];
79
101
80
- $ symfonyLock = new Lock (getenv ('SYMFONY_LOCKFILE ' ) ?: str_replace ('composer.json ' , 'symfony.lock ' , Factory::getComposerFile ()));
81
-
82
102
/** @var Recipe $recipe */
83
103
foreach ($ recipes as $ name => $ recipe ) {
84
- $ lockRef = $ symfonyLock ->get ($ name )['recipe ' ]['ref ' ] ?? null ;
104
+ $ lockRef = $ this -> symfonyLock ->get ($ name )['recipe ' ]['ref ' ] ?? null ;
85
105
86
106
$ 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> ' ;
91
109
} elseif ($ recipe ->getRef () !== $ lockRef ) {
92
110
$ additional = '<comment>(update available)</comment> ' ;
93
111
}
94
112
$ write [] = sprintf (' * %s %s ' , $ name , $ additional );
95
113
}
96
114
97
115
// 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.';
116
+ $ write [] = '' ;
117
+ $ write [] = '<fg=blue>Run</>: ' ;
118
+ $ write [] = ' * composer symfony:recipes vendor/package to see details about a recipe. ' ;
101
119
//$write[] = ' * composer symfony:recipes:update vendor/package to update that recipe.';
102
120
//$write[] = ' * composer symfony:recipes:blame to see a tree of all of the installed/updated files.';
103
121
$ write [] = '' ;
@@ -106,4 +124,130 @@ protected function execute(InputInterface $input, OutputInterface $output)
106
124
107
125
return 0 ;
108
126
}
127
+
128
+ private function displayPackageInformation (Recipe $ recipe )
129
+ {
130
+ $ recipeLock = $ this ->symfonyLock ->get ($ recipe ->getName ());
131
+
132
+ $ lockRef = $ recipeLock ['recipe ' ]['ref ' ] ?? null ;
133
+ $ lockFiles = $ recipeLock ['files ' ] ?? null ;
134
+
135
+ $ status = '<comment>up to date</comment> ' ;
136
+ if ($ recipe ->isAuto ()) {
137
+ $ status = '<comment>auto-generated recipe</comment> ' ;
138
+ } elseif (null === $ lockRef && null !== $ recipe ->getRef ()) {
139
+ $ status = '<comment>recipe not installed</comment> ' ;
140
+ } elseif ($ recipe ->getRef () !== $ lockRef ) {
141
+ $ status = '<comment>upate available</comment> ' ;
142
+ }
143
+
144
+ $ io = $ this ->getIO ();
145
+ $ io ->write ('<info>name</info> : ' .$ recipe ->getName ());
146
+ $ io ->write ('<info>version</info> : ' .$ recipeLock ['version ' ]);
147
+ $ io ->write ('<info>repo</info> : ' .$ recipeLock ['recipe ' ]['repo ' ]);
148
+ $ io ->write ('<info>sha</info> : ' .$ lockRef );
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
+ // TODO : Uncomment the line when the feature will be implemented
161
+ //$io->write([
162
+ // '',
163
+ // 'Update this recipe by running composer symfony:recipe:update symfony/framework-bundle',
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
+ * TODO : 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
+ }
109
253
}
0 commit comments