@@ -61,11 +61,24 @@ class OutputHelper
61
61
{
62
62
use InflectsString;
63
63
64
+ /**
65
+ * The output writer instance used to write formatted output.
66
+ *
67
+ * @var Writer
68
+ */
64
69
protected Writer $ writer ;
65
-
66
- /** @var int Max width of command name */
70
+ /**
71
+ * Max width of command name.
72
+ *
73
+ * @var int
74
+ */
67
75
protected int $ maxCmdName = 0 ;
68
76
77
+ /**
78
+ * Class constructor.
79
+ *
80
+ * @param Writer|null $writer The output writer instance used to write formatted output.
81
+ */
69
82
public function __construct (?Writer $ writer = null )
70
83
{
71
84
$ this ->writer = $ writer ?? new Writer ;
@@ -79,7 +92,7 @@ public function printTrace(Throwable $e): void
79
92
$ eClass = get_class ($ e );
80
93
81
94
$ this ->writer ->colors (
82
- "{ $ eClass} <red> {$ e ->getMessage ()}</end><eol/> " .
95
+ "$ eClass <red> {$ e ->getMessage ()}</end><eol/> " .
83
96
'( ' . t ('thrown in ' ) . " <yellow> {$ e ->getFile ()}</end><white>: {$ e ->getLine ()})</end> "
84
97
);
85
98
@@ -107,6 +120,19 @@ public function printTrace(Throwable $e): void
107
120
$ this ->writer ->colors ($ traceStr );
108
121
}
109
122
123
+ /**
124
+ * Converts an array of arguments into a string representation.
125
+ *
126
+ * Each array element is converted based on its type:
127
+ * - Scalar values (int, float, string, bool) are var_exported
128
+ * - Objects are converted using __toString() if available, otherwise class name is used
129
+ * - Arrays are recursively processed and wrapped in square brackets
130
+ * - Other types are converted to their type name
131
+ *
132
+ * @param array $args Array of arguments to be stringified
133
+ *
134
+ * @return string The comma-separated string representation of all arguments
135
+ */
110
136
public function stringifyArgs (array $ args ): string
111
137
{
112
138
$ holder = [];
@@ -118,7 +144,14 @@ public function stringifyArgs(array $args): string
118
144
return implode (', ' , $ holder );
119
145
}
120
146
121
- protected function stringifyArg ($ arg ): string
147
+ /**
148
+ * Converts the provided argument into a string representation.
149
+ *
150
+ * @param mixed $arg The argument to be converted into a string. This can be of any type.
151
+ *
152
+ * @return string A string representation of the provided argument.
153
+ */
154
+ protected function stringifyArg (mixed $ arg ): string
122
155
{
123
156
if (is_scalar ($ arg )) {
124
157
return var_export ($ arg , true );
@@ -196,15 +229,17 @@ protected function showHelp(string $for, array $items, string $header = '', stri
196
229
return ;
197
230
}
198
231
199
- $ space = 4 ;
200
- $ group = $ lastGroup = null ;
232
+ $ space = 4 ;
233
+ $ lastGroup = null ;
201
234
202
235
$ withDefault = $ for === 'Options ' || $ for === 'Arguments ' ;
203
236
foreach (array_values ($ this ->sortItems ($ items , $ padLen , $ for )) as $ idx => $ item ) {
204
237
$ name = $ this ->getName ($ item );
205
238
if ($ for === 'Commands ' && $ lastGroup !== $ group = $ item ->group ()) {
206
- $ this ->writer ->help_group ($ group ?: '* ' , true );
207
239
$ lastGroup = $ group ;
240
+ if ($ group !== '' ) {
241
+ $ this ->writer ->help_group ($ group , true );
242
+ }
208
243
}
209
244
$ desc = str_replace (["\r\n" , "\n" ], str_pad ("\n" , $ padLen + $ space + 3 ), $ item ->desc ($ withDefault ));
210
245
@@ -254,12 +289,21 @@ public function showUsage(string $usage): self
254
289
return $ this ;
255
290
}
256
291
292
+ /**
293
+ * Shows an error message when a command is not found and suggests similar commands.
294
+ * Uses levenshtein distance to find commands that are similar to the attempted one.
295
+ *
296
+ * @param string $attempted The command name that was attempted to be executed
297
+ * @param array $available List of available command names
298
+ *
299
+ * @return OutputHelper For method chaining
300
+ */
257
301
public function showCommandNotFound (string $ attempted , array $ available ): self
258
302
{
259
303
$ closest = [];
260
304
foreach ($ available as $ cmd ) {
261
305
$ lev = levenshtein ($ attempted , $ cmd );
262
- if ($ lev > 0 || $ lev < 5 ) {
306
+ if ($ lev > 0 && $ lev < 5 ) {
263
307
$ closest [$ cmd ] = $ lev ;
264
308
}
265
309
}
@@ -278,12 +322,12 @@ public function showCommandNotFound(string $attempted, array $available): self
278
322
* Sort items by name. As a side effect sets max length of all names.
279
323
*
280
324
* @param Parameter[]|Command[] $items
281
- * @param int $max
325
+ * @param int|null $max
282
326
* @param string $for
283
327
*
284
328
* @return array
285
329
*/
286
- protected function sortItems (array $ items , &$ max = 0 , string $ for = '' ): array
330
+ protected function sortItems (array $ items , ? int &$ max = 0 , string $ for = '' ): array
287
331
{
288
332
$ max = max (array_map (fn ($ item ) => strlen ($ this ->getName ($ item )), $ items ));
289
333
@@ -292,8 +336,10 @@ protected function sortItems(array $items, &$max = 0, string $for = ''): array
292
336
}
293
337
294
338
uasort ($ items , static function ($ a , $ b ) {
295
- $ aName = $ a instanceof Groupable ? $ a ->group () . $ a ->name () : $ a ->name ();
296
- $ bName = $ b instanceof Groupable ? $ b ->group () . $ b ->name () : $ b ->name ();
339
+ // Items in the default group (where group() returns empty/falsy) are prefixed with '__'
340
+ // to ensure they appear at the top of the sorted list, whilst grouped items follow after
341
+ $ aName = $ a instanceof Groupable ? ($ a ->group () ?: '__ ' ) . $ a ->name () : $ a ->name ();
342
+ $ bName = $ b instanceof Groupable ? ($ b ->group () ?: '__ ' ) . $ b ->name () : $ b ->name ();
297
343
298
344
return $ aName <=> $ bName ;
299
345
});
@@ -308,7 +354,7 @@ protected function sortItems(array $items, &$max = 0, string $for = ''): array
308
354
*
309
355
* @return string
310
356
*/
311
- protected function getName ($ item ): string
357
+ protected function getName (Parameter | Command $ item ): string
312
358
{
313
359
$ name = $ item ->name ();
314
360
0 commit comments