Skip to content

Commit a8b00a4

Browse files
committed
fix: output proper styling tags for help when using Composer
1 parent b277e2e commit a8b00a4

File tree

5 files changed

+104
-39
lines changed

5 files changed

+104
-39
lines changed

src/Command/Command.php

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Console\Input\InputInterface;
1919
use Symfony\Component\Console\Output\OutputInterface;
2020

21+
use function array_column;
2122
use function assert;
2223
use function basename;
2324
use function filter_var;
@@ -37,6 +38,21 @@ abstract class Command extends SymfonyCommand
3738
{
3839
private const WRAP_WIDTH = 78;
3940

41+
private const CUSTOM_HELP_TAGS = [
42+
[
43+
'pattern' => '/<code>(.*)<\/(?:code)?>/Us',
44+
'replacement' => '<fg=bright-blue>$1</>',
45+
],
46+
[
47+
'pattern' => '/<file>(.*)<\/(?:file)?>/Us',
48+
'replacement' => '<fg=bright-magenta>$1</>',
49+
],
50+
[
51+
'pattern' => '/<link>(.*)<\/(?:link)?>/Us',
52+
'replacement' => '<fg=cyan;options=underscore>$1</>',
53+
],
54+
];
55+
4056
private ?EventDispatcher $eventDispatcher = null;
4157
private readonly ExtraConfiguration $extra;
4258

@@ -82,20 +98,24 @@ final protected function execute(InputInterface $input, OutputInterface $output)
8298
return $exitCode + $this->eventDispatcher->dispatchScript((string) $this->getName());
8399
}
84100

85-
public function setHelp(string $help): static
101+
public function getHelp(): string
86102
{
87-
$name = (string) $this->getName();
88-
89-
// The Symfony method Command::getParsedHelp() already does this, but
90-
// we do it here to account for proper line wrapping in wrapHelp().
91-
$placeholders = ['%command.name%', '%command.full_name%'];
92-
93-
// phpcs:ignore SlevomatCodingStandard.Variables.DisallowSuperGlobalVariable
94-
$replacements = [$name, trim(basename(($_SERVER['PHP_SELF'] ?? '')) . ' ' . $name)];
95-
96-
$help = str_replace($placeholders, $replacements, $help ?: $this->getDescription());
103+
return $this->wrapHelp(
104+
$this->replaceHelpTokens(
105+
parent::getHelp(),
106+
(string) $this->getName(),
107+
),
108+
);
109+
}
97110

98-
return parent::setHelp($this->wrapHelp($help));
111+
public function getHelpForComposer(): string
112+
{
113+
return $this->wrapHelp(
114+
$this->replaceHelpTokens(
115+
parent::getHelp(),
116+
$this->getExtra()->getPrefixedCommandName(),
117+
),
118+
);
99119
}
100120

101121
private function wrapHelp(string $message): string
@@ -187,4 +207,26 @@ private function buildComposerExtraConfiguration(): ExtraConfiguration
187207
memoryLimit: $commandConfig['memory-limit'] ?? $config['memory-limit'] ?? null,
188208
);
189209
}
210+
211+
private function replaceHelpTokens(string $helpText, string $commandName): string
212+
{
213+
// The Symfony method Command::getParsedHelp() already does this, but
214+
// we do it here to account for proper line wrapping in wrapHelp().
215+
$placeholders = ['%command.name%', '%command.full_name%'];
216+
217+
// phpcs:ignore SlevomatCodingStandard.Variables.DisallowSuperGlobalVariable
218+
$replacements = [$commandName, trim(basename(($_SERVER['PHP_SELF'] ?? '')) . ' ' . $commandName)];
219+
220+
$helpText = str_replace($placeholders, $replacements, $helpText ?: $this->getDescription());
221+
222+
// We could use OutputFormatterStyle for this, but when running in the
223+
// context of a Composer plugin, we're not able to apply those styles
224+
// to the Composer console application, so we must use replacements
225+
// instead.
226+
return (string) preg_replace(
227+
array_column(self::CUSTOM_HELP_TAGS, 'pattern'),
228+
array_column(self::CUSTOM_HELP_TAGS, 'replacement'),
229+
$helpText,
230+
);
231+
}
190232
}

src/Composer/ComposerCommand.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public function __construct(Command $wrappedCommand)
2525
parent::__construct($this->wrappedCommand->getExtra()->getPrefixedCommandName());
2626
}
2727

