19
19
use Symfony \Component \Console \Command \Command ;
20
20
use Symfony \Component \Console \Input \InputArgument ;
21
21
use Symfony \Component \Console \Input \InputInterface ;
22
+ use Symfony \Component \Console \Input \InputOption ;
22
23
use Symfony \Component \Console \Question \Question ;
23
24
use Symfony \UX \StimulusBundle \StimulusBundle ;
24
25
use Symfony \WebpackEncoreBundle \WebpackEncoreBundle ;
@@ -44,25 +45,34 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
44
45
{
45
46
$ command
46
47
->addArgument ('name ' , InputArgument::REQUIRED , 'The name of the Stimulus controller (e.g. <fg=yellow>hello</>) ' )
48
+ ->addOption ('typescript ' , 'ts ' , InputOption::VALUE_NONE , 'Create a TypeScript controller (default is JavaScript) ' )
47
49
->setHelp ($ this ->getHelpFileContents ('MakeStimulusController.txt ' ))
48
50
;
51
+
52
+ $ inputConfig ->setArgumentAsNonInteractive ('typescript ' );
49
53
}
50
54
51
55
public function interact (InputInterface $ input , ConsoleStyle $ io , Command $ command ): void
52
56
{
53
57
$ command ->addArgument ('extension ' , InputArgument::OPTIONAL );
54
58
$ command ->addArgument ('targets ' , InputArgument::OPTIONAL );
55
59
$ command ->addArgument ('values ' , InputArgument::OPTIONAL );
60
+ $ command ->addArgument ('classes ' , InputArgument::OPTIONAL );
61
+
62
+ if ($ input ->getOption ('typescript ' )) {
63
+ $ input ->setArgument ('extension ' , 'ts ' );
64
+ } else {
65
+ $ chosenExtension = $ io ->choice (
66
+ 'Language (<fg=yellow>JavaScript</> or <fg=yellow>TypeScript</>) ' ,
67
+ [
68
+ 'js ' => 'JavaScript ' ,
69
+ 'ts ' => 'TypeScript ' ,
70
+ ],
71
+ 'js ' ,
72
+ );
56
73
57
- $ chosenExtension = $ io ->choice (
58
- 'Language (<fg=yellow>JavaScript</> or <fg=yellow>TypeScript</>) ' ,
59
- [
60
- 'js ' => 'JavaScript ' ,
61
- 'ts ' => 'TypeScript ' ,
62
- ]
63
- );
64
-
65
- $ input ->setArgument ('extension ' , $ chosenExtension );
74
+ $ input ->setArgument ('extension ' , $ chosenExtension );
75
+ }
66
76
67
77
if ($ io ->confirm ('Do you want to include targets? ' )) {
68
78
$ targets = [];
@@ -98,16 +108,35 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
98
108
99
109
$ input ->setArgument ('values ' , $ values );
100
110
}
111
+
112
+ if ($ io ->confirm ('Do you want to add classes? ' , false )) {
113
+ $ classes = [];
114
+ $ isFirstClass = true ;
115
+
116
+ while (true ) {
117
+ $ newClass = $ this ->askForNextClass ($ io , $ classes , $ isFirstClass );
118
+ if (null === $ newClass ) {
119
+ break ;
120
+ }
121
+
122
+ $ isFirstClass = false ;
123
+ $ classes [] = $ newClass ;
124
+ }
125
+
126
+ $ input ->setArgument ('classes ' , $ classes );
127
+ }
101
128
}
102
129
103
130
public function generate (InputInterface $ input , ConsoleStyle $ io , Generator $ generator ): void
104
131
{
105
132
$ controllerName = Str::asSnakeCase ($ input ->getArgument ('name ' ));
106
133
$ chosenExtension = $ input ->getArgument ('extension ' );
107
- $ targets = $ input ->getArgument ('targets ' );
108
- $ values = $ input ->getArgument ('values ' );
134
+ $ targets = $ targetArgs = $ input ->getArgument ('targets ' ) ?? [];
135
+ $ values = $ valuesArg = $ input ->getArgument ('values ' ) ?? [];
136
+ $ classes = $ classesArgs = $ input ->getArgument ('classes ' ) ?? [];
109
137
110
138
$ targets = empty ($ targets ) ? $ targets : \sprintf ("['%s'] " , implode ("', ' " , $ targets ));
139
+ $ classes = $ classes ? \sprintf ("['%s'] " , implode ("', ' " , $ classes )) : null ;
111
140
112
141
$ fileName = \sprintf ('%s_controller.%s ' , $ controllerName , $ chosenExtension );
113
142
$ filePath = \sprintf ('assets/controllers/%s ' , $ fileName );
@@ -118,6 +147,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
118
147
[
119
148
'targets ' => $ targets ,
120
149
'values ' => $ values ,
150
+ 'classes ' => $ classes ,
121
151
]
122
152
);
123
153
@@ -128,7 +158,12 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
128
158
$ io ->text ([
129
159
'Next: ' ,
130
160
\sprintf ('- Open <info>%s</info> and add the code you need ' , $ filePath ),
131
- 'Find the documentation at <fg=yellow>https://github.com/symfony/stimulus-bridge</> ' ,
161
+ '- Use the controller in your templates: ' ,
162
+ ...array_map (
163
+ fn (string $ line ): string => " $ line " ,
164
+ explode ("\n" , $ this ->generateUsageExample ($ controllerName , $ targetArgs , $ valuesArg , $ classesArgs )),
165
+ ),
166
+ 'Find the documentation at <fg=yellow>https://symfony.com/bundles/StimulusBundle</> ' ,
132
167
]);
133
168
}
134
169
@@ -215,6 +250,29 @@ private function askForNextValue(ConsoleStyle $io, array $values, bool $isFirstV
215
250
return ['name ' => $ valueName , 'type ' => $ type ];
216
251
}
217
252
253
+ /** @param string[] $classes */
254
+ private function askForNextClass (ConsoleStyle $ io , array $ classes , bool $ isFirstClass ): ?string
255
+ {
256
+ $ questionText = 'New class name (press <return> to stop adding classes) ' ;
257
+
258
+ if (!$ isFirstClass ) {
259
+ $ questionText = 'Add another class? Enter the class name (or press <return> to stop adding classes) ' ;
260
+ }
261
+
262
+ $ className = $ io ->ask ($ questionText , validator: function (?string $ name ) use ($ classes ) {
263
+ if (str_contains ($ name , ' ' )) {
264
+ throw new \InvalidArgumentException ('Class name cannot contain spaces. ' );
265
+ }
266
+ if (\in_array ($ name , $ classes , true )) {
267
+ throw new \InvalidArgumentException (\sprintf ('The "%s" class already exists. ' , $ name ));
268
+ }
269
+
270
+ return $ name ;
271
+ });
272
+
273
+ return $ className ?: null ;
274
+ }
275
+
218
276
private function printAvailableTypes (ConsoleStyle $ io ): void
219
277
{
220
278
foreach ($ this ->getValuesTypes () as $ type ) {
@@ -234,6 +292,51 @@ private function getValuesTypes(): array
234
292
];
235
293
}
236
294
295
+ /**
296
+ * @param array<int, string> $targets
297
+ * @param array<array{name: string, type: string}> $values
298
+ * @param array<int, string> $classes
299
+ */
300
+ private function generateUsageExample (string $ name , array $ targets , array $ values , array $ classes ): string
301
+ {
302
+ $ slugify = fn (string $ name ) => str_replace ('_ ' , '- ' , Str::asSnakeCase ($ name ));
303
+ $ controller = $ slugify ($ name );
304
+
305
+ $ htmlTargets = [];
306
+ foreach ($ targets as $ target ) {
307
+ $ htmlTargets [] = \sprintf ('<div data-%s-target="%s"></div> ' , $ controller , $ target );
308
+ }
309
+
310
+ $ htmlValues = [];
311
+ foreach ($ values as ['name ' => $ name , 'type ' => $ type ]) {
312
+ $ value = match ($ type ) {
313
+ 'Array ' => '[] ' ,
314
+ 'Boolean ' => 'false ' ,
315
+ 'Number ' => '123 ' ,
316
+ 'Object ' => '{} ' ,
317
+ 'String ' => 'abc ' ,
318
+ default => '' ,
319
+ };
320
+ $ htmlValues [] = \sprintf ('data-%s-%s-value="%s" ' , $ controller , $ slugify ($ name ), $ value );
321
+ }
322
+
323
+ $ htmlClasses = [];
324
+ foreach ($ classes as $ class ) {
325
+ $ value = Str::asLowerCamelCase ($ class );
326
+ $ htmlClasses [] = \sprintf ('data-%s-%s-class="%s" ' , $ controller , $ slugify ($ class ), $ value );
327
+ }
328
+
329
+ return \sprintf (
330
+ '<div data-controller="%s"%s%s%s>%s%s</div> ' ,
331
+ $ controller ,
332
+ $ htmlValues ? ("\n " .implode ("\n " , $ htmlValues )) : '' ,
333
+ $ htmlClasses ? ("\n " .implode ("\n " , $ htmlClasses )) : '' ,
334
+ ($ htmlValues || $ htmlClasses ) ? "\n" : '' ,
335
+ $ htmlTargets ? ("\n " .implode ("\n " , $ htmlTargets )) : '' ,
336
+ "\n <!-- ... --> \n" ,
337
+ );
338
+ }
339
+
237
340
public function configureDependencies (DependencyBuilder $ dependencies ): void
238
341
{
239
342
// lower than 8.1, allow WebpackEncoreBundle
0 commit comments