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 @@ -5,6 +5,7 @@
### Features

* Add `directory` and `filter` parameters to `#[AsPathArgument]` and `#[AsPathOption]` attributes to improve autocomplete
* Add `input` option to Context to pass data to process stdin (useful for sensitive data like passwords)

### Fixes

Expand Down
13 changes: 13 additions & 0 deletions doc/docs/getting-started/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,16 @@ the process:
```php
{% include "/examples/basic/run/pty.php" start="<?php\n\nnamespace run;\n\n" %}
```

## Providing input to a process

You can provide input to a process via stdin using the `input` option on the
context. This is useful for commands that require input but you don't want to
expose sensitive data (like passwords) in command line arguments:

```php
Comment thread
Amoifr marked this conversation as resolved.
{% include "/examples/basic/run/input.php" start="<?php\n\nnamespace run;\n\n" %}
```

The `input` option accepts a string, a `\Stringable`, a resource, or an
`\Iterator<string>`.
19 changes: 19 additions & 0 deletions examples/basic/run/input.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace run;

use Castor\Attribute\AsTask;

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

#[AsTask(description: 'Run a sub-process with stdin input')]
function run_with_input(): void
{
$result = run(
['cat'],
context: context()->withInput("Hello from stdin!\n"),
);
io()->writeln('Output: ' . $result->getOutput());
}
40 changes: 38 additions & 2 deletions src/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ class Context implements \ArrayAccess
public readonly string $workingDirectory;

/**
* @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
* @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
* @param string|\Stringable|resource|\Iterator<string>|null $input The input to send to the process stdin
*
* @phpstan-param ContextData $data The input parameter accepts an array or an Object
*/
Expand All @@ -35,6 +36,7 @@ public function __construct(
public readonly string $name = '',
public readonly string $notificationTitle = '',
public readonly array $verboseArguments = [],
public readonly mixed $input = null,
) {
$this->workingDirectory = $workingDirectory ?? PathHelper::getRoot(false);
}
Expand Down Expand Up @@ -91,6 +93,7 @@ public function withData(array $data, bool $keepExisting = true, bool $recursive
$this->name,
$this->notificationTitle,
$this->verboseArguments,
$this->input,
);
}

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

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

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

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

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

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

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

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

Expand All @@ -263,6 +274,7 @@ public function withVerbosityLevel(VerbosityLevel $verbosityLevel): self
$this->name,
$this->notificationTitle,
$this->verboseArguments,
$this->input,
);
}

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

Expand All @@ -305,6 +318,7 @@ public function withNotificationTitle(string $notificationTitle): self
$this->name,
$notificationTitle,
$this->verboseArguments,
$this->input,
);
}

Expand All @@ -325,6 +339,28 @@ public function withVerboseArguments(array $arguments = []): self
$this->name,
$this->notificationTitle,
$arguments,
$this->input,
);
}

/** @param string|\Stringable|resource|\Iterator<string>|null $input */
public function withInput(mixed $input): 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,
$this->verboseArguments,
$input,
);
}

Expand Down
7 changes: 5 additions & 2 deletions src/Runner/ProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@ public function run(
;
}

// TTY does not work on windows, and PTY is a mess, so let's skip everything!
if (!OsHelper::isWindows()) {
// When input is provided, we need to disable TTY and PTY because they require interactive terminal
if (null !== $context->input) {
$process->setInput($context->input);
} elseif (!OsHelper::isWindows()) {
// TTY does not work on windows, and PTY is a mess, so let's skip everything!
if ($context->tty) {
$process->setTty(true);
$process->setInput(\STDIN);
Expand Down
1 change: 1 addition & 0 deletions tests/Generated/ListTest.php.output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ run:process-helper Run a sub-process and d
run:pty Run a command with PTY disabled
run:quiet Executes something but does not output anything
run:run Run a sub-process
run:run-with-input Run a sub-process with stdin input
run:tty Run a command with TTY enabled
run:variables Run a sub-process with environment variables and display information about it
run:verbose-arguments A failing task that will suggest to re-run with verbose arguments
Expand Down
22 changes: 22 additions & 0 deletions tests/Generated/RunRunWithInputTest.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 RunRunWithInputTest extends TaskTestCase
{
// run:run-with-input
public function test(): void
{
$process = $this->runTask(['run:run-with-input']);

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

$this->assertStringEqualsFileWithCleaning(__FILE__ . '.output.txt', $process->getOutput());
$this->assertSame('', $process->getErrorOutput());
}
}
3 changes: 3 additions & 0 deletions tests/Generated/RunRunWithInputTest.php.output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hello from stdin!
Output: Hello from stdin!