Skip to content

Commit

Permalink
Add support for getting raw parameters without validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lyrixx committed Mar 11, 2024
1 parent b4bbadf commit 1650d97
Show file tree
Hide file tree
Showing 17 changed files with 158 additions and 17 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Expand Up @@ -2,9 +2,11 @@

## Not released yet

* Add a option `ignoreValidationErrors` on `AsTask` attribute to ignore
parameters & options validation errors
* Add a way to merge an application `box.json` config file used by `castor:repack`command
* Deprecate `Context::withPath()` in favor of `Context::withCurrentDirectory()`
* Deprecate `path` argument in `capture()`, `exit_code()`, `run()`, `with()` in favor of `currentDirectory`
* Add a way to merge an application `box.json` config file used by `castor:repack`command

## 0.14.0 (2024-03-08)

Expand Down
3 changes: 2 additions & 1 deletion bin/castor
Expand Up @@ -2,6 +2,7 @@
<?php

use Castor\Console\ApplicationFactory;
use Castor\Console\Input\Input;

if (file_exists($file = __DIR__ . '/../vendor/autoload.php')) {
require $file;
Expand All @@ -11,4 +12,4 @@ if (file_exists($file = __DIR__ . '/../vendor/autoload.php')) {
throw new \RuntimeException('Unable to find autoloader.');
}

ApplicationFactory::create()->run();
ApplicationFactory::create()->run(new Input());
16 changes: 9 additions & 7 deletions bin/generate-tests.php
Expand Up @@ -116,19 +116,21 @@
add_test([], $class, '{{ base }}/tests/Examples/fixtures/broken/' . $dir->getRelativePath());
}

add_test(['parallel:sleep', '--sleep5', '0', '--sleep7', '0', '--sleep10', '0'], 'ParallelSleepTest');
add_test(['context:context', '--context', 'run'], 'ContextContextRunTest');
add_test(['args:passthru', 'a', 'b', '--no', '--foo', 'bar', '-x'], 'ArgPassthruExpanded');
add_test(['context:context', '--context', 'dynamic'], 'ContextContextDynamicTest');
add_test(['context:context', '--context', 'my_default', '-v'], 'ContextContextMyDefaultTest');
add_test(['context:context', '--context', 'no_no_exist'], 'ContextContextDoNotExistTest');
add_test(['context:context', '--context', 'production'], 'ContextContextProductionTest');
add_test(['context:context', '--context', 'path'], 'ContextContextPathTest');
add_test(['context:context', '--context', 'dynamic'], 'ContextContextDynamicTest');
add_test(['context:context', '--context', 'production'], 'ContextContextProductionTest');
add_test(['context:context', '--context', 'run'], 'ContextContextRunTest');
add_test(['enabled:hello', '--context', 'production'], 'EnabledInProduction');
add_test([], 'NewProjectTest', '/tmp');
add_test(['parallel:sleep', '--sleep5', '0', '--sleep7', '0', '--sleep10', '0'], 'ParallelSleepTest');
// In /tmp
add_test(['completion', 'bash'], 'NoConfigCompletionTest', '/tmp');
add_test(['init'], 'NewProjectInitTest', '/tmp');
add_test(['unknown:task'], 'NoConfigUnknownTest', '/tmp');
add_test(['unknown:task', 'toto', '--foo', 1], 'NoConfigUnknownWithArgsTest', '/tmp');
add_test(['completion', 'bash'], 'NoConfigCompletionTest', '/tmp');
add_test(['unknown:task'], 'NoConfigUnknownTest', '/tmp');
add_test([], 'NewProjectTest', '/tmp');