28+
public function getHelp(): string
29+
{
30+
return $this->wrappedCommand->getHelpForComposer();
31+
}
32+
2833
protected function configure(): void
2934
{
3035
/** @var string[] $aliases */
@@ -33,7 +38,6 @@ protected function configure(): void
3338
$this
3439
->setAliases($aliases)
3540
->setDescription($this->wrappedCommand->getDescription())
36-
->setHelp($this->wrappedCommand->getHelp())
3741
->setDefinition($this->wrappedCommand->getDefinition())
3842
->setHidden($this->wrappedCommand->isHidden());
3943

src/DevToolsApplication.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
namespace Ramsey\Dev\Tools;
1313

1414
use Symfony\Component\Console\Application;
15-
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
16-
use Symfony\Component\Console\Input\InputInterface;
17-
use Symfony\Component\Console\Output\ConsoleOutput;
18-
use Symfony\Component\Console\Output\OutputInterface;
1915

2016
final class DevToolsApplication extends Application
2117
{
@@ -28,22 +24,6 @@ public function __construct(public readonly Configuration $configuration = new C
2824
$this->registerCommands();
2925
}
3026

31-
public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
32-
{
33-
$output ??= new ConsoleOutput();
34-
35-
$codeStyle = new OutputFormatterStyle('bright-blue');
36-
$output->getFormatter()->setStyle('code', $codeStyle);
37-
38-
$fileStyle = new OutputFormatterStyle('bright-magenta');
39-
$output->getFormatter()->setStyle('file', $fileStyle);
40-
41-
$linkStyle = new OutputFormatterStyle('cyan', options: ['underscore']);
42-
$output->getFormatter()->setStyle('link', $linkStyle);
43-
44-
return parent::run($input, $output);
45-
}
46-
4727
private function registerCommands(): void
4828
{
4929
$this->addCommands([

tests/Command/CommandTest.php

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
class CommandTest extends TestCase
2121
{
22-
public function testSetHelp(): void
22+
public function testGetHelp(): void
2323
{
2424
$helpTextToSet = <<<'EOD'
2525
<info>Lorem ipsum dolor sit amet</info>, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
@@ -28,6 +28,10 @@ public function testSetHelp(): void
2828
Eget nulla facilisi etiam dignissim.
2929
Fermentum dui faucibus in ornare quam viverra orci sagittis.
3030
31+
This is the command: %command.name%
32+
33+
This is the command full name: %command.full_name%
34+
3135
<code>This is some code</code>
3236
3337
* Foo
@@ -50,7 +54,11 @@ public function testSetHelp(): void
5054
Eget nulla facilisi etiam dignissim.
5155
Fermentum dui faucibus in ornare quam viverra orci sagittis.
5256
53-
<code>This is some code</code>
57+
This is the command: my-command
58+
59+
This is the command full name: phpunit my-command
60+
61+
<fg=bright-blue>This is some code</>
5462
5563
* Foo
5664
* Bar
@@ -59,14 +67,14 @@ public function testSetHelp(): void
5967
Eget nulla facilisi etiam dignissim. Fermentum dui faucibus in ornare quam
6068
viverra orci sagittis.
6169
62-
Quisque non tellus orci ac <code>auctor augue</code> mauris augue. Tincidunt nunc pulvinar
70+
Quisque non tellus orci ac <fg=bright-blue>auctor augue</> mauris augue. Tincidunt nunc pulvinar
6371
sapien et ligula ullamcorper malesuada proin. Molestie ac feugiat sed lectus
6472
vestibulum mattis. Ultricies mi quis hendrerit dolor magna. Est ultricies
65-
integer <link>quis</link> auctor elit sed vulputate. Vitae tortor condimentum lacinia quis
73+
integer <fg=cyan;options=underscore>quis</> auctor elit sed vulputate. Vitae tortor condimentum lacinia quis
6674
vel eros donec ac odio. Egestas tellus <href=https://example.com>rutrum tellus pellentesque</>.
6775
EOD;
6876

69-
$command = new class (new Configuration()) extends Command {
77+
$command = new #[AsCommand(name: 'my-command')] class (new Configuration()) extends Command {
7078
protected function doExecute(InputInterface $input, OutputInterface $output): int
7179
{
7280
return SymfonyCommand::SUCCESS;
@@ -78,6 +86,36 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in
7886
$this->assertStringEqualsStringIgnoringLineEndings($expectedHelp, $command->getHelp());
7987
}
8088

89+
public function testGetHelpForComposer(): void
90+
{
91+
$helpTextToSet = <<<'EOD'
92+
This is the command: %command.name%
93+
94+
This is the command full name: %command.full_name%
95+
96+
<code>This is some code</code>
97+
EOD;
98+
99+
$expectedHelp = <<<'EOD'
100+
This is the command: dev:my-command
101+
102+
This is the command full name: phpunit dev:my-command
103+
104+
<fg=bright-blue>This is some code</>
105+
EOD;
106+
107+
$command = new #[AsCommand(name: 'my-command')] class (new Configuration()) extends Command {
108+
protected function doExecute(InputInterface $input, OutputInterface $output): int
109+
{
110+
return SymfonyCommand::SUCCESS;
111+
}
112+
};
113+
114+
$command->setHelp($helpTextToSet);
115+
116+
$this->assertStringEqualsStringIgnoringLineEndings($expectedHelp, $command->getHelpForComposer());
117+
}
118+
81119
public function testComposerConfigurationWithScriptAsArray(): void
82120
{
83121
$eventDispatcher = $this->mockery(EventDispatcher::class);

tests/Composer/ComposerCommandTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public function testConfiguringCommand(): void
2727
'getAliases' => ['foo', 'bar'],
2828
'getDescription' => 'A test description',
2929
'getHelp' => 'Some help to show you how to use <code>foobar</code>',
30+
'getHelpForComposer' => 'Some help to show you how to use <code>wat:foobar</code>',
3031
'getDefinition' => $definition,
3132
'isHidden' => true,
3233
'getUsages' => ['foobar baz', 'foobar qux', 'quux'],
@@ -38,7 +39,7 @@ public function testConfiguringCommand(): void
3839
$this->assertSame('wat:foobar', $composerCommand->getName());
3940
$this->assertSame(['foo', 'bar'], $composerCommand->getAliases());
4041
$this->assertSame('A test description', $composerCommand->getDescription());
41-
$this->assertSame('Some help to show you how to use <code>foobar</code>', $composerCommand->getHelp());
42+
$this->assertSame('Some help to show you how to use <code>wat:foobar</code>', $composerCommand->getHelp());
4243
$this->assertSame($definition, $composerCommand->getDefinition());
4344
$this->assertTrue($composerCommand->isHidden());
4445
$this->assertSame(['wat:foobar baz', 'wat:foobar qux', 'wat:foobar quux'], $composerCommand->getUsages());

0 commit comments

Comments
 (0)