feat: emulate rust's process api more closely
Yoink some docs as well
This commit is contained in:
188
src/Command.php
188
src/Command.php
@@ -4,16 +4,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace Nih\CommandBuilder;
|
||||
|
||||
use RuntimeException;
|
||||
use Stringable;
|
||||
use TypeError;
|
||||
use ValueError;
|
||||
|
||||
/**
|
||||
* A process builder, providing fine-grained control over how a new process
|
||||
* should be spawned.
|
||||
*
|
||||
* TODO: Wonky env handling.
|
||||
*/
|
||||
final class Command implements Stringable
|
||||
{
|
||||
public readonly string $command;
|
||||
public readonly string $program;
|
||||
|
||||
private array $args = [];
|
||||
private array $args;
|
||||
private ?array $envs = null;
|
||||
private ?string $cwd = null;
|
||||
|
||||
@@ -21,16 +25,22 @@ final class Command implements Stringable
|
||||
private ?Stdio $stdout = null;
|
||||
private ?Stdio $stderr = null;
|
||||
|
||||
public function __construct(string $command)
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct(string $program)
|
||||
{
|
||||
if (strlen($command) === 0) {
|
||||
throw new ValueError('Empty command');
|
||||
if (strlen($program) === 0) {
|
||||
throw new ValueError('Empty program name');
|
||||
}
|
||||
|
||||
$this->command = $command;
|
||||
$this->args[] = $this->command;
|
||||
$this->program = $program;
|
||||
$this->args = [$this->program];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an argument to pass to the program.
|
||||
*/
|
||||
public function arg(string|Stringable $arg): static
|
||||
{
|
||||
$this->args[] = (string) $arg;
|
||||
@@ -38,9 +48,11 @@ final class Command implements Stringable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|Stringable> $args
|
||||
* Adds multiple arguments to pass to the program.
|
||||
*
|
||||
* @param iterable<string|Stringable> $args
|
||||
*/
|
||||
public function args(array $args): static
|
||||
public function args(iterable $args): static
|
||||
{
|
||||
foreach ($args as $arg) {
|
||||
$this->args[] = (string) $arg;
|
||||
@@ -49,30 +61,71 @@ final class Command implements Stringable
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function currentDir(string|Stringable $cwd): static
|
||||
/**
|
||||
* Inserts or updates an explicit environment variable mapping.
|
||||
*/
|
||||
public function env(string $key, string|Stringable $val): static
|
||||
{
|
||||
$this->cwd = (string) $cwd;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function env(string $var, string|Stringable $value): static
|
||||
{
|
||||
$this->envs[$var] = $value;
|
||||
$this->envs[$key] = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string|Stringable> $envs
|
||||
* Inserts or updates multiple environment variable mappings.
|
||||
*
|
||||
* @param iterable<string, string|Stringable> $vars
|
||||
*/
|
||||
public function envs(array $envs): static
|
||||
public function envs(iterable $vars): static
|
||||
{
|
||||
foreach ($envs as $var => $value) {
|
||||
$this->envs[$var] = $value;
|
||||
foreach ($vars as $key => $val) {
|
||||
$this->envs[$key] = (string) $val;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an explicitly set environment variable and prevents inheriting it
|
||||
* from a parent process.
|
||||
*/
|
||||
public function envRemove(string $key): static
|
||||
{
|
||||
if (is_null($this->envs)) {
|
||||
$this->envs = getenv();
|
||||
}
|
||||
|
||||
if (isset($this->envs[$key])) {
|
||||
unset($this->envs[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all explicitly set environment variables and prevents inheriting
|
||||
* any parent process environment variables.
|
||||
*/
|
||||
public function envClear(): static
|
||||
{
|
||||
$this->envs = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the working directory for the child process.
|
||||
*/
|
||||
public function currentDir(string|Stringable $dir): static
|
||||
{
|
||||
$this->cwd = (string) $dir;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the child process’s standard input (stdin) handle.
|
||||
*
|
||||
* Defaults to inherit when used with spawn or status, and defaults to piped
|
||||
* when used with output.
|
||||
*/
|
||||
public function stdin(Stdio $stdin): static
|
||||
{
|
||||
$stdin = match ($stdin->type) {
|
||||
@@ -85,6 +138,12 @@ final class Command implements Stringable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the child process’s standard output (stdout) handle.
|
||||
*
|
||||
* Defaults to inherit when used with spawn or status, and defaults to piped
|
||||
* when used with output.
|
||||
*/
|
||||
public function stdout(Stdio $stdout): static
|
||||
{
|
||||
$stdout = match ($stdout->type) {
|
||||
@@ -97,6 +156,12 @@ final class Command implements Stringable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the child process’s standard error (stderr) handle.
|
||||
*
|
||||
* Defaults to inherit when used with spawn or status, and defaults to piped
|
||||
* when used with output.
|
||||
*/
|
||||
public function stderr(Stdio $stderr): static
|
||||
{
|
||||
$stderr = match ($stderr->type) {
|
||||
@@ -123,6 +188,10 @@ final class Command implements Stringable
|
||||
// }
|
||||
|
||||
/**
|
||||
* Executes the command as a child process, returning a handle to it.
|
||||
*
|
||||
* By default, stdin, stdout and stderr are inherited from the parent.
|
||||
*
|
||||
* @param bool $shell Run the command with or without a shell
|
||||
*/
|
||||
public function spawn(bool $shell = true): Child
|
||||
@@ -141,6 +210,11 @@ final class Command implements Stringable
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command as a child process, waiting for it to finish and
|
||||
* collecting its status.
|
||||
*
|
||||
* By default, stdin, stdout and stderr are inherited from the parent.
|
||||
*
|
||||
* @param bool $shell Run the command with or without a shell
|
||||
*/
|
||||
public function status(bool $shell = true): ExitStatus
|
||||
@@ -149,6 +223,12 @@ final class Command implements Stringable
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the command as a child process, waiting for it to finish and
|
||||
* collecting all of its output.
|
||||
*
|
||||
* By default, stdout and stderr are captured (and used to provide the
|
||||
* resulting output). Stdin is not inherited from the parent.
|
||||
*
|
||||
* @param bool $shell Run the command with or without a shell
|
||||
*/
|
||||
public function output(bool $shell = true): Output
|
||||
@@ -166,6 +246,42 @@ final class Command implements Stringable
|
||||
])->waitWithOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the program that was given to the constructor.
|
||||
*/
|
||||
public function getProgram(): string
|
||||
{
|
||||
return $this->program;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arguments that will be passed to the program.
|
||||
*
|
||||
* This does not include the path to the program as the first argument.
|
||||
*/
|
||||
public function getArgs(): array
|
||||
{
|
||||
return array_slice($this->args, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the environment variables set for the child process.
|
||||
*/
|
||||
public function getEnvs(): array
|
||||
{
|
||||
return $this->envs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the working directory for the child process.
|
||||
*
|
||||
* This returns `NULL` if the working directory will not be changed.
|
||||
*/
|
||||
public function getCurrentDir(): ?string
|
||||
{
|
||||
return $this->cwd;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode(' ', array_map(escapeshellarg(...), $this->args));
|
||||
@@ -185,33 +301,23 @@ final class Command implements Stringable
|
||||
|
||||
if ($shell) {
|
||||
$command = (string) $this;
|
||||
} else if (is_executable($this->command)) {
|
||||
} else if (is_executable($this->program)) {
|
||||
$command = $this->args;
|
||||
} else {
|
||||
throw new CommandException(sprintf(
|
||||
'Command "%s" is not executable',
|
||||
$this->command,
|
||||
'Program "%s" is not executable',
|
||||
$this->program,
|
||||
));
|
||||
}
|
||||
|
||||
$proc = proc_open($command, $descriptorSpec, $pipes, $this->cwd, $this->envs);
|
||||
|
||||
if ($proc === false) {
|
||||
throw new RuntimeException('Failed proc_open');
|
||||
throw new CommandException(sprintf(
|
||||
'Program "%s" failed to start',
|
||||
$this->program,
|
||||
));
|
||||
}
|
||||
|
||||
$stdin = array_key_exists(0, $pipes)
|
||||
? new ChildStdin($pipes[0])
|
||||
: null;
|
||||
|
||||
$stdout = array_key_exists(1, $pipes)
|
||||
? new ChildStdout($pipes[1])
|
||||
: null;
|
||||
|
||||
$stderr = array_key_exists(2, $pipes)
|
||||
? new ChildStderr($pipes[2])
|
||||
: null;
|
||||
|
||||
return new Child($stdin, $stdout, $stderr, $proc);
|
||||
return new Child($proc, $pipes);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user