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