Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- `Innmind\Framework\Application::mapRoute()`
- `Innmind\Framework\Application::routes(class-string<Innmind\Framework\Http\Route\Reference>)`
- `Innmind\Framework\Application::recoverRouteError()`
- `Innmind\Framework\Cli\Command`

### Changed

Expand Down
44 changes: 31 additions & 13 deletions src/Application/Cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ final class Cli implements Implementation
* @psalm-mutation-free
*
* @param \Closure(OperatingSystem, Environment): Builder $container
* @param Sequence<callable(Container): Command> $commands
* @param (\Closure(Container): Command)|Sequence<callable(Container): Command>|null $commands
* @param \Closure(Command, Container): Command $mapCommand
*/
private function __construct(
private OperatingSystem $os,
private Environment $env,
private \Closure $container,
private Sequence $commands,
private \Closure|Sequence|null $commands,
private \Closure $mapCommand,
) {
}
Expand All @@ -55,7 +55,7 @@ public static function of(OperatingSystem $os, Environment $env): self
$os,
$env,
static fn() => Builder::new(),
Sequence::of(),
null,
static fn(Command $command) => $command,
);
}
Expand Down Expand Up @@ -118,11 +118,21 @@ public function service(Service $name, callable $definition): self
#[\Override]
public function command(callable $command): self
{
$commands = $this->commands;

if (\is_null($commands)) {
$commands = \Closure::fromCallable($command);
} else if ($commands instanceof Sequence) {
$commands = ($commands)($command);
} else {
$commands = Sequence::of($commands, $command);
}

return new self(
$this->os,
$this->env,
$this->container,
($this->commands)($command),
$commands,
$this->mapCommand,
);
}
Expand Down Expand Up @@ -197,15 +207,23 @@ public function run($input)
$command,
$container,
);
$commands = $this->commands->map(static fn($command) => new Defer(
\Closure::fromCallable($command),
$container,
$mapCommand,
));

return $commands->match(
static fn($first, $rest) => Commands::of($first, ...$rest->toList())($input),
static fn() => $input->output(Str::of("Hello world\n")),
);
if (\is_null($this->commands)) {
return $input->output(Str::of("Hello world\n"));
}

if ($this->commands instanceof Sequence) {
$commands = $this->commands->map(static fn($command) => new Defer(
\Closure::fromCallable($command),
$container,
$mapCommand,
));

return Commands::for($commands)($input);
}

return Commands::of($mapCommand(
($this->commands)($container),
))($input);
}
}
71 changes: 71 additions & 0 deletions src/Cli/Command.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
declare(strict_types = 1);

namespace Innmind\Framework\Cli;

use Innmind\CLI\{
Command as CommandInterface,
Command\Usage,
Console,
};
use Innmind\DI\{
Container,
Service,
};
use Innmind\Immutable\Attempt;

final class Command implements CommandInterface
{
private ?CommandInterface $command = null;

/**
* @param class-string<CommandInterface> $class
* @param list<Service> $dependencies
*/
private function __construct(
private Container $get,
private string $class,
private array $dependencies,
) {
}

#[\Override]
public function __invoke(Console $console): Attempt
{
return $this->load()($console);
}

/**
* @psalm-pure
* @no-named-arguments
*
* @param class-string<CommandInterface> $class
*/
public static function of(
string $class,
Service ...$dependencies,
): callable {
return static fn(Container $get) => new self($get, $class, $dependencies);
}

/**
* @psalm-mutation-free
*/
#[\Override]
public function usage(): Usage
{
return Usage::for($this->class)->load(
fn() => $this->load()->usage(),
);
}

private function load(): CommandInterface
{
return $this->command ??= new ($this->class)(
...\array_map(
$this->get,
$this->dependencies,
),
);
}
}
78 changes: 78 additions & 0 deletions tests/ApplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Middleware\Optional,
Middleware\LoadDotEnv,
Http\Route,
Cli\Command as CommandReference,
};
use Innmind\OperatingSystem\Factory;
use Innmind\CLI\{
Expand Down Expand Up @@ -63,6 +64,25 @@ public function route(): callable
}
}

#[Command\Name('lazy')]
final class Lazy implements Command
{
public function __construct(
private Str $output,
) {
}

public function __invoke(Console $console): Attempt
{
return $console->output($this->output);
}

public function usage(): Usage
{
return Usage::for(self::class)->flag('foo');
}
}

class ApplicationTest extends TestCase
{
use BlackBox;
Expand Down Expand Up @@ -477,6 +497,64 @@ public function usage(): Usage
});
}

public function testLazyCommandAreNotLoaded(): BlackBox\Proof
{
return $this
->forAll(
Set::sequence(Set::strings())->between(0, 10),
Set::of(true, false),
Set::sequence(
Set::compose(
static fn($key, $value) => [$key, $value],
Set::strings()->randomize(),
Set::strings(),
),
)->between(0, 10),
Set::strings(),
)
->prove(function($inputs, $interactive, $variables, $output) {
$loaded = false;
$app = Application::cli(Factory::build(), Environment::test($variables))
->command(CommandReference::of(Lazy::class, Services::service))
->command(CommandReference::of(Lazy::class, Services::service))
->service(Services::service, static function() use ($output, &$loaded) {
$loaded = true;

return Str::of($output);
});

$env = $app->run(InMemory::of(
$inputs,
$interactive,
['script-name', 'help'],
$variables,
'/',
))->unwrap();

$this->assertSame([" lazy \n", " lazy \n"], $env->outputs());
$this->assertNull($env->exitCode()->match(
static fn($exit) => $exit,
static fn() => null,
));
$this->assertFalse($loaded);

$env = $app->run(InMemory::of(
$inputs,
$interactive,
['script-name', 'lazy'],
$variables,
'/',
))->unwrap();

$this->assertSame([$output], $env->outputs());
$this->assertNull($env->exitCode()->match(
static fn($exit) => $exit,
static fn() => null,
));
$this->assertTrue($loaded);
});
}

public function testDecoratingCommands(): BlackBox\Proof
{
return $this
Expand Down