12
12
namespace Symfony \Flex \Command ;
13
13
14
14
use Composer \Command \BaseCommand ;
15
- use Composer \ Factory ;
15
+ use Symfony \ Component \ Console \ Input \ InputArgument ;
16
16
use Symfony \Component \Console \Input \InputInterface ;
17
17
use Symfony \Component \Console \Output \OutputInterface ;
18
18
use Symfony \Flex \InformationOperation ;
19
19
use Symfony \Flex \Lock ;
20
20
use Symfony \Flex \Recipe ;
21
21
22
+ /**
23
+ * @author Maxime Hélias <[email protected] >
24
+ */
22
25
class RecipesCommand extends BaseCommand
23
26
{
24
27
/** @var \Symfony\Flex\Flex */
25
28
private $ flex ;
26
29
27
- public function __construct (/* cannot be type-hinted */ $ flex )
30
+ /** @var Lock */
31
+ private $ symfonyLock ;
32
+
33
+ public function __construct (/* cannot be type-hinted */ $ flex , Lock $ symfonyLock )
28
34
{
29
35
$ this ->flex = $ flex ;
36
+ $ this ->symfonyLock = $ symfonyLock ;
30
37
31
38
parent ::__construct ();
32
39
}
33
40
34
41
protected function configure ()
35
42
{
36
- $ this ->setName ('symfony:recipes:show ' )
37
- ->setAliases (['symfony: recipes ' ])
43
+ $ this ->setName ('symfony:recipes ' )
44
+ ->setAliases (['recipes ' ])
38
45
->setDescription ('Shows information about all available recipes. ' )
46
+ ->setDefinition ([
47
+ new InputArgument ('package ' , InputArgument::OPTIONAL , 'Package to inspect, if not provided all packages are. ' ),
48
+ ])
39
49
;
40
50
}
41
51
42
52
protected function execute (InputInterface $ input , OutputInterface $ output )
43
53
{
44
- $ locker = $ this ->getComposer ()->getLocker ();
45
- $ lockData = $ locker ->getLockData ();
54
+ $ installedRepo = $ this ->getComposer ()->getRepositoryManager ()->getLocalRepository ();
46
55
47
- // Merge all packages installed
48
- $ packages = array_merge ($ lockData ['packages ' ], $ lockData ['packages-dev ' ]);
56
+ // Inspect one or all packages
57
+ $ package = $ input ->getArgument ('package ' );
58
+ if (null !== $ package ) {
59
+ $ packages = [0 => ['name ' => strtolower ($ package )]];
60
+ } else {
61
+ $ locker = $ this ->getComposer ()->getLocker ();
62
+ $ lockData = $ locker ->getLockData ();
49
63
50
- $ installedRepo = $ this ->getComposer ()->getRepositoryManager ()->getLocalRepository ();
64
+ // Merge all packages installed
65
+ $ packages = array_merge ($ lockData ['packages ' ], $ lockData ['packages-dev ' ]);
66
+ }
51
67
52
68
$ operations = [];
53
- foreach ($ packages as $ key => $ value ) {
69
+ foreach ($ packages as $ value ) {
54
70
if (null === $ pkg = $ installedRepo ->findPackage ($ value ['name ' ], '* ' )) {
55
71
$ this ->getIO ()->writeError (sprintf ('<error>Package %s is not installed</error> ' , $ value ['name ' ]));
56
72
@@ -63,12 +79,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
63
79
$ recipes = $ this ->flex ->fetchRecipes ($ operations );
64
80
ksort ($ recipes );
65
81
66
- if (\count ($ recipes ) <= 0 ) {
82
+ $ nbRecipe = \count ($ recipes );
83
+ if ($ nbRecipe <= 0 ) {
67
84
$ this ->getIO ()->writeError ('<error>No recipe found</error> ' );
68
85
69
86
return 1 ;
70
87
}
71
88
89
+ // Display the information about a specific recipe
90
+ if (1 === $ nbRecipe ) {
91
+ $ this ->displayPackageInformation (current ($ recipes ));
92
+
93
+ return 0 ;
94
+ }
95
+
96
+ // display a resume of all packages
72
97
$ write = [
73
98
'' ,
74
99
'<bg=blue;fg=white> </> ' ,
@@ -77,33 +102,155 @@ protected function execute(InputInterface $input, OutputInterface $output)
77
102
'' ,
78
103
];
79
104
80
- $ symfonyLock = new Lock (getenv ('SYMFONY_LOCKFILE ' ) ?: str_replace ('composer.json ' , 'symfony.lock ' , Factory::getComposerFile ()));
81
-
82
105
/** @var Recipe $recipe */
83
106
foreach ($ recipes as $ name => $ recipe ) {
84
- $ lockRef = $ symfonyLock ->get ($ name )['recipe ' ]['ref ' ] ?? null ;
107
+ $ lockRef = $ this -> symfonyLock ->get ($ name )['recipe ' ]['ref ' ] ?? null ;
85
108
86
109
$ 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> ' ;
110
+ if (null === $ lockRef && null !== $ recipe ->getRef ()) {
111
+ $ additional = '<comment>(recipe not installed)</comment> ' ;
91
112
} elseif ($ recipe ->getRef () !== $ lockRef ) {
92
113
$ additional = '<comment>(update available)</comment> ' ;
93
114
}
94
115
$ write [] = sprintf (' * %s %s ' , $ name , $ additional );
95
116
}
96
117
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.';
118
+ $ write [] = '' ;
119
+ $ write [] = '<fg=blue>Run</>: ' ;
120
+ $ write [] = ' * composer symfony:recipes vendor/package to see details about a recipe. ' ;
121
+ $ write [] = ' * composer symfony:recipes:install vendor/package --force -v to update that recipe. ' ;
102
122
//$write[] = ' * composer symfony:recipes:blame to see a tree of all of the installed/updated files.';
103
123
$ write [] = '' ;
104
124
105
125
$ this ->getIO ()->write ($ write );
106
126
107
127
return 0 ;
108
128
}
129
+
130
+ private function displayPackageInformation (Recipe $ recipe )
131
+ {
132
+ $ recipeLock = $ this ->symfonyLock ->get ($ recipe ->getName ());
133
+
134
+ $ lockRef = $ recipeLock ['recipe ' ]['ref ' ] ?? null ;
135
+ $ lockFiles = $ recipeLock ['files ' ] ?? null ;
136
+
137
+ $ status = '<comment>up to date</comment> ' ;
138
+ if ($ recipe ->isAuto ()) {
139
+ $ status = '<comment>auto-generated recipe</comment> ' ;
140
+ } elseif (null === $ lockRef && null !== $ recipe ->getRef ()) {
141
+ $ status = '<comment>recipe not installed</comment> ' ;
142
+ } elseif ($ recipe ->getRef () !== $ lockRef ) {
143
+ $ status = '<comment>update available</comment> ' ;
144
+ }
145
+
146
+ $ io = $ this ->getIO ();
147
+ $ io ->write ('<info>name</info> : ' .$ recipe ->getName ());
148
+ $ io ->write ('<info>version</info> : ' .$ recipeLock ['version ' ]);
149
+ if (!$ recipe ->isAuto ()) {
150
+ $ io ->write ('<info>repo</info> : ' .sprintf ('https://%s/tree/master/%s/%s ' , $ recipeLock ['recipe ' ]['repo ' ], $ recipe ->getName (), $ recipeLock ['version ' ]));
151
+ }
152
+ $ io ->write ('<info>status</info> : ' .$ status );
153
+
154
+ if (null !== $ lockFiles ) {
155
+ $ io ->write ('<info>files</info> : ' );
156
+ $ io ->write ('' );
157
+
158
+ $ tree = $ this ->generateFilesTree ($ lockFiles );
159
+
160
+ $ this ->displayFilesTree ($ tree );
161
+ }
162
+
163
+ $ io ->write ([
164
+ '' ,
165
+ 'Update this recipe by running composer: ' ,
166
+ sprintf ('<info>symfony:recipes:install %s --force -v</info> ' , $ recipe ->getName ()),
167
+ ]);
168
+ }
169
+
170
+ private function generateFilesTree (array $ files ): array
171
+ {
172
+ $ tree = [];
173
+ foreach ($ files as $ file ) {
174
+ $ path = explode ('/ ' , $ file );
175
+
176
+ $ tree = array_merge_recursive ($ tree , $ this ->addNode ($ path ));
177
+ }
178
+
179
+ return $ tree ;
180
+ }
181
+
182
+ private function addNode (array $ node ): array
183
+ {
184
+ $ current = array_shift ($ node );
185
+
186
+ $ subTree = [];
187
+ if (null !== $ current ) {
188
+ $ subTree [$ current ] = $ this ->addNode ($ node );
189
+ }
190
+
191
+ return $ subTree ;
192
+ }
193
+
194
+ /**
195
+ * Note : We do not display file modification information with Configurator like ComposerScripts, Container, DockerComposer, Dockerfile, Env, Gitignore and Makefile.
196
+ */
197
+ private function displayFilesTree (array $ tree )
198
+ {
199
+ $ endKey = array_key_last ($ tree );
200
+ foreach ($ tree as $ dir => $ files ) {
201
+ $ treeBar = '├ ' ;
202
+ $ total = \count ($ files );
203
+ if (0 === $ total || $ endKey === $ dir ) {
204
+ $ treeBar = '└ ' ;
205
+ }
206
+
207
+ $ info = sprintf (
208
+ '%s──%s ' ,
209
+ $ treeBar ,
210
+ $ dir
211
+ );
212
+ $ this ->writeTreeLine ($ info );
213
+
214
+ $ treeBar = str_replace ('└ ' , ' ' , $ treeBar );
215
+
216
+ $ this ->displayTree ($ files , $ treeBar );
217
+ }
218
+ }
219
+
220
+ private function displayTree (array $ tree , $ previousTreeBar = '├ ' , $ level = 1 )
221
+ {
222
+ $ previousTreeBar = str_replace ('├ ' , '│ ' , $ previousTreeBar );
223
+ $ treeBar = $ previousTreeBar .' ├ ' ;
224
+
225
+ $ i = 0 ;
226
+ $ total = \count ($ tree );
227
+
228
+ foreach ($ tree as $ dir => $ files ) {
229
+ ++$ i ;
230
+ if ($ i === $ total ) {
231
+ $ treeBar = $ previousTreeBar .' └ ' ;
232
+ }
233
+
234
+ $ info = sprintf (
235
+ '%s──%s ' ,
236
+ $ treeBar ,
237
+ $ dir
238
+ );
239
+ $ this ->writeTreeLine ($ info );
240
+
241
+ $ treeBar = str_replace ('└ ' , ' ' , $ treeBar );
242
+
243
+ $ this ->displayTree ($ files , $ treeBar , $ level + 1 );
244
+ }
245
+ }
246
+
247
+ private function writeTreeLine ($ line )
248
+ {
249
+ $ io = $ this ->getIO ();
250
+ if (!$ io ->isDecorated ()) {
251
+ $ line = str_replace (['└ ' , '├ ' , '── ' , '│ ' ], ['`- ' , '|- ' , '- ' , '| ' ], $ line );
252
+ }
253
+
254
+ $ io ->write ($ line );
255
+ }
109
256
}
0 commit comments