function add_test(array $args, string $class, ?string $cwd = null)
{
Expand Down
10 changes: 10 additions & 0 deletions examples/args.php
Expand Up @@ -4,6 +4,7 @@

use Castor\Attribute\AsArgument;
use Castor\Attribute\AsOption;
use Castor\Attribute\AsRawTokens;
use Castor\Attribute\AsTask;

use function Castor\run;
Expand Down Expand Up @@ -32,3 +33,12 @@ function another_args(
): void {
run(['echo', $required, $test2]);
}

/**
* @param string[] $rawTokens
*/
#[AsTask(description: 'Dumps all arguments and options, without configuration nor validation', ignoreValidationErrors: true)]
function passthru(#[AsRawTokens] array $rawTokens): void
{
var_dump($rawTokens);
}
8 changes: 8 additions & 0 deletions src/Attribute/AsRawTokens.php
@@ -0,0 +1,8 @@
<?php

namespace Castor\Attribute;

#[\Attribute(\Attribute::TARGET_PARAMETER)]
class AsRawTokens
{
}
1 change: 1 addition & 0 deletions src/Attribute/AsTask.php
Expand Up @@ -16,6 +16,7 @@ public function __construct(
public array $aliases = [],
public array $onSignals = [],
public string|bool $enabled = true,
public bool $ignoreValidationErrors = false,
) {
}
}
9 changes: 6 additions & 3 deletions src/Console/Application.php
Expand Up @@ -4,6 +4,7 @@

use Castor\Console\Command\SymfonyTaskCommand;
use Castor\Console\Command\TaskCommand;
use Castor\Console\Input\Input;
use Castor\Context;
use Castor\ContextDescriptor;
use Castor\ContextGeneratorDescriptor;
Expand Down Expand Up @@ -46,7 +47,7 @@ class Application extends SymfonyApplication
public const VERSION = 'v0.14.0';

// "Current" objects availables at some point of the lifecycle
private InputInterface $input;
private Input $input;
private SectionOutput $sectionOutput;
private SymfonyStyle $symfonyStyle;
private Command $command;
Expand Down Expand Up @@ -94,7 +95,7 @@ public function __construct(
GlobalHelper::setApplication($this);
}

public function getInput(): InputInterface
public function getInput(): Input
{
return $this->input ?? throw new \LogicException('Input not available yet.');
}
Expand Down Expand Up @@ -126,7 +127,9 @@ public function getCommand(bool $allowNull = false): ?Command
// is registered
public function doRun(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
if ($input instanceof Input) {
$this->input = $input;
}
$this->sectionOutput = new SectionOutput($output);
$this->symfonyStyle = new SymfonyStyle($input, $output);
$this->logger->pushHandler(new ConsoleHandler($output));
Expand Down
7 changes: 5 additions & 2 deletions src/Console/Command/SymfonyTaskCommand.php
Expand Up @@ -3,6 +3,7 @@
namespace Castor\Console\Command;

use Castor\Attribute\AsSymfonyTask;
use Castor\Console\Input\Input;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
Expand Down Expand Up @@ -67,10 +68,12 @@ protected function configure(): void
}
}

/**
* @param Input $input
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$r = new \ReflectionProperty($input, 'tokens');
$extra = array_filter($r->getValue($input), fn ($item) => $item !== $this->taskAttribute->name);
$extra = array_filter($input->getRawTokens(), fn ($item) => $item !== $this->taskAttribute->name);

$p = new Process([...$this->taskAttribute->console, $this->taskAttribute->originalName, ...$extra]);
$p->run(fn ($type, $bytes) => print ($bytes));
Expand Down
32 changes: 32 additions & 0 deletions src/Console/Command/TaskCommand.php
Expand Up @@ -5,8 +5,10 @@
use Castor\Attribute\AsArgument;
use Castor\Attribute\AsCommandArgument;
use Castor\Attribute\AsOption;
use Castor\Attribute\AsRawTokens;
use Castor\Attribute\AsTask;
use Castor\Console\Application;
use Castor\Console\Input\Input;
use Castor\Event\AfterExecuteTaskEvent;
use Castor\Event\BeforeExecuteTaskEvent;
use Castor\EventDispatcher;
Expand Down Expand Up @@ -76,7 +78,15 @@ public function isEnabled(): bool

protected function configure(): void
{
if ($this->taskAttribute->ignoreValidationErrors) {
$this->ignoreValidationErrors();
}

foreach ($this->function->getParameters() as $parameter) {
if ($parameter->getAttributes(AsRawTokens::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
continue;
}

$taskArgumentAttribute = $parameter->getAttributes(AsCommandArgument::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null;

if ($taskArgumentAttribute) {
Expand Down Expand Up @@ -140,11 +150,33 @@ protected function configure(): void
}
}

/**
* @param Input $input
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$args = [];

foreach ($this->function->getParameters() as $parameter) {
if ($parameter->getAttributes(AsRawTokens::class, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
$parameters = [];
$keep = false;
foreach ($input->getRawTokens() as $value) {
if ($value === $input->getFirstArgument()) {
$keep = true;

continue;
}
if ($keep) {
$parameters[] = $value;
}
}

$args[] = $parameters;

continue;
}

$name = $this->getParameterName($parameter);
if ($input->hasArgument($name)) {
$args[] = $input->getArgument($name);
Expand Down
17 changes: 17 additions & 0 deletions src/Console/Input/Input.php
@@ -0,0 +1,17 @@
<?php

namespace Castor\Console\Input;

use Symfony\Component\Console\Input\ArgvInput;

class Input extends ArgvInput
{
/**
* @return list<string>
*/
public function getRawTokens(): array
{
// @phpstan-ignore-next-line
return (fn () => $this->tokens)->bindTo($this, ArgvInput::class)();
}
}
4 changes: 2 additions & 2 deletions src/GlobalHelper.php
Expand Up @@ -3,10 +3,10 @@
namespace Castor;

use Castor\Console\Application;
use Castor\Console\Input\Input;
use Monolog\Logger;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;
Expand Down Expand Up @@ -57,7 +57,7 @@ public static function getLogger(): Logger
return self::getApplication()->logger;
}

public static function getInput(): InputInterface
public static function getInput(): Input
{
return self::getApplication()->getInput();
}
Expand Down
3 changes: 2 additions & 1 deletion src/functions.php
Expand Up @@ -4,6 +4,7 @@

use Castor\Attribute\AsContextGenerator;
use Castor\Console\Application;
use Castor\Console\Input\Input;
use Castor\Exception\ExecutableNotFoundException;
use Castor\Exception\MinimumVersionRequirementNotMetException;
use Castor\Exception\WaitFor\ExitedBeforeTimeoutException;
Expand Down Expand Up @@ -631,7 +632,7 @@ function get_application(): Application
return app();
}

function input(): InputInterface
function input(): Input
{
return GlobalHelper::getInput();
}
Expand Down
22 changes: 22 additions & 0 deletions tests/Examples/Generated/ArgPassthruExpanded.php
@@ -0,0 +1,22 @@
<?php

namespace Castor\Tests\Examples\Generated;

use Castor\Tests\TaskTestCase;

class ArgPassthruExpanded extends TaskTestCase
{
// args:passthru
public function test(): void
{
$process = $this->runTask(['args:passthru', 'a', 'b', '--no', '--foo', 'bar', '-x']);

$this->assertSame(0, $process->getExitCode());
$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
if (file_exists(__FILE__ . '.err.txt')) {
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
} else {
$this->assertSame('', $process->getErrorOutput());
}
}
}
14 changes: 14 additions & 0 deletions tests/Examples/Generated/ArgPassthruExpanded.php.output.txt
@@ -0,0 +1,14 @@
array(6) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(4) "--no"
[3]=>
string(5) "--foo"
[4]=>
string(3) "bar"
[5]=>
string(2) "-x"
}
22 changes: 22 additions & 0 deletions tests/Examples/Generated/ArgsPassthruTest.php
@@ -0,0 +1,22 @@
<?php

namespace Castor\Tests\Examples\Generated;

use Castor\Tests\TaskTestCase;

class ArgsPassthruTest extends TaskTestCase
{
// args:passthru
public function test(): void
{
$process = $this->runTask(['args:passthru']);

$this->assertSame(0, $process->getExitCode());
$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput());
if (file_exists(__FILE__ . '.err.txt')) {
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput());
} else {
$this->assertSame('', $process->getErrorOutput());
}
}
}
2 changes: 2 additions & 0 deletions tests/Examples/Generated/ArgsPassthruTest.php.output.txt
@@ -0,0 +1,2 @@
array(0) {
}
1 change: 1 addition & 0 deletions tests/Examples/Generated/ListTest.php.output.txt
Expand Up @@ -6,6 +6,7 @@ list List commands
no-namespace Task without a namespace
args:another-args Dumps all arguments and options, without configuration
args:args Dumps all arguments and options, with custom configuration
args:passthru Dumps all arguments and options, without configuration not validation
bar:bar Prints bar, but also executes foo
cache:complex Cache with usage of CacheItemInterface
cache:simple Cache a simple call
Expand Down

0 comments on commit 1650d97

Please sign in to comment.