Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve output formatting and verbosity #101

Merged
merged 2 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions bin/drall
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ foreach ([

use Drall\Drall;

try {
$drall = new Drall();
$exitCode = $drall->run();
}
catch (Exception $e) {
echo "ERROR {$e->getCode()}: {$e->getMessage()}";
exit(1);
}

exit($exitCode);
$drall = new Drall();
$drall->setAutoExit(TRUE);
$drall->run();
4 changes: 2 additions & 2 deletions src/Command/BaseCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ protected function preExecute(InputInterface $input, OutputInterface $output) {
}

if ($group = $this->getDrallGroup($input)) {
$this->logger->debug('Detected group: {group}', ['group' => $group]);
$this->logger->info('Using group: {group}', ['group' => $group]);
}

if ($filter = $this->getDrallFilter($input)) {
$this->logger->debug('Detected filter: {filter}', ['filter' => $filter]);
$this->logger->info('Using filter: {filter}', ['filter' => $filter]);
}
}

Expand Down
88 changes: 51 additions & 37 deletions src/Command/ExecCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Drall\Model\EnvironmentId;
use Drall\Model\Placeholder;
use Drall\Trait\SignalAwareTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -91,15 +92,38 @@ protected function configure() {
}

protected function initialize(InputInterface $input, OutputInterface $output): void {
if (!method_exists($input, 'getRawTokens')) {
parent::initialize($input, $output);
return;
}

// Parts of the command after "exec".
$rawTokens = $input->getRawTokens(TRUE);

// If obsolete --drall-* options are present, then abort.
if (method_exists($input, 'getRawTokens')) {
foreach ($input->getRawTokens() as $token) {
if (str_starts_with($token, '--drall-')) {
$output->writeln(<<<EOT
foreach ($rawTokens as $token) {
if (str_starts_with($token, '--drall-')) {
$output->writeln(<<<EOT
In Drall 4.x, all --drall-* options have been renamed.
See https://github.com/jigarius/drall/issues/99
EOT);
throw new \RuntimeException('Obsolete options detected.');
throw new \RuntimeException('Obsolete options detected');
}
}

// If options are present, an options separator (--) is required.
if (!in_array('--', $rawTokens)) {
foreach ($rawTokens as $token) {
if (str_starts_with($token, '-')) {
$output->writeln(<<<EOT
When using options, a "--" must be placed before the command to be executed.

<comment>Incorrect:</comment> drall exec --dry-run drush --field=site core:status
<comment>Correct:</comment> drall exec --dry-run -- drush --field=site core:status

Notice the `--` between `--dry-run` and the word `drush`.
EOT);
throw new \RuntimeException('Missing options separator');
}
}
}
Expand Down Expand Up @@ -140,19 +164,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
// Display commands without executing them.
if ($input->getOption('dry-run')) {
foreach ($values as $value) {
$sCommand = Placeholder::replace([$placeholder->value => $value], $command);
$output->writeln("# Item: $value", OutputInterface::VERBOSITY_VERBOSE);
$output->writeln($sCommand);
$pCommand = Placeholder::replace([$placeholder->value => $value], $command);
$output->writeln("$value: Preview");
$output->writeln($pCommand, OutputInterface::VERBOSITY_QUIET);
}

return 0;
return Command::SUCCESS;
}

$progressBar = new ProgressBar(
$this->isProgressBarHidden($input) ? new NullOutput() : $output,
count($values)
);
$exitCode = 0;
$exitCode = Command::SUCCESS;

// Handle interruption signals to stop Drall gracefully.
$isStopping = FALSE;
Expand All @@ -162,7 +186,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
// If a previous SIGINT was received, then stop immediately.
if ($isStopping) {
$this->logger->error('Interrupted by user.');
exit(1);
exit(Command::FAILURE);
}

// Prepare to stop after the current item is processed.
Expand Down Expand Up @@ -194,14 +218,22 @@ function ($value) use ($command, $placeholder, $output, $progressBar, &$exitCode
yield $process->start();
$this->logger->debug('Running: {command}', ['command' => $sCommand]);

$sOutput = yield ByteStream\buffer($process->getStdout());
if (0 !== yield $process->join()) {
$exitCode = 1;
// @todo Improve formatting of headings.
$pOutput = yield ByteStream\buffer($process->getStdout());
$pStatus = 'Done';
$pIcon = '✔';
if (Command::SUCCESS !== yield $process->join()) {
$pStatus = 'Failed';
$pIcon = '✖';
$exitCode = Command::FAILURE;
}

$pMessage = "$pIcon $value: $pStatus";

$progressBar->clear();
$output->writeln("Finished: $value");
$output->write($sOutput);
// Always display command output, even in --quiet mode.
$output->writeln($pMessage, OutputInterface::VERBOSITY_QUIET);
$output->write($pOutput);

$progressBar->advance();
$progressBar->display();
Expand All @@ -217,7 +249,7 @@ function ($value) use ($command, $placeholder, $output, $progressBar, &$exitCode

if ($isStopping) {
$this->logger->error('Interrupted by user.');
return 1;
return Command::FAILURE;
}

return $exitCode;
Expand All @@ -241,29 +273,10 @@ function ($value) use ($command, $placeholder, $output, $progressBar, &$exitCode
* Output: drush st --fields=site
*/
private function getCommand(InputInterface $input, OutputInterface $output): ?string {
$rawTokens = $input->getRawTokens(TRUE);
if (!in_array('--', $rawTokens)) {
foreach ($rawTokens as $token) {
if (str_starts_with($token, '-')) {
$output->writeln(<<<EOT
When using options, a "--" must be placed before the command to be executed.

<comment>Incorrect:</comment> drall exec --dry-run drush --field=site core:status
<comment>Correct:</comment> drall exec --dry-run -- drush --field=site core:status

Notice the `--` between `--dry-run` and the word `drush`.
EOT);
$this->logger->error('Separator "--" must be used when using options.');
return NULL;
}
}
}

// @todo Throw an error if --drall-* options are present.
// Everything after the first "--" is treated as an argument. All such
// arguments are treated as parts of the command to be executed.
$command = implode(' ', $input->getArguments()['cmd']);
$this->logger->debug("Command: {command}", ['command' => $command]);
$this->logger->debug("Command received: {command}", ['command' => $command]);

if (
str_contains($command, 'drush') &&
Expand All @@ -272,6 +285,7 @@ private function getCommand(InputInterface $input, OutputInterface $output): ?st
// Inject --uri=@@dir for Drush commands without placeholders.
$command = preg_replace('/\b(drush) /', 'drush --uri=@@dir ', $command, -1);
$this->logger->debug('Injected --uri parameter for Drush command.');
$this->logger->notice("Command modified: {command}", ['command' => $command]);
}

return $command;
Expand Down
12 changes: 4 additions & 8 deletions src/Drall.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,12 @@ public function __construct() {
protected function configureIO(InputInterface $input, OutputInterface $output): void {
parent::configureIO($input, $output);

if ($input->hasParameterOption('--debug', TRUE)) {
if (
$input->hasParameterOption('--debug', TRUE) ||
$input->hasParameterOption('-d', TRUE)
) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
}
elseif ($input->hasParameterOption('--verbose', TRUE)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
}
else {
$output->setVerbosity(OutputInterface::VERBOSITY_NORMAL);
}

// The parent::configureIO sets verbosity in a SHELL_VERBOSITY. This causes
// other Symfony Console apps to become verbose, for example, Drush. To
Expand All @@ -69,7 +66,6 @@ protected function getDefaultInputDefinition(): InputDefinition {
// Remove unneeded options.
$options = $definition->getOptions();
unset(
$options['quiet'],
$options['no-interaction'],
);
$definition->setOptions($options);
Expand Down
34 changes: 22 additions & 12 deletions src/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ abstract class TestCase extends TestCaseBase {

const PATH_EMPTY_DRUPAL = '/opt/empty-drupal';

/**
* Normalizes console output by stripping unimportant spaces.
*
* @param string $output
* Raw output.
*
* @return string
* Normalized output.
*/
private static function normalizeString(string $output): string {
return preg_replace('@(\s+)\n@', "\n", $output);
}

/**
* Creates a temporary file path.
*
Expand Down Expand Up @@ -57,19 +70,16 @@ protected function createDrupalFinderStub(?string $root = NULL): DrupalFinderCom
return $drupalFinder;
}

/**
* Asserts Shell output ignoring unimportant whitespace.
*
* @param string $expected
* Expected output.
* @param mixed $actual
* Actual output.
* @param string $message
* Error message.
*/
protected function assertOutputEquals(string $expected, mixed $actual, string $message = ''): void {
$actual = preg_replace('@(\s+)\n@', "\n", $actual ?? '');
$this->assertEquals($expected, $actual, $message);
$this->assertEquals($expected, self::normalizeString($actual ?? ''), $message);
}

protected function assertOutputStartsWith(string $expected, mixed $actual, string $message = ''): void {
$this->assertStringStartsWith($expected, self::normalizeString($actual ?? ''), $message);
}

protected function assertOutputContainsString(string $expected, mixed $actual, string $message = ''): void {
$this->assertStringContainsString($expected, self::normalizeString($actual ?? ''), $message);
}

}
62 changes: 62 additions & 0 deletions test/Integration/Command/BaseCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Drall\Integration\Command;

use Drall\TestCase;
use Symfony\Component\Process\Process;

/**
* @testdox Base Command
* @covers \Drall\Command\BaseCommand
*/
class BaseCommandTest extends TestCase {

/**
* @testdox Detects --group.
*/
public function testWithGroup(): void {
$process1 = Process::fromShellCommandline(
'drall exec --group=bluish --dry-run -vv -- ./vendor/bin/drush st',
static::PATH_DRUPAL,
);
$process1->run();
$this->assertOutputStartsWith(<<<EOT
[info] Using group: bluish
EOT, $process1->getOutput());

// Short form.
$process1 = Process::fromShellCommandline(
'drall exec -g bluish --dry-run -vv -- ./vendor/bin/drush st',
static::PATH_DRUPAL,
);
$process1->run();
$this->assertOutputStartsWith(<<<EOT
[info] Using group: bluish
EOT, $process1->getOutput());
}

/**
* @testdox Detects --filter.
*/
public function testWithFilter(): void {
$process1 = Process::fromShellCommandline(
'drall exec --filter=leo --dry-run -vv -- ./vendor/bin/drush st',
static::PATH_DRUPAL,
);
$process1->run();
$this->assertOutputStartsWith(<<<EOT
[info] Using filter: leo
EOT, $process1->getOutput());

// Short form.
$process2 = Process::fromShellCommandline(
'drall exec -f leo --dry-run -vv -- ./vendor/bin/drush st',
static::PATH_DRUPAL,
);
$process2->run();
$this->assertOutputStartsWith(<<<EOT
[info] Using filter: leo
EOT, $process2->getOutput());
}

}
Loading
Loading