Skip to content

Commit 763569e

Browse files
acataluddiadhocore
andauthored
Improve the visualisation of commands in the default group (#126)
* Configured PhpUnit cache file path for a less polluted root folder. Removed the "*" label for the default group. * Commands in the default group gets printed first, without an empty grouping section. * Added comments to the OutputHelper's class. Moved protected methods after the public ones. * Removed in-line if statement - Fixed #125. * Minor code formatting to comply with StyleCI. * Minor code formatting to comply with StyleCI. * Minor code formatting to comply with StyleCI. * Restored original method positions to facilitate easier change comparisons #125. --------- Co-authored-by: Jitendra Adhikari <2908547+adhocore@users.noreply.github.com>
1 parent b7a1aa7 commit 763569e

File tree

3 files changed

+76
-28
lines changed

3 files changed

+76
-28
lines changed

phpunit.xml.dist

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
backupGlobals="false"
4-
backupStaticAttributes="false"
5-
colors="true"
6-
convertErrorsToExceptions="true"
7-
convertNoticesToExceptions="true"
8-
convertWarningsToExceptions="true"
9-
processIsolation="true"
10-
stopOnFailure="false"
11-
bootstrap="tests/bootstrap.php"
12-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
3+
backupGlobals="false"
4+
backupStaticAttributes="false"
5+
colors="true"
6+
convertErrorsToExceptions="true"
7+
convertNoticesToExceptions="true"
8+
convertWarningsToExceptions="true"
9+
processIsolation="true"
10+
stopOnFailure="false"
11+
bootstrap="tests/bootstrap.php"
12+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
13+
cacheResultFile="./tests/data/cache/.phpunit.result.cache"
14+
>
1315
<coverage processUncoveredFiles="true">
1416
<include>
1517
<directory suffix=".php">./src</directory>

src/Helper/OutputHelper.php

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,24 @@ class OutputHelper
6161
{
6262
use InflectsString;
6363

64+
/**
65+
* The output writer instance used to write formatted output.
66+
*
67+
* @var Writer
68+
*/
6469
protected Writer $writer;
65-
66-
/** @var int Max width of command name */
70+
/**
71+
* Max width of command name.
72+
*
73+
* @var int
74+
*/
6775
protected int $maxCmdName = 0;
6876

77+
/**
78+
* Class constructor.
79+
*
80+
* @param Writer|null $writer The output writer instance used to write formatted output.
81+
*/
6982
public function __construct(?Writer $writer = null)
7083
{
7184
$this->writer = $writer ?? new Writer;
@@ -79,7 +92,7 @@ public function printTrace(Throwable $e): void
7992
$eClass = get_class($e);
8093

8194
$this->writer->colors(
82-
"{$eClass} <red>{$e->getMessage()}</end><eol/>" .
95+
"$eClass <red>{$e->getMessage()}</end><eol/>" .
8396
'(' . t('thrown in') . " <yellow>{$e->getFile()}</end><white>:{$e->getLine()})</end>"
8497
);
8598

@@ -107,6 +120,19 @@ public function printTrace(Throwable $e): void
107120
$this->writer->colors($traceStr);
108121
}
109122

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+
*/
110136
public function stringifyArgs(array $args): string
111137
{
112138
$holder = [];
@@ -118,7 +144,14 @@ public function stringifyArgs(array $args): string
118144
return implode(', ', $holder);
119145
}
120146

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
122155
{
123156
if (is_scalar($arg)) {
124157
return var_export($arg, true);
@@ -196,15 +229,17 @@ protected function showHelp(string $for, array $items, string $header = '', stri
196229
return;
197230
}
198231

199-
$space = 4;
200-
$group = $lastGroup = null;
232+
$space = 4;
233+
$lastGroup = null;
201234

202235
$withDefault = $for === 'Options' || $for === 'Arguments';
203236
foreach (array_values($this->sortItems($items, $padLen, $for)) as $idx => $item) {
204237
$name = $this->getName($item);
205238
if ($for === 'Commands' && $lastGroup !== $group = $item->group()) {
206-
$this->writer->help_group($group ?: '*', true);
207239
$lastGroup = $group;
240+
if ($group !== '') {
241+
$this->writer->help_group($group, true);
242+
}
208243
}
209244
$desc = str_replace(["\r\n", "\n"], str_pad("\n", $padLen + $space + 3), $item->desc($withDefault));
210245

@@ -254,12 +289,21 @@ public function showUsage(string $usage): self
254289
return $this;
255290
}
256291

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+
*/
257301
public function showCommandNotFound(string $attempted, array $available): self
258302
{
259303
$closest = [];
260304
foreach ($available as $cmd) {
261305
$lev = levenshtein($attempted, $cmd);
262-
if ($lev > 0 || $lev < 5) {
306+
if ($lev > 0 && $lev < 5) {
263307
$closest[$cmd] = $lev;
264308
}
265309
}
@@ -278,12 +322,12 @@ public function showCommandNotFound(string $attempted, array $available): self
278322
* Sort items by name. As a side effect sets max length of all names.
279323
*
280324
* @param Parameter[]|Command[] $items
281-
* @param int $max
325+
* @param int|null $max
282326
* @param string $for
283327
*
284328
* @return array
285329
*/
286-
protected function sortItems(array $items, &$max = 0, string $for = ''): array
330+
protected function sortItems(array $items, ?int &$max = 0, string $for = ''): array
287331
{
288332
$max = max(array_map(fn ($item) => strlen($this->getName($item)), $items));
289333

@@ -292,8 +336,10 @@ protected function sortItems(array $items, &$max = 0, string $for = ''): array
292336
}
293337

294338
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();
297343

298344
return $aName <=> $bName;
299345
});
@@ -308,7 +354,7 @@ protected function sortItems(array $items, &$max = 0, string $for = ''): array
308354
*
309355
* @return string
310356
*/
311-
protected function getName($item): string
357+
protected function getName(Parameter|Command $item): string
312358
{
313359
$name = $item->name();
314360

tests/Helper/OutputHelperTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
class OutputHelperTest extends TestCase
3030
{
31-
protected static $ou = __DIR__ . '/output';
31+
protected static string $ou = __DIR__ . '/output';
3232

3333
public function setUp(): void
3434
{
@@ -88,16 +88,16 @@ public function test_show_commands()
8888
new Command('group:mkdir', 'Make a folder'),
8989
], 'Cmd Header', 'Cmd Footer');
9090

91+
// If the default group exists, we expect visually to be rendered at the very top.
9192
$this->assertSame([
9293
'Cmd Header',
9394
'',
9495
'Commands:',
96+
' mkdir Make a folder',
97+
' rm Remove file or folder',
9598
'group',
9699
' group:mkdir Make a folder',
97100
' group:rm Remove file or folder',
98-
'*',
99-
' mkdir Make a folder',
100-
' rm Remove file or folder',
101101
'',
102102
'Cmd Footer',
103103
], $this->output());
@@ -150,7 +150,7 @@ public function test_stringify()
150150
$this->assertSame("[NULL, 'string', 10000, 12.345, DateTime]", $str);
151151
}
152152

153-
public function newHelper()
153+
public function newHelper(): OutputHelper
154154
{
155155
return new OutputHelper(new Writer(static::$ou, new class extends Color {
156156
protected string $format = ':txt:';

0 commit comments

Comments
 (0)