From b941ee370cc72903b4c1fcfdd44197d28c8199f4 Mon Sep 17 00:00:00 2001 From: azjezz Date: Thu, 3 Feb 2022 20:16:48 +0100 Subject: [PATCH] feat(shell): introduce error output behavior feature Signed-off-by: azjezz --- CHANGELOG.md | 4 ++ config/.phpcs.xml | 1 + docs/component/shell.md | 7 +++- src/Psl/Internal/Loader.php | 3 +- src/Psl/Shell/ErrorOutputBehavior.php | 27 ++++++++++++ .../Shell/{ => Internal}/escape_argument.php | 4 +- src/Psl/Shell/execute.php | 25 ++++------- tests/unit/Shell/EscapeArgumentTest.php | 41 ------------------- tests/unit/Shell/ExecuteTest.php | 28 +++++++++++++ 9 files changed, 78 insertions(+), 62 deletions(-) create mode 100644 src/Psl/Shell/ErrorOutputBehavior.php rename src/Psl/Shell/{ => Internal}/escape_argument.php (97%) delete mode 100644 tests/unit/Shell/EscapeArgumentTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 947f70db5..0014bd92b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,3 +67,7 @@ * **BC** - `Psl\Result\ResultInterface::getException()` method has been renamed to `Psl\Result\ResultInterface::getThrowable()` * **BC** - `Psl\Result\wrap` function now catches all `Throwable`s instead of only `Exception`s * introduced a new `Psl\Result\reflect` function +* **BC** - `Psl\Shell\escape_argument` function has been removed, `Shell\execute` arguments are now always escaped. +* **BC** - `$escape_arguments` argument of `Shell\execute` function has been removed. +* introduced a new `Psl\Shell\ErrorOutputBehavior` enum +* added a new `$error_output_behavior` argument to `Shell\execute` function, which can be used to return the command error output content, as well as the standard output content. diff --git a/config/.phpcs.xml b/config/.phpcs.xml index 7601791f3..a1f6b3408 100644 --- a/config/.phpcs.xml +++ b/config/.phpcs.xml @@ -15,6 +15,7 @@ */src/Psl/Hash/Hmac/Algorithm.php */src/Psl/OS/OperatingSystemFamily.php */src/Psl/Password/Algorithm.php + */src/Psl/Shell/ErrorOutputBehavior.php diff --git a/docs/component/shell.md b/docs/component/shell.md index 5473a7293..5536124f4 100644 --- a/docs/component/shell.md +++ b/docs/component/shell.md @@ -12,7 +12,10 @@ #### `Functions` -- [escape_argument](./../../src/Psl/Shell/escape_argument.php#L17) -- [execute](./../../src/Psl/Shell/execute.php#L42) +- [execute](./../../src/Psl/Shell/execute.php#L41) + +#### `Enums` + +- [ErrorOutputBehavior](./../../src/Psl/Shell/ErrorOutputBehavior.php#L7) diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index e9d8f0daf..c288902f3 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -377,8 +377,8 @@ final class Loader 'Psl\Encoding\Base64\decode', 'Psl\Encoding\Hex\encode', 'Psl\Encoding\Hex\decode', - 'Psl\Shell\escape_argument', 'Psl\Shell\execute', + 'Psl\Shell\Internal\escape_argument', 'Psl\Html\encode', 'Psl\Html\encode_special_characters', 'Psl\Html\decode', @@ -743,6 +743,7 @@ final class Loader 'Psl\Hash\Hmac\Algorithm', 'Psl\OS\OperatingSystemFamily', 'Psl\Password\Algorithm', + 'Psl\Shell\ErrorOutputBehavior' ]; public const TYPE_CONSTANTS = 1; diff --git a/src/Psl/Shell/ErrorOutputBehavior.php b/src/Psl/Shell/ErrorOutputBehavior.php new file mode 100644 index 000000000..b7c4d624f --- /dev/null +++ b/src/Psl/Shell/ErrorOutputBehavior.php @@ -0,0 +1,27 @@ + $environment A dict with the environment variables for the command that * will be run. - * @param bool $escape_arguments If set to true ( default ), all $arguments will be escaped using `escape_argument`. * * @psalm-taint-sink shell $command * @@ -44,23 +43,10 @@ function execute( array $arguments = [], ?string $working_directory = null, array $environment = [], - bool $escape_arguments = true, + ErrorOutputBehavior $error_output_behavior = ErrorOutputBehavior::DISCARD, ?float $timeout = null ): string { - if ($escape_arguments) { - $arguments = Vec\map( - $arguments, - /** - * @param string $argument - * - * @return string - * - * @pure - */ - static fn(string $argument): string => escape_argument($argument) - ); - } - + $arguments = Vec\map($arguments, Internal\escape_argument(...)); $commandline = Str\join([$command, ...$arguments], ' '); /** @psalm-suppress MissingThrowsDocblock - safe ( $offset is within-of-bounds ) */ @@ -201,5 +187,10 @@ static function (array $m) use ( throw new Exception\FailedExecutionException($commandline, $stdout_content, $stderr_content, $code); } - return $stdout_content; + return match ($error_output_behavior) { + ErrorOutputBehavior::PREPEND => $stderr_content . $stdout_content, + ErrorOutputBehavior::APPEND => $stdout_content . $stderr_content, + ErrorOutputBehavior::REPLACE => $stderr_content, + ErrorOutputBehavior::DISCARD => $stdout_content, + }; } diff --git a/tests/unit/Shell/EscapeArgumentTest.php b/tests/unit/Shell/EscapeArgumentTest.php deleted file mode 100644 index 938ebcad1..000000000 --- a/tests/unit/Shell/EscapeArgumentTest.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ - public function provideData(): iterable - { - yield ['a"b%c%']; - yield ['a"b^c^']; - yield ["a\nb'c"]; - yield ['a^b c!']; - yield ["a!b\tc"]; - yield ["look up ^"]; - yield ['a\\\\"\\"']; - yield ['éÉèÈàÀöä']; - yield ['1']; - yield ['1.1']; - yield ['1%2']; - yield ["Hey there,\nHow are you doing!"]; - yield ['']; - } -} diff --git a/tests/unit/Shell/ExecuteTest.php b/tests/unit/Shell/ExecuteTest.php index ad5c3f1c8..644fcac88 100644 --- a/tests/unit/Shell/ExecuteTest.php +++ b/tests/unit/Shell/ExecuteTest.php @@ -91,4 +91,32 @@ public function testItThrowsWhenWorkingDirectoryDoesntExist(): void Shell\execute(PHP_BINARY, ['-r', 'echo getcwd();'], $dir); } + + public function testErrorOutputIsDiscarded(): void + { + $result = Shell\execute(PHP_BINARY, ['-r', 'fwrite(STDOUT, "hello"); fwrite(STDERR, " world");']); + + static::assertSame('hello', $result); + } + + public function testErrorOutputIsAppended(): void + { + $result = Shell\execute(PHP_BINARY, ['-r', 'fwrite(STDOUT, "hello"); fwrite(STDERR, " world");'], error_output_behavior: Shell\ErrorOutputBehavior::APPEND); + + static::assertSame('hello world', $result); + } + + public function testErrorOutputIsPrepended(): void + { + $result = Shell\execute(PHP_BINARY, ['-r', 'fwrite(STDOUT, "hello"); fwrite(STDERR, " world");'], error_output_behavior: Shell\ErrorOutputBehavior::PREPEND); + + static::assertSame(' worldhello', $result); + } + + public function testErrorOutputIsReplacingStandardOutput(): void + { + $result = Shell\execute(PHP_BINARY, ['-r', 'fwrite(STDOUT, "hello"); fwrite(STDERR, " world");'], error_output_behavior: Shell\ErrorOutputBehavior::REPLACE); + + static::assertSame(' world', $result); + } }