Skip to content

Commit

Permalink
Merge pull request #507 from jolicode/feat/verbose-args
Browse files Browse the repository at this point in the history
feat(run): add possibility to specify verbose arguments for a context and rerun command with them
  • Loading branch information
lyrixx authored Sep 18, 2024
2 parents df0603a + 222406f commit 10a776d
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* Add `Context::toInteractive()` method
* Add `Castor\Event\ContextCreatedEvent` to allow updating the context after it is created
* Add `run_phar()` function to run a phar file in all contexts
* Add `Context::withVerboseArguments()` method to pass verbose arguments to the underlying process when needed

### Vendor

Expand Down
19 changes: 16 additions & 3 deletions bin/generate-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\InputStream;
use Symfony\Component\Process\Process;

use function Symfony\Component\String\u;
Expand Down Expand Up @@ -176,6 +177,7 @@
add_test(['context:context', '--context', 'run'], 'ContextContextRun');
add_test(['context:context', '--context', 'updated'], 'ContextContextUpdated');
add_test(['enabled:hello', '--context', 'production'], 'EnabledInProduction');
add_test(['failure:verbose-arguments'], 'FailureVerboseArgumentsTrue', input: "yes\n");
add_test(['list', '--raw', '--format', 'txt', '--short'], 'List', needRemote: true, skipOnBinary: true);
// Transient test, disabled for now
// add_test(['parallel:sleep', '--sleep5', '0', '--sleep7', '0', '--sleep10', '0'], 'ParallelSleep');
Expand All @@ -193,7 +195,7 @@

echo "\nDone.\n";

function add_test(array $args, string $class, ?string $cwd = null, bool $needRemote = false, bool $skipOnBinary = false, bool $needResetVendor = false)
function add_test(array $args, string $class, ?string $cwd = null, bool $needRemote = false, bool $skipOnBinary = false, bool $needResetVendor = false, ?string $input = null)
{
$class .= 'Test';
$fp = fopen(__FILE__, 'r');
Expand All @@ -206,6 +208,7 @@ function add_test(array $args, string $class, ?string $cwd = null, bool $needRem
(new Filesystem())->remove($workingDirectory . '/.castor/vendor');
}

$inputStream = $input ? new InputStream() : null;
$process = new Process(
[\PHP_BINARY, __DIR__ . '/castor', '--no-ansi', ...$args],
cwd: $workingDirectory,
Expand All @@ -215,9 +218,18 @@ function add_test(array $args, string $class, ?string $cwd = null, bool $needRem
'CASTOR_NO_REMOTE' => $needRemote ? 0 : 1,
'CASTOR_TEST' => 'true',
],
input: $inputStream,
timeout: null,
);
$process->run();
if ($inputStream) {
$process->start();
usleep(500_000);
$inputStream->write($input);
$inputStream->close();
$process->wait();
} else {
$process->run();
}

$err = OutputCleaner::cleanOutput($process->getErrorOutput());

Expand All @@ -229,6 +241,7 @@ function add_test(array $args, string $class, ?string $cwd = null, bool $needRem
'{{ cwd }}' => $cwd ? ', ' . var_export($cwd, true) : '',
'{{ needRemote }}' => $needRemote ? ', needRemote: true' : '',
'{{ needResetVendor }}' => $needResetVendor ? ', needResetVendor: true' : '',
'{{ input }}' => $input ? ', input: ' . var_export($input, true) : '',
'{{ skip-on-binary }}' => match ($skipOnBinary) {
true => <<<'PHP'
Expand Down Expand Up @@ -274,7 +287,7 @@ class {{ class_name }} extends TaskTestCase
// {{ task }}
public function test(): void
{{{ skip-on-binary }}
$process = $this->runTask([{{ args }}]{{ cwd }}{{ needRemote }}{{ needResetVendor }});
$process = $this->runTask([{{ args }}]{{ cwd }}{{ needRemote }}{{ needResetVendor }}{{ input }});

if ({{ exitCode }} !== $process->getExitCode()) {
throw new ProcessFailedException($process);
Expand Down
25 changes: 25 additions & 0 deletions doc/getting-started/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,31 @@ function foo(): void
}
```

## Passing verbose arguments

Castor allow you to pass verbose arguments to the underlying process. You can
do that by using the `withVerboseArguments` method:

```php
use Castor\Attribute\AsTask;

use function Castor\context;
use function Castor\run;

#[AsTask()]
function foo(): void
{
run('php bin/console do:some:task', context: context()->withVerboseArguments(['-v']));
}
```

By default, Castor will not pass any verbose arguments to the command. However
if you run castor with the `-v` option, it will pass the verbose arguments to this command.

Also if this command fails, castor will ask you if you want to retry the command
with the verbose arguments.


## Advanced usage

See [this documentation](../going-further/interacting-with-castor/advanced-context.md) for more usage about
Expand Down
6 changes: 6 additions & 0 deletions examples/failure.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ function allow_failure(): void
{
run('bash -c i_do_not_exist', context: context()->withAllowFailure()->withPty(false));
}

#[AsTask(description: 'A failing task authorized to fail')]
function verbose_arguments(): void
{
run('bash -c i_do_not_exist', context: context()->withVerboseArguments(['-x', '-e']));
}
36 changes: 35 additions & 1 deletion src/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class Context implements \ArrayAccess
/**
* @phpstan-param ContextData $data The input parameter accepts an array or an Object
*
* @param array<string, string|\Stringable|int> $environment A list of environment variables to add to the task
* @param array<string, string|\Stringable|int> $environment A list of environment variables to add to the task
* @param string[] $verboseArguments A list of arguments to pass to the command to enable verbose output
*/
public function __construct(
public readonly array $data = [],
Expand All @@ -31,6 +32,7 @@ public function __construct(
// Do not use this argument, it is only used internally by the application
public readonly string $name = '',
public readonly string $notificationTitle = '',
public readonly array $verboseArguments = [],
) {
$this->workingDirectory = $workingDirectory ?? PathHelper::getRoot();
}
Expand Down Expand Up @@ -85,6 +87,7 @@ public function withData(array $data, bool $keepExisting = true, bool $recursive
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -104,6 +107,7 @@ public function withEnvironment(array $environment, bool $keepExisting = true):
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -129,6 +133,7 @@ public function withWorkingDirectory(string $workingDirectory): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -147,6 +152,7 @@ public function withTty(bool $tty = true): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -165,6 +171,7 @@ public function withPty(bool $pty = true): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -183,6 +190,7 @@ public function withTimeout(?float $timeout): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -201,6 +209,7 @@ public function withQuiet(bool $quiet = true): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -219,6 +228,7 @@ public function withAllowFailure(bool $allowFailure = true): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -237,6 +247,7 @@ public function withNotify(?bool $notify = true): self
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -255,6 +266,7 @@ public function withVerbosityLevel(VerbosityLevel|LegacyVerbosityLevel $verbosit
$verbosityLevel,
$this->name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -273,6 +285,7 @@ public function withName(string $name): self
$this->verbosityLevel,
$name,
$this->notificationTitle,
$this->verboseArguments,
);
}

Expand All @@ -291,6 +304,27 @@ public function withNotificationTitle(string $notificationTitle): self
$this->verbosityLevel,
$this->name,
$notificationTitle,
$this->verboseArguments,
);
}

/** @param string[] $arguments */
public function withVerboseArguments(array $arguments = []): self
{
return new self(
$this->data,
$this->environment,
$this->workingDirectory,
$this->tty,
$this->pty,
$this->timeout,
$this->quiet,
$this->allowFailure,
$this->notify,
$this->verbosityLevel,
$this->name,
$this->notificationTitle,
$arguments,
);
}

Expand Down
35 changes: 33 additions & 2 deletions src/Runner/ProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
use Castor\CommandBuilder\CommandBuilderInterface;
use Castor\CommandBuilder\ContextUpdaterInterface;
use Castor\Console\Output\SectionOutput;
use Castor\Console\Output\VerbosityLevel;
use Castor\Context;
use Castor\ContextRegistry;
use Castor\Event;
use Castor\Helper\Notifier;
use JoliCode\PhpOsHelper\OsHelper;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
Expand All @@ -27,7 +28,8 @@ public function __construct(
private readonly SectionOutput $sectionOutput,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly Notifier $notifier,
private readonly LoggerInterface $logger = new NullLogger(),
private readonly LoggerInterface $logger,
private readonly SymfonyStyle $io,
) {
}

Expand Down Expand Up @@ -108,8 +110,16 @@ public function run(
}

if (\is_array($command)) {
if ($context->verbosityLevel->isVerbose() && $context->verboseArguments) {
$command = array_merge($command, $context->verboseArguments);
}

$process = new Process($command, $context->workingDirectory, $context->environment, null, $context->timeout);
} else {
if ($context->verbosityLevel->isVerbose() && $context->verboseArguments) {
$command = \sprintf('%s %s', $command, implode(' ', $context->verboseArguments));
}

$process = Process::fromShellCommandline($command, $context->workingDirectory, $context->environment, null, $context->timeout);
}

Expand Down Expand Up @@ -182,6 +192,27 @@ public function run(

if (0 !== $exitCode) {
$this->logger->notice(\sprintf('Command finished with an error (exit code=%d).', $process->getExitCode()));

if ($context->verboseArguments && !$context->verbosityLevel->isVerbose()) {
$retry = $this->io->confirm('Do you want to retry the command with verbose arguments?', false);

if ($retry) {
return $this->run(
command: $command,
environment: $environment,
workingDirectory: $workingDirectory,
tty: $tty,
pty: $pty,
timeout: $timeout,
quiet: $quiet,
allowFailure: $allowFailure,
notify: $notify,
callback: $callback,
context: $context->withVerbosityLevel(VerbosityLevel::VERBOSE),
);
}
}

if (!$context->allowFailure) {
if ($context->verbosityLevel->isVerbose()) {
throw new ProcessFailedException($process);
Expand Down
22 changes: 22 additions & 0 deletions tests/Generated/FailureVerboseArgumentsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Castor\Tests\Generated;

use Castor\Tests\TaskTestCase;
use Symfony\Component\Process\Exception\ProcessFailedException;

class FailureVerboseArgumentsTest extends TaskTestCase
{
// failure:verbose-arguments
public function test(): void
{
$process = $this->runTask(['failure:verbose-arguments']);

if (1 !== $process->getExitCode()) {
throw new ProcessFailedException($process);
}

$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
}
}
7 changes: 7 additions & 0 deletions tests/Generated/FailureVerboseArgumentsTest.php.err.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
In failure.php line 25:

The command "bash -c i_do_not_exist" failed.


failure:verbose-arguments

4 changes: 4 additions & 0 deletions tests/Generated/FailureVerboseArgumentsTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bash: line 1: i_do_not_exist: command not found

Do you want to retry the command with verbose arguments? (yes/no) [no]:
>
23 changes: 23 additions & 0 deletions tests/Generated/FailureVerboseArgumentsTrueTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Castor\Tests\Generated;

use Castor\Tests\TaskTestCase;
use Symfony\Component\Process\Exception\ProcessFailedException;

class FailureVerboseArgumentsTrueTest extends TaskTestCase
{
// failure:verbose-arguments
public function test(): void
{
$process = $this->runTask(['failure:verbose-arguments'], input: 'yes
');

if (1 !== $process->getExitCode()) {
throw new ProcessFailedException($process);
}

$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
}
}
Loading

0 comments on commit 10a776d

Please sign in to comment.