├── .github
├── FUNDING.yml
└── workflows
│ └── integration.yml
├── LICENSE
├── composer.json
├── phive.xml
├── phpstan.neon
└── src
├── Command.php
├── Command
├── Executable.php
├── OutputFormatter.php
├── Result.php
├── Runner.php
└── Runner
│ ├── Result.php
│ └── Simple.php
├── CommandLine.php
├── Output
└── Util.php
├── Processor.php
├── Processor
├── ProcOpen.php
└── Symfony.php
├── Reader.php
├── Reader
├── Abstraction.php
└── StandardInput.php
└── Util.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: sebastianfeldmann
2 |
--------------------------------------------------------------------------------
/.github/workflows/integration.yml:
--------------------------------------------------------------------------------
1 | name: "CI-Build"
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.operating-system }}
8 | strategy:
9 | max-parallel: 3
10 | matrix:
11 | operating-system: [ubuntu-latest]
12 | php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4']
13 | steps:
14 | - uses: actions/checkout@master
15 |
16 | - name: Install PHP
17 | uses: shivammathur/setup-php@master
18 | with:
19 | php-version: ${{ matrix.php-versions }}
20 | extensions: xdebug
21 |
22 | - name: Validate composer.json and composer.lock
23 | run: composer validate
24 |
25 | - name: Install dependencies
26 | run: composer install --prefer-dist --no-progress --no-suggest
27 |
28 | - name: Install phive
29 | run: wget -O phive.phar https://phar.io/releases/phive.phar
30 |
31 | - name: Make phive executable
32 | run: chmod +x ./phive.phar
33 |
34 | - name: Install tooling
35 | run: ./phive.phar --no-progress --home ./.phive install --trust-gpg-keys 4AA394086372C20A,31C7E470E2138192,8E730BA25823D8B5,CF1A108D0E7AE720,A978220305CD5C32,51C67305FFC2E5C0 --force-accept-unsigned
36 |
37 | - name: Execute unit tests
38 | run: tools/phpunit
39 |
40 | - name: Check coding style
41 | run: tools/phpcs --standard=psr12 src tests
42 |
43 | - name: Static code analysis
44 | run: tools/phpstan analyse
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Sebastian Feldmann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sebastianfeldmann/cli",
3 | "description": "PHP cli helper classes",
4 | "type": "library",
5 | "keywords": ["cli"],
6 | "homepage": "https://github.com/sebastianfeldmann/cli",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Sebastian Feldmann",
11 | "email": "sf@sebastian-feldmann.info"
12 | }
13 | ],
14 | "support": {
15 | "issues": "https://github.com/sebastianfeldmann/cli/issues"
16 | },
17 | "require": {
18 | "php": ">=7.2"
19 | },
20 | "require-dev": {
21 | "symfony/process": "^4.3 | ^5.0 | ^6.0 | ^7.0"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "SebastianFeldmann\\Cli\\": "src/"
26 | }
27 | },
28 | "autoload-dev": {
29 | "psr-4": {
30 | "SebastianFeldmann\\Cli\\": "tests/cli/"
31 | }
32 | },
33 | "scripts": {
34 | "post-install-cmd": "tools/phive install",
35 | "test": "tools/phpunit",
36 | "static": "tools/phpstan analyse",
37 | "style": "tools/phpcs --standard=psr12 src tests"
38 | },
39 | "extra": {
40 | "branch-alias": {
41 | "dev-master": "3.4.x-dev"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/phive.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 5
3 | paths:
4 | - %currentWorkingDirectory%/src/
5 |
--------------------------------------------------------------------------------
/src/Command.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli;
13 |
14 | /**
15 | * Class Command
16 | *
17 | * @package SebastianFeldmann\Cli
18 | * @author Sebastian Feldmann
19 | * @link https://github.com/sebastianfeldmann/cli
20 | * @since Class available since Release 0.9.0
21 | */
22 | interface Command
23 | {
24 | /**
25 | * Get the cli command.
26 | *
27 | * @return string
28 | */
29 | public function getCommand(): string;
30 |
31 | /**
32 | * Returns a list of exit codes that are valid.
33 | *
34 | * @return array
35 | */
36 | public function getAcceptableExitCodes(): array;
37 |
38 | /**
39 | * Convert command to string
40 | *
41 | * @return string
42 | */
43 | public function __toString(): string;
44 | }
45 |
--------------------------------------------------------------------------------
/src/Command/Executable.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Command;
13 |
14 | use SebastianFeldmann\Cli\Command;
15 | use SebastianFeldmann\Cli\Util;
16 |
17 | /**
18 | * Class Executable
19 | *
20 | * @package SebastianFeldmann\Cli
21 | * @author Sebastian Feldmann
22 | * @link https://github.com/sebastianfeldmann/cli
23 | * @since Class available since Release 0.9.0
24 | */
25 | class Executable implements Command
26 | {
27 | /**
28 | * Command name
29 | *
30 | * @var string
31 | */
32 | private $cmd;
33 |
34 | /**
35 | * Display stdErr
36 | *
37 | * @var boolean
38 | */
39 | private $isSilent = false;
40 |
41 | /**
42 | * Command options
43 | *
44 | * @var string[]
45 | */
46 | private $options = [];
47 |
48 | /**
49 | * List of variables to define
50 | *
51 | * @var string[]
52 | */
53 | private $vars = [];
54 |
55 | /**
56 | * List of acceptable exit codes.
57 | *
58 | * @var array
59 | */
60 | private $acceptableExitCodes = [];
61 |
62 | /**
63 | * Constructor.
64 | *
65 | * @param string $cmd
66 | * @param int[] $exitCodes
67 | */
68 | public function __construct(string $cmd, array $exitCodes = [0])
69 | {
70 | $this->cmd = $cmd;
71 | $this->acceptableExitCodes = $exitCodes;
72 | }
73 |
74 | /**
75 | * Returns the string to execute on the command line.
76 | *
77 | * @return string
78 | */
79 | public function getCommand(): string
80 | {
81 | $cmd = $this->getVars() . sprintf('"%s"', $this->cmd)
82 | . (count($this->options) ? ' ' . implode(' ', $this->options) : '')
83 | . ($this->isSilent ? ' 2> /dev/null' : '');
84 |
85 | return Util::escapeSpacesIfOnWindows($cmd);
86 | }
87 |
88 | /**
89 | * Returns a list of exit codes that are valid.
90 | *
91 | * @return int[]
92 | */
93 | public function getAcceptableExitCodes(): array
94 | {
95 | return $this->acceptableExitCodes;
96 | }
97 |
98 | /**
99 | * Silence the 'Cmd' by redirecting its stdErr output to /dev/null.
100 | * The silence feature is disabled for Windows systems.
101 | *
102 | * @param bool $bool
103 | * @return \SebastianFeldmann\Cli\Command\Executable
104 | */
105 | public function silence($bool = true): Executable
106 | {
107 | $this->isSilent = $bool && !defined('PHP_WINDOWS_VERSION_BUILD');
108 | return $this;
109 | }
110 |
111 | /**
112 | * Add option to list.
113 | *
114 | * @param string $option
115 | * @param mixed $value
116 | * @param string $glue
117 | * @return \SebastianFeldmann\Cli\Command\Executable
118 | */
119 | public function addOption(string $option, $value = null, string $glue = '='): Executable
120 | {
121 | if ($value !== null) {
122 | // force space for multiple arguments e.g. --option 'foo' 'bar'
123 | if (is_array($value)) {
124 | $glue = ' ';
125 | }
126 | $value = $glue . $this->escapeArgument($value);
127 | } else {
128 | $value = '';
129 | }
130 | $this->options[] = $option . $value;
131 |
132 | return $this;
133 | }
134 |
135 | /**
136 | * Add a var definition to a command
137 | *
138 | * @param string $name
139 | * @param string $value
140 | * @return $this
141 | */
142 | public function addVar(string $name, string $value): Executable
143 | {
144 | $this->vars[$name] = $value;
145 |
146 | return $this;
147 | }
148 |
149 | /**
150 | * Return variable definition string e.g. "MYFOO='sometext' MYBAR='nothing' "
151 | *
152 | * @return string
153 | */
154 | protected function getVars(): string
155 | {
156 | $varStrings = [];
157 |
158 | foreach ($this->vars as $name => $value) {
159 | $varStrings[] = $name . '=' . escapeshellarg($value);
160 | }
161 |
162 | return count($varStrings) ? implode(' ', $varStrings) . ' ' : '';
163 | }
164 |
165 | /**
166 | * Adds an option to a command if it is not empty.
167 | *
168 | * @param string $option
169 | * @param mixed $check
170 | * @param bool $asValue
171 | * @param string $glue
172 | * @return \SebastianFeldmann\Cli\Command\Executable
173 | */
174 | public function addOptionIfNotEmpty(string $option, $check, bool $asValue = true, string $glue = '='): Executable
175 | {
176 | if (!empty($check)) {
177 | if ($asValue) {
178 | $this->addOption($option, $check, $glue);
179 | } else {
180 | $this->addOption($option);
181 | }
182 | }
183 | return $this;
184 | }
185 |
186 | /**
187 | * Add argument to list.
188 | *
189 | * @param mixed $argument
190 | * @return \SebastianFeldmann\Cli\Command\Executable
191 | */
192 | public function addArgument($argument): Executable
193 | {
194 | $this->options[] = $this->escapeArgument($argument);
195 | return $this;
196 | }
197 |
198 | /**
199 | * Escape a shell argument.
200 | *
201 | * @param mixed $argument
202 | * @return string
203 | */
204 | protected function escapeArgument($argument): string
205 | {
206 | if (is_array($argument)) {
207 | $argument = array_map('escapeshellarg', $argument);
208 | $escaped = implode(' ', $argument);
209 | } else {
210 | $escaped = escapeshellarg($argument);
211 | }
212 | return $escaped;
213 | }
214 |
215 | /**
216 | * Returns the command to execute.
217 | *
218 | * @return string
219 | */
220 | public function __toString(): string
221 | {
222 | return $this->getCommand();
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/Command/OutputFormatter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Command;
13 |
14 | /**
15 | * Interface OutputFormatter
16 | *
17 | * @package SebastianFeldmann\Cli
18 | * @author Sebastian Feldmann
19 | * @link https://github.com/sebastianfeldmann/cli
20 | * @since Class available since Release 0.9.0
21 | */
22 | interface OutputFormatter
23 | {
24 | /**
25 | * Format the output.
26 | *
27 | * @param array $output
28 | * @return iterable
29 | */
30 | public function format(array $output): iterable;
31 | }
32 |
--------------------------------------------------------------------------------
/src/Command/Result.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Command;
13 |
14 | use SebastianFeldmann\Cli\Output\Util as OutputUtil;
15 |
16 | /**
17 | * Class Result
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 0.9.0
23 | */
24 | class Result
25 | {
26 | /**
27 | * Command that got executed.
28 | *
29 | * @var string
30 | */
31 | private $cmd;
32 |
33 | /**
34 | * Result code.
35 | *
36 | * @var int
37 | */
38 | private $code;
39 |
40 | /**
41 | * List of valid exit codes.
42 | *
43 | * @var int[]
44 | */
45 | private $validExitCodes;
46 |
47 | /**
48 | * Output buffer.
49 | *
50 | * @var array
51 | */
52 | private $buffer;
53 |
54 | /**
55 | * StdOut.
56 | *
57 | * @var string
58 | */
59 | private $stdOut;
60 |
61 | /**
62 | * StdErr.
63 | *
64 | * @var string
65 | */
66 | private $stdErr;
67 |
68 | /**
69 | * Path where the output is redirected to.
70 | *
71 | * @var string
72 | */
73 | private $redirectPath;
74 |
75 | /**
76 | * Result constructor.
77 | *
78 | * @param string $cmd
79 | * @param int $code
80 | * @param string $stdOut
81 | * @param string $stdErr
82 | * @param string $redirectPath
83 | * @param int[] $validExitCodes
84 | */
85 | public function __construct(
86 | string $cmd,
87 | int $code,
88 | string $stdOut = '',
89 | string $stdErr = '',
90 | string $redirectPath = '',
91 | array $validExitCodes = [0]
92 | ) {
93 | $this->cmd = $cmd;
94 | $this->code = $code;
95 | $this->stdOut = $stdOut;
96 | $this->stdErr = $stdErr;
97 | $this->redirectPath = $redirectPath;
98 | $this->validExitCodes = $validExitCodes;
99 | }
100 |
101 | /**
102 | * Cmd getter.
103 | *
104 | * @return string
105 | */
106 | public function getCmd(): string
107 | {
108 | return $this->cmd;
109 | }
110 |
111 | /**
112 | * Code getter.
113 | *
114 | * @return int
115 | */
116 | public function getCode(): int
117 | {
118 | return $this->code;
119 | }
120 |
121 | /**
122 | * Command executed successful.
123 | */
124 | public function isSuccessful(): bool
125 | {
126 | return in_array($this->code, $this->validExitCodes);
127 | }
128 |
129 | /**
130 | * StdOutput getter.
131 | *
132 | * @return string
133 | */
134 | public function getStdOut(): string
135 | {
136 | return $this->stdOut;
137 | }
138 |
139 | /**
140 | * StdError getter.
141 | *
142 | * @return string
143 | */
144 | public function getStdErr(): string
145 | {
146 | return $this->stdErr;
147 | }
148 |
149 | /**
150 | * Is the output redirected to a file.
151 | *
152 | * @return bool
153 | */
154 | public function isOutputRedirected(): bool
155 | {
156 | return !empty($this->redirectPath);
157 | }
158 |
159 | /**
160 | * Return path to the file where the output is redirected to.
161 | *
162 | * @return string
163 | */
164 | public function getRedirectPath(): string
165 | {
166 | return $this->redirectPath;
167 | }
168 |
169 | /**
170 | * Return the output as array.
171 | *
172 | * @return array
173 | */
174 | public function getStdOutAsArray(): array
175 | {
176 | if (null === $this->buffer) {
177 | $this->buffer = $this->textToBuffer();
178 | }
179 | return $this->buffer;
180 | }
181 |
182 | /**
183 | * Converts a string into an array.
184 | *
185 | * @return array
186 | */
187 | private function textToBuffer(): array
188 | {
189 | return OutputUtil::trimEmptyLines(explode("\n", OutputUtil::normalizeLineEndings($this->stdOut)));
190 | }
191 |
192 | /**
193 | * Magic to string method.
194 | *
195 | * @return string
196 | */
197 | public function __toString(): string
198 | {
199 | return $this->stdOut;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/Command/Runner.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Command;
13 |
14 | use SebastianFeldmann\Cli\Command;
15 | use SebastianFeldmann\Cli\Command\Runner\Result as RunnerResult;
16 |
17 | /**
18 | * Interface Runner
19 | *
20 | * @package SebastianFeldmann\Cli
21 | * @author Sebastian Feldmann
22 | * @link https://github.com/sebastianfeldmann/cli
23 | * @since Class available since Release 0.9.0
24 | */
25 | interface Runner
26 | {
27 | /**
28 | * Execute a command.
29 | *
30 | * @param \SebastianFeldmann\Cli\Command $command
31 | * @param \SebastianFeldmann\Cli\Command\OutputFormatter|null $formatter
32 | * @return \SebastianFeldmann\Cli\Command\Runner\Result
33 | */
34 | public function run(Command $command, ?OutputFormatter $formatter = null): RunnerResult;
35 | }
36 |
--------------------------------------------------------------------------------
/src/Command/Runner/Result.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Command\Runner;
13 |
14 | use SebastianFeldmann\Cli\Command\Result as CommandResult;
15 |
16 | /**
17 | * Class Result
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 0.9.0
23 | */
24 | class Result
25 | {
26 | /**
27 | * Result of executed command.
28 | *
29 | * @var \SebastianFeldmann\Cli\Command\Result
30 | */
31 | private $cmdResult;
32 |
33 | /**
34 | * Formatted output of executed result.
35 | *
36 | * @var iterable
37 | */
38 | private $formatted;
39 |
40 | /**
41 | * Result constructor.
42 | *
43 | * @param \SebastianFeldmann\Cli\Command\Result $cmdResult
44 | * @param iterable $formatted
45 | */
46 | public function __construct(CommandResult $cmdResult, iterable $formatted = [])
47 | {
48 | $this->cmdResult = $cmdResult;
49 | $this->formatted = $formatted;
50 | }
51 |
52 | /**
53 | * Get the raw command result.
54 | *
55 | * @return \SebastianFeldmann\Cli\Command\Result
56 | */
57 | public function getCommandResult(): CommandResult
58 | {
59 | return $this->cmdResult;
60 | }
61 |
62 | /**
63 | * Return true if command execution was successful.
64 | *
65 | * @return bool
66 | */
67 | public function isSuccessful(): bool
68 | {
69 | return $this->cmdResult->isSuccessful();
70 | }
71 |
72 | /**
73 | * Return the command exit code.
74 | *
75 | * @return int
76 | */
77 | public function getCode(): int
78 | {
79 | return $this->cmdResult->getCode();
80 | }
81 |
82 | /**
83 | * Return the executed cli command.
84 | *
85 | * @return string
86 | */
87 | public function getCmd(): string
88 | {
89 | return $this->cmdResult->getCmd();
90 | }
91 |
92 | /**
93 | * Return commands output to stdOut.
94 | *
95 | * @return string
96 | */
97 | public function getStdOut(): string
98 | {
99 | return $this->cmdResult->getStdOut();
100 | }
101 |
102 | /**
103 | * Return commands error output to stdErr.
104 | *
105 | * @return string
106 | */
107 | public function getStdErr(): string
108 | {
109 | return $this->cmdResult->getStdErr();
110 | }
111 |
112 | /**
113 | * Is the output redirected to a file.
114 | *
115 | * @return bool
116 | */
117 | public function isOutputRedirected(): bool
118 | {
119 | return $this->cmdResult->isOutputRedirected();
120 | }
121 |
122 | /**
123 | * Return path to the file where the output is redirected to.
124 | *
125 | * @return string
126 | */
127 | public function getRedirectPath(): string
128 | {
129 | return $this->cmdResult->getRedirectPath();
130 | }
131 |
132 | /**
133 | * Return cmd output as array.
134 | *
135 | * @return array
136 | */
137 | public function getBufferedOutput(): array
138 | {
139 | return $this->cmdResult->getStdOutAsArray();
140 | }
141 |
142 | /**
143 | * Return formatted output.
144 | *
145 | * @return iterable
146 | */
147 | public function getFormattedOutput(): iterable
148 | {
149 | return $this->formatted;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Command/Runner/Simple.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Command\Runner;
13 |
14 | use RuntimeException;
15 | use SebastianFeldmann\Cli\Command;
16 | use SebastianFeldmann\Cli\Command\Runner;
17 | use SebastianFeldmann\Cli\Command\OutputFormatter;
18 | use SebastianFeldmann\Cli\Processor;
19 |
20 | /**
21 | * Class Simple
22 | *
23 | * @package SebastianFeldmann\Cli
24 | * @author Sebastian Feldmann
25 | * @link https://github.com/sebastianfeldmann/cli
26 | * @since Class available since Release 0.9.0
27 | */
28 | class Simple implements Runner
29 | {
30 | /**
31 | * Class handling system calls.
32 | *
33 | * @var \SebastianFeldmann\Cli\Processor
34 | */
35 | private $processor;
36 |
37 | /**
38 | * Exec constructor.
39 | *
40 | * @param \SebastianFeldmann\Cli\Processor|null $processor
41 | */
42 | public function __construct(?Processor $processor = null)
43 | {
44 | $this->processor = $processor !== null
45 | ? $processor
46 | : new Processor\ProcOpen();
47 | }
48 |
49 | /**
50 | * Execute a cli command.
51 | *
52 | * @param \SebastianFeldmann\Cli\Command $command
53 | * @param \SebastianFeldmann\Cli\Command\OutputFormatter|null $formatter
54 | * @return \SebastianFeldmann\Cli\Command\Runner\Result
55 | */
56 | public function run(Command $command, ?OutputFormatter $formatter = null): Result
57 | {
58 | $cmd = $this->processor->run($command->getCommand(), $command->getAcceptableExitCodes());
59 |
60 | if (!$cmd->isSuccessful()) {
61 | throw new RuntimeException(
62 | 'Command failed:' . PHP_EOL
63 | . ' exit-code: ' . $cmd->getCode() . PHP_EOL
64 | . ' message: ' . $cmd->getStdErr() . PHP_EOL,
65 | $cmd->getCode()
66 | );
67 | }
68 |
69 | $formatted = $formatter !== null ? $formatter->format($cmd->getStdOutAsArray()) : [];
70 | $result = new Result($cmd, $formatted);
71 |
72 | return $result;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/CommandLine.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli;
13 |
14 | use RuntimeException;
15 |
16 | /**
17 | * Class CommandLine
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 0.9.0
23 | */
24 | class CommandLine implements Command
25 | {
26 | /**
27 | * List of system commands to execute
28 | *
29 | * @var \SebastianFeldmann\Cli\Command[]
30 | */
31 | private $commands = [];
32 |
33 | /**
34 | * Redirect the output
35 | *
36 | * @var string
37 | */
38 | private $redirectOutput;
39 |
40 | /**
41 | * Output pipeline
42 | *
43 | * @var \SebastianFeldmann\Cli\Command[]
44 | */
45 | private $pipeline = [];
46 |
47 | /**
48 | * Should 'pipefail' be set?
49 | *
50 | * @var bool
51 | */
52 | private $pipeFail = false;
53 |
54 | /**
55 | * List of acceptable exit codes.
56 | *
57 | * @var array
58 | */
59 | private $acceptedExitCodes = [0];
60 |
61 | /**
62 | * Set the list of accepted exit codes
63 | *
64 | * @param int[] $codes
65 | * @return void
66 | */
67 | public function acceptExitCodes(array $codes): void
68 | {
69 | $this->acceptedExitCodes = $codes;
70 | }
71 |
72 | /**
73 | * Redirect the stdOut.
74 | *
75 | * @param string $path
76 | * @return void
77 | */
78 | public function redirectOutputTo($path): void
79 | {
80 | $this->redirectOutput = $path;
81 | }
82 |
83 | /**
84 | * Should the output be redirected
85 | *
86 | * @return bool
87 | */
88 | public function isOutputRedirected(): bool
89 | {
90 | return !empty($this->redirectOutput);
91 | }
92 |
93 | /**
94 | * Redirect getter.
95 | *
96 | * @return string
97 | */
98 | public function getRedirectPath(): string
99 | {
100 | return $this->redirectOutput;
101 | }
102 |
103 | /**
104 | * Pipe the command into given command
105 | *
106 | * @param \SebastianFeldmann\Cli\Command $cmd
107 | * @return void
108 | */
109 | public function pipeOutputTo(Command $cmd): void
110 | {
111 | if (!$this->canPipe()) {
112 | throw new RuntimeException('Can\'t pipe output');
113 | }
114 | $this->pipeline[] = $cmd;
115 | }
116 |
117 | /**
118 | * Get the 'pipefail' option command snippet
119 | *
120 | * @return string
121 | */
122 | public function getPipeFail(): string
123 | {
124 | return ($this->isPiped() && $this->pipeFail) ? 'set -o pipefail; ' : '';
125 | }
126 |
127 | /**
128 | * Can the pipe '|' operator be used
129 | *
130 | * @return bool
131 | */
132 | public function canPipe(): bool
133 | {
134 | return !defined('PHP_WINDOWS_VERSION_BUILD');
135 | }
136 |
137 | /**
138 | * Is there a command pipeline
139 | *
140 | * @return bool
141 | */
142 | public function isPiped(): bool
143 | {
144 | return !empty($this->pipeline);
145 | }
146 |
147 | /**
148 | * Should the pipefail option be set
149 | *
150 | * @param bool $pipeFail
151 | */
152 | public function pipeFail(bool $pipeFail)
153 | {
154 | $this->pipeFail = $pipeFail;
155 | }
156 |
157 | /**
158 | * Return command pipeline
159 | *
160 | * @return string
161 | */
162 | public function getPipeline(): string
163 | {
164 | return $this->isPiped() ? ' | ' . implode(' | ', $this->pipeline) : '';
165 | }
166 |
167 | /**
168 | * Adds a cli command to list of commands to execute
169 | *
170 | * @param \SebastianFeldmann\Cli\Command $cmd
171 | * @return void
172 | */
173 | public function addCommand(Command $cmd): void
174 | {
175 | $this->commands[] = $cmd;
176 | }
177 |
178 | /**
179 | * Generates the system command
180 | *
181 | * @return string
182 | */
183 | public function getCommand(): string
184 | {
185 | $amount = count($this->commands);
186 | if ($amount < 1) {
187 | throw new RuntimeException('no command to execute');
188 | }
189 | $cmd = $this->getPipeFail()
190 | . ($amount > 1 ? '(' . implode(' && ', $this->commands) . ')' : $this->commands[0])
191 | . $this->getPipeline()
192 | . (!empty($this->redirectOutput) ? ' > ' . $this->redirectOutput : '');
193 |
194 | return $cmd;
195 | }
196 |
197 | /**
198 | * Returns a list of exit codes that are valid
199 | *
200 | * @return array
201 | */
202 | public function getAcceptableExitCodes(): array
203 | {
204 | return $this->acceptedExitCodes;
205 | }
206 |
207 | /**
208 | * Returns the command to execute
209 | *
210 | * @return string
211 | */
212 | public function __toString(): string
213 | {
214 | return $this->getCommand();
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/Output/Util.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Output;
13 |
14 | /**
15 | * Class Util
16 | *
17 | * @package SebastianFeldmann\Cli
18 | * @author Sebastian Feldmann
19 | * @link https://github.com/sebastianfeldmann/cli
20 | * @since Class available since Release 2.1.0
21 | */
22 | class Util
23 | {
24 | /**
25 | * Remove empty entries at the end of an array.
26 | *
27 | * @param array $lines
28 | * @return array
29 | */
30 | public static function trimEmptyLines(array $lines): array
31 | {
32 | for ($last = count($lines) - 1; $last > -1; $last--) {
33 | if (!empty($lines[$last])) {
34 | return $lines;
35 | }
36 | unset($lines[$last]);
37 | }
38 | return $lines;
39 | }
40 |
41 | /**
42 | * Replaces all 'known' line endings with unix \n line endings
43 | *
44 | * @param string $text
45 | * @return string
46 | */
47 | public static function normalizeLineEndings(string $text): string
48 | {
49 | $mod = preg_match('/[\p{Cyrillic}]/u', $text) ? 'u' : '';
50 | return preg_replace('~(*BSR_UNICODE)\R~' . $mod, "\n", $text);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Processor.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli;
13 |
14 | use SebastianFeldmann\Cli\Command\Result;
15 |
16 | /**
17 | * Interface Processor
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 0.9.0
23 | */
24 | interface Processor
25 | {
26 | /**
27 | * Execute a system call.
28 | *
29 | * @param string $cmd
30 | * @param int[] $acceptableExitCodes
31 | * @return \SebastianFeldmann\Cli\Command\Result
32 | */
33 | public function run(string $cmd, array $acceptableExitCodes = [0]): Result;
34 | }
35 |
--------------------------------------------------------------------------------
/src/Processor/ProcOpen.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Processor;
13 |
14 | use RuntimeException;
15 | use SebastianFeldmann\Cli\Command\Result;
16 | use SebastianFeldmann\Cli\Processor;
17 |
18 | /**
19 | * Class ProcOpen
20 | *
21 | * @package SebastianFeldmann\Cli
22 | * @author Sebastian Feldmann
23 | * @link https://github.com/sebastianfeldmann/cli
24 | * @since Class available since Release 0.9.0
25 | */
26 | class ProcOpen implements Processor
27 | {
28 | /**
29 | * Execute the command
30 | *
31 | * @param string $cmd
32 | * @param int[] $acceptableExitCodes
33 | * @return \SebastianFeldmann\Cli\Command\Result
34 | */
35 | public function run(string $cmd, array $acceptableExitCodes = [0]): Result
36 | {
37 | $old = error_reporting(0);
38 | $descriptorSpec = [
39 | ['pipe', 'r'],
40 | ['pipe', 'w'],
41 | ['pipe', 'w'],
42 | ];
43 |
44 | $process = proc_open($cmd, $descriptorSpec, $pipes);
45 | if (!is_resource($process)) {
46 | throw new RuntimeException('can\'t execute \'proc_open\'');
47 | }
48 |
49 | // Loop on process until it exits normally.
50 | $stdOut = "";
51 | $stdErr = "";
52 | do {
53 | // Consume output streams while the process runs. The buffer will block process updates when full
54 | $status = proc_get_status($process);
55 | $stdOut .= stream_get_contents($pipes[1]);
56 | $stdErr .= stream_get_contents($pipes[2]);
57 | } while ($status['running']);
58 |
59 | // make sure all pipes are closed before calling proc_close
60 | foreach ($pipes as $index => $pipe) {
61 | fclose($pipe);
62 | unset($pipes[$index]);
63 | }
64 |
65 | proc_close($process);
66 | error_reporting($old);
67 |
68 | return new Result($cmd, $status['exitcode'], $stdOut, $stdErr, '', $acceptableExitCodes);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Processor/Symfony.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Processor;
13 |
14 | use SebastianFeldmann\Cli\Command\Result;
15 | use SebastianFeldmann\Cli\Processor;
16 | use Symfony\Component\Process\Process;
17 |
18 | /**
19 | * Class ProcOpen
20 | *
21 | * @package SebastianFeldmann\Cli
22 | * @author Sebastian Feldmann
23 | * @link https://github.com/sebastianfeldmann/cli
24 | * @since Class available since Release 3.2.2
25 | */
26 | class Symfony implements Processor
27 | {
28 | /**
29 | * Execute the command
30 | *
31 | * @param string $cmd
32 | * @param int[] $acceptableExitCodes
33 | * @return \SebastianFeldmann\Cli\Command\Result
34 | */
35 | public function run(string $cmd, array $acceptableExitCodes = [0]): Result
36 | {
37 | // the else (:) variant is there to keep backwards compatibility with previous symfony versions
38 | // and is only getting executed in those. This is the reason why the Process constructor is
39 | // given a string instead of an array. The whole ternary can be removed if Symfony versions
40 | // below 4.2 are not supported anymore.
41 | $process = method_exists(Process::class, 'fromShellCommandline')
42 | ? Process::fromShellCommandline($cmd)
43 | : new Process($cmd); // @phpstan-ignore-line
44 |
45 | $process->setTimeout(null);
46 | $process->run();
47 | return new Result(
48 | $cmd,
49 | $process->getExitCode(),
50 | $process->getOutput(),
51 | $process->getErrorOutput(),
52 | '',
53 | $acceptableExitCodes
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Reader.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli;
13 |
14 | use Iterator;
15 |
16 | /**
17 | * Interface Reader
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 3.3.0
23 | */
24 | interface Reader extends Iterator
25 | {
26 | /**
27 | * Get the current line.
28 | *
29 | * @return string
30 | */
31 | public function current(): string;
32 | }
33 |
--------------------------------------------------------------------------------
/src/Reader/Abstraction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Reader;
13 |
14 | use Iterator;
15 | use SebastianFeldmann\Cli\Reader;
16 |
17 | /**
18 | * Abstract Reader class
19 | *
20 | * @package SebastianFeldmann\Cli
21 | * @author Sebastian Feldmann
22 | * @link https://github.com/sebastianfeldmann/cli
23 | * @since Class available since Release 3.3.0
24 | */
25 | abstract class Abstraction implements Reader
26 | {
27 | /**
28 | * Internal iterator to handle foreach
29 | *
30 | * @var \Iterator
31 | */
32 | private $iterator;
33 |
34 | /**
35 | * Return the internal iterator
36 | *
37 | * @return \Iterator
38 | */
39 | private function getIterator(): Iterator
40 | {
41 | return $this->iterator;
42 | }
43 |
44 | /**
45 | * Set the pointer to the next line
46 | *
47 | * @return void
48 | */
49 | public function next(): void
50 | {
51 | $this->getIterator()->next();
52 | }
53 |
54 | /**
55 | * Get the line number of the current line
56 | *
57 | * @return int
58 | */
59 | public function key(): int
60 | {
61 | return $this->getIterator()->key();
62 | }
63 |
64 | /**
65 | * Check whether the current line is valid
66 | *
67 | * @return bool
68 | */
69 | public function valid(): bool
70 | {
71 | return $this->getIterator()->valid();
72 | }
73 |
74 | /**
75 | * Recreate/rewind the iterator
76 | *
77 | * @return void
78 | */
79 | public function rewind(): void
80 | {
81 | $this->iterator = $this->createIterator();
82 | }
83 |
84 | /**
85 | * Get the current line
86 | *
87 | * @return string
88 | */
89 | public function current(): string
90 | {
91 | return $this->getIterator()->current();
92 | }
93 |
94 | /**
95 | * Create the internal iterator
96 | *
97 | * @return iterable
98 | */
99 | abstract protected function createIterator(): iterable;
100 | }
101 |
--------------------------------------------------------------------------------
/src/Reader/StandardInput.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli\Reader;
13 |
14 | use Exception;
15 |
16 | /**
17 | * StandardInput
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 3.3.0
23 | */
24 | class StandardInput extends Abstraction
25 | {
26 | /**
27 | * Standard Input stream handle
28 | *
29 | * @var resource
30 | */
31 | private $handle;
32 |
33 | /**
34 | * StandardInput constructor.
35 | *
36 | * @param resource $stdInHandle
37 | */
38 | public function __construct($stdInHandle)
39 | {
40 | $this->handle = $stdInHandle;
41 | }
42 |
43 | /**
44 | * Create the generator
45 | *
46 | * @return iterable
47 | * @throws \Exception
48 | */
49 | protected function createIterator(): iterable
50 | {
51 | $read = [$this->handle];
52 | $write = [];
53 | $except = [];
54 | $result = stream_select($read, $write, $except, 0);
55 |
56 | if ($result === false) {
57 | throw new Exception('stream_select failed');
58 | }
59 | if ($result !== 0) {
60 | while (!\feof($this->handle)) {
61 | yield \fgets($this->handle);
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Util.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace SebastianFeldmann\Cli;
13 |
14 | use RuntimeException;
15 |
16 | /**
17 | * Interface Processor
18 | *
19 | * @package SebastianFeldmann\Cli
20 | * @author Sebastian Feldmann
21 | * @link https://github.com/sebastianfeldmann/cli
22 | * @since Class available since Release 1.0.4
23 | */
24 | abstract class Util
25 | {
26 | /**
27 | * List of console style codes.
28 | *
29 | * @var array
30 | */
31 | private static $ansiCodes = [
32 | 'bold' => 1,
33 | 'fg-black' => 30,
34 | 'fg-red' => 31,
35 | 'fg-green' => 32,
36 | 'fg-yellow' => 33,
37 | 'fg-cyan' => 36,
38 | 'fg-white' => 37,
39 | 'bg-red' => 41,
40 | 'bg-green' => 42,
41 | 'bg-yellow' => 43
42 | ];
43 |
44 | /**
45 | * Detect a given command's location.
46 | *
47 | * @param string $cmd The command to locate
48 | * @param string $path Directory where the command should be
49 | * @param array $optionalLocations Some fallback locations where to search for the command
50 | * @return string Absolute path to detected command including command itself
51 | * @throws \RuntimeException
52 | */
53 | public static function detectCmdLocation(string $cmd, string $path = '', array $optionalLocations = []): string
54 | {
55 | $detectionSteps = [
56 | function ($cmd) use ($path) {
57 | if (!empty($path)) {
58 | return self::detectCmdLocationInPath($cmd, $path);
59 | }
60 | return '';
61 | },
62 | function ($cmd) {
63 | return self::detectCmdLocationWithWhich($cmd);
64 | },
65 | function ($cmd) {
66 | $paths = explode(PATH_SEPARATOR, self::getEnvPath());
67 | return self::detectCmdLocationInPaths($cmd, $paths);
68 | },
69 | function ($cmd) use ($optionalLocations) {
70 | return self::detectCmdLocationInPaths($cmd, $optionalLocations);
71 | }
72 | ];
73 |
74 | foreach ($detectionSteps as $step) {
75 | $bin = $step($cmd);
76 | if (!empty($bin)) {
77 | return $bin;
78 | }
79 | }
80 |
81 | throw new RuntimeException(sprintf('\'%s\' was nowhere to be found please specify the correct path', $cmd));
82 | }
83 |
84 | /**
85 | * Detect a command in a given path.
86 | *
87 | * @param string $cmd
88 | * @param string $path
89 | * @return string
90 | * @throws \RuntimeException
91 | */
92 | public static function detectCmdLocationInPath(string $cmd, string $path): string
93 | {
94 | $command = $path . DIRECTORY_SEPARATOR . $cmd;
95 | $bin = self::getExecutable($command);
96 | if (empty($bin)) {
97 | throw new RuntimeException(sprintf('wrong path specified for \'%s\': %s', $cmd, $path));
98 | }
99 | return $bin;
100 | }
101 |
102 | /**
103 | * Detect command location using which cli command.
104 | *
105 | * @param string $cmd
106 | * @return string
107 | */
108 | public static function detectCmdLocationWithWhich($cmd): string
109 | {
110 | $bin = '';
111 | // on nx systems use 'which' command.
112 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
113 | $command = trim(`which $cmd`);
114 | $bin = self::getExecutable($command);
115 | }
116 | return $bin;
117 | }
118 |
119 | /**
120 | * Check path list for executable command.
121 | *
122 | * @param string $cmd
123 | * @param array $paths
124 | * @return string
125 | */
126 | public static function detectCmdLocationInPaths($cmd, array $paths): string
127 | {
128 | foreach ($paths as $path) {
129 | $command = $path . DIRECTORY_SEPARATOR . $cmd;
130 | $bin = self::getExecutable($command);
131 | if (null !== $bin) {
132 | return $bin;
133 | }
134 | }
135 | return '';
136 | }
137 |
138 | /**
139 | * Return local $PATH variable.
140 | *
141 | * @return string
142 | * @throws \RuntimeException
143 | */
144 | public static function getEnvPath(): string
145 | {
146 | // check for unix and windows case $_SERVER index
147 | foreach (['PATH', 'Path', 'path'] as $index) {
148 | if (isset($_SERVER[$index])) {
149 | return $_SERVER[$index];
150 | }
151 | }
152 | throw new RuntimeException('cant find local PATH variable');
153 | }
154 |
155 | /**
156 | * Returns the executable command if the command is executable, empty string otherwise.
157 | * Search for $command.exe on Windows systems.
158 | *
159 | * @param string $command
160 | * @return string
161 | */
162 | public static function getExecutable($command): string
163 | {
164 | if (is_executable($command)) {
165 | return $command;
166 | }
167 | // on windows check the .exe suffix
168 | if (defined('PHP_WINDOWS_VERSION_BUILD')) {
169 | $command .= '.exe';
170 | if (is_executable($command)) {
171 | return $command;
172 | }
173 | }
174 | return '';
175 | }
176 |
177 | /**
178 | * Is given path absolute.
179 | *
180 | * @param string $path
181 | * @return bool
182 | */
183 | public static function isAbsolutePath($path): bool
184 | {
185 | // path already absolute?
186 | if ($path[0] === '/') {
187 | return true;
188 | }
189 |
190 | // Matches the following on Windows:
191 | // - \\NetworkComputer\Path
192 | // - \\.\D:
193 | // - \\.\c:
194 | // - C:\Windows
195 | // - C:\windows
196 | // - C:/windows
197 | // - c:/windows
198 | if (defined('PHP_WINDOWS_VERSION_BUILD') && self::isAbsoluteWindowsPath($path)) {
199 | return true;
200 | }
201 |
202 | // Stream
203 | if (strpos($path, '://') !== false) {
204 | return true;
205 | }
206 |
207 | return false;
208 | }
209 |
210 | /**
211 | * Is given path an absolute windows path.
212 | *
213 | * @param string $path
214 | * @return bool
215 | */
216 | public static function isAbsoluteWindowsPath($path): bool
217 | {
218 | return ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))));
219 | }
220 |
221 | /**
222 | * Converts a path to an absolute one if necessary relative to a given base path.
223 | *
224 | * @param string $path
225 | * @param string $base
226 | * @param bool $useIncludePath
227 | * @return string
228 | */
229 | public static function toAbsolutePath(string $path, string $base, bool $useIncludePath = false): string
230 | {
231 | if (self::isAbsolutePath($path)) {
232 | return $path;
233 | }
234 |
235 | $file = $base . DIRECTORY_SEPARATOR . $path;
236 |
237 | if ($useIncludePath && !file_exists($file)) {
238 | $includePathFile = stream_resolve_include_path($path);
239 | if ($includePathFile) {
240 | $file = $includePathFile;
241 | }
242 | }
243 | return $file;
244 | }
245 |
246 | /**
247 | * Formats a buffer with a specified ANSI color sequence if colors are enabled.
248 | *
249 | * @author Sebastian Bergmann
250 | * @param string $color
251 | * @param string $buffer
252 | * @return string
253 | */
254 | public static function formatWithColor(string $color, string $buffer): string
255 | {
256 | $codes = array_map('trim', explode(',', $color));
257 | $lines = explode("\n", $buffer);
258 | $padding = max(array_map('strlen', $lines));
259 |
260 | $styles = [];
261 | foreach ($codes as $code) {
262 | $styles[] = self::$ansiCodes[$code];
263 | }
264 | $style = sprintf("\x1b[%sm", implode(';', $styles));
265 |
266 | $styledLines = [];
267 | foreach ($lines as $line) {
268 | $styledLines[] = strlen($line) ? $style . str_pad($line, $padding) . "\x1b[0m" : '';
269 | }
270 |
271 | return implode(PHP_EOL, $styledLines);
272 | }
273 |
274 | /**
275 | * Fills up a text buffer with '*' to consume by default 72 chars.
276 | *
277 | * @param string $buffer
278 | * @param int $length
279 | * @return string
280 | */
281 | public static function formatWithAsterisk(string $buffer, int $length = 72): string
282 | {
283 | return $buffer . str_repeat('*', $length - strlen($buffer)) . PHP_EOL;
284 | }
285 |
286 | /**
287 | * Can command pipe operator be used.
288 | *
289 | * @return bool
290 | */
291 | public static function canPipe(): bool
292 | {
293 | return !defined('PHP_WINDOWS_VERSION_BUILD');
294 | }
295 |
296 | /**
297 | * Removes a directory that is not empty.
298 | *
299 | * @param string $dir
300 | */
301 | public static function removeDir(string $dir)
302 | {
303 | foreach (scandir($dir) as $file) {
304 | if ('.' === $file || '..' === $file) {
305 | continue;
306 | }
307 | if (is_dir($dir . '/' . $file)) {
308 | self::removeDir($dir . '/' . $file);
309 | } else {
310 | unlink($dir . '/' . $file);
311 | }
312 | }
313 | rmdir($dir);
314 | }
315 |
316 | /**
317 | * Wraps windows command with a double quote to escape spaces
318 | * i.e: `E:/Program Files/tar.exe -zcf ...` escaped to `"E:/Program Files/tar.exe -zcf ..."`
319 | * @param string $cmd
320 | * @return string
321 | */
322 | public static function escapeSpacesIfOnWindows(string $cmd): string
323 | {
324 | return defined('PHP_WINDOWS_VERSION_BUILD') ? sprintf('"%s"', $cmd) : $cmd;
325 | }
326 | }
327 |
--------------------------------------------------------------------------------