Completed
Push — master ( 908c0b...5086b8 )
by
unknown
23s queued 11s
created

ShellBuilder::addVariable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 12
rs 10
cc 3
nc 3
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPSu\ShellCommandBuilder;
6
7
use PHPSu\ShellCommandBuilder\Collection\CollectionTuple;
8
use PHPSu\ShellCommandBuilder\Collection\Pipeline;
9
use PHPSu\ShellCommandBuilder\Collection\Redirection;
10
use PHPSu\ShellCommandBuilder\Collection\ShellList;
11
use PHPSu\ShellCommandBuilder\Conditional\BasicExpression;
12
use PHPSu\ShellCommandBuilder\Definition\ControlOperator;
13
use PHPSu\ShellCommandBuilder\Definition\GroupType;
14
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
15
use PHPSu\ShellCommandBuilder\Literal\ShellVariable;
16
use TypeError;
17
18
final class ShellBuilder implements ShellInterface, \JsonSerializable
19
{
20
    /** @var array<ShellInterface>  */
21
    private $commandList = [];
22
    /** @var int */
23
    private $groupType;
24
    /**
25
     * name of the coprocess - empty string means anonymous
26
     * @var null|string
27
     */
28
    private $asynchronously;
29
    /** @var bool */
30
    private $processSubstitution = false;
31
    /** @var bool */
32
    private $commandSubstitution = false;
33
    /** @var array<string, ShellVariable> */
34
    private $variables = [];
35
36
    /**
37
     * This is a shortcut for quicker fluid access to the shell builder
38
     * @return static
39
     */
40
    public static function new(): self
41
    {
42
        return new ShellBuilder();
43
    }
44
45
    /**
46
     * This is a shortcut for quicker fluid access to the command api
47
     * @param string $executable
48
     * @return ShellCommand
49
     */
50
    public static function command(string $executable): ShellCommand
51
    {
52
        return new ShellCommand($executable, new self());
53
    }
54
55
    public function __construct(int $groupType = GroupType::NO_GROUP)
56
    {
57
        $this->groupType = $groupType;
58
    }
59
60
    public function createCommand(string $name, bool $withNewBuilder = false): ShellCommand
61
    {
62
        return new ShellCommand($name, $withNewBuilder ? new self() : $this);
63
    }
64
65
    public function runAsynchronously(bool $isAsync = true, string $name = ''): self
66
    {
67
        $this->asynchronously = $isAsync ? $name : null;
68
        return $this;
69
    }
70
71
    /**
72
     * @param string $variable
73
     * @param string|ShellInterface $value
74
     * @param bool $useBackticks
75
     * @param bool $escape is the value instance of ShellInterface, then this variable is automatically false
76
     * @return $this
77
     * @throws ShellBuilderException
78
     */
79
    public function addVariable(string $variable, $value, bool $useBackticks = false, bool $escape = true): self
80
    {
81
        if (isset($this->variables[$variable])) {
82
            throw new ShellBuilderException('Variable has already been declared.');
83
        }
84
        $shellVariable = new ShellVariable($variable, $value);
85
        $shellVariable->wrapWithBackticks($useBackticks);
86
        if (is_string($value)) {
87
            $shellVariable->setEscape($escape);
88
        }
89
        $this->variables[$variable] = $shellVariable;
90
        return $this;
91
    }
92
93
    public function removeVariable(string $variable): self
94
    {
95
        unset($this->variables[$variable]);
96
        return $this;
97
    }
98
99
    /**
100
     * @param string|ShellInterface $command
101
     * @return $this
102
     * @throws ShellBuilderException
103
     */
104
    public function add($command): self
105
    {
106
        $command = $this->parseCommand($command, true);
107
        if (empty($this->commandList)) {
108
            $this->commandList[] = $command;
109
            return $this;
110
        }
111
        $this->commandList[] = ShellList::add($command);
112
        return $this;
113
    }
114
115
    /**
116
     * @param string|ShellInterface $command
117
     * @return $this
118
     * @throws ShellBuilderException
119
     */
120
    public function and($command): self
121
    {
122
        $this->commandList[] = ShellList::addAnd($this->parseCommand($command));
123
        return $this;
124
    }
125
126
    /**
127
     * @param string|ShellInterface $command
128
     * @return $this
129
     * @throws ShellBuilderException
130
     */
131
    public function or($command): self
132
    {
133
        $this->commandList[] = ShellList::addOr($this->parseCommand($command));
134
        return $this;
135
    }
136
137
    /**
138
     * @param string|ShellInterface $command
139
     * @return $this
140
     * @throws ShellBuilderException
141
     */
142
    public function async($command = ''): self
143
    {
144
        $this->commandList[] = ShellList::async($this->parseCommand($command));
145
        return $this;
146
    }
147
148
    /**
149
     * @param string|ShellInterface $command
150
     * @return $this
151
     * @throws ShellBuilderException
152
     */
153
    public function pipe($command): self
154
    {
155
        $this->commandList[] = Pipeline::pipe($this->parseCommand($command));
156
        return $this;
157
    }
158
159
    /**
160
     * @param string|ShellInterface $command
161
     * @return $this
162
     * @throws ShellBuilderException
163
     */
164
    public function pipeWithForward($command): self
165
    {
166
        $this->commandList[] = Pipeline::pipeErrorForward($this->parseCommand($command));
167
        return $this;
168
    }
169
170
    /**
171
     * @param string|ShellInterface $command
172
     * @param bool $append
173
     * @return $this
174
     * @throws ShellBuilderException
175
     */
176
    public function redirectOutput($command, bool $append = false): self
177
    {
178
        $command = $this->parseCommand($command);
179
        $this->commandList[] = Redirection::redirectOutput($command, $append);
180
        return $this;
181
    }
182
183
    /**
184
     * @param string|ShellInterface $command
185
     * @return $this
186
     * @throws ShellBuilderException
187
     */
188
    public function redirectInput($command): self
189
    {
190
        $command = $this->parseCommand($command);
191
        $this->commandList[] = Redirection::redirectInput($command);
192
        return $this;
193
    }
194
195
    /**
196
     * @param string|ShellInterface $command
197
     * @return $this
198
     * @throws ShellBuilderException
199
     */
200
    public function redirectError($command): self
201
    {
202
        $command = $this->parseCommand($command);
203
        $this->commandList[] = Redirection::redirectError($command);
204
        return $this;
205
    }
206
207
    /**
208
     * @param string|ShellInterface $command
209
     * @param bool $toLeft
210
     * @return $this
211
     * @throws ShellBuilderException
212
     */
213
    public function redirect($command, bool $toLeft = true): self
214
    {
215
        $command = $this->parseCommand($command);
216
        $this->commandList[] = Redirection::redirectBetweenFiles($command, $toLeft);
217
        return $this;
218
    }
219
220
    /**
221
     * @param ShellInterface|string $command
222
     * @param bool $toLeft
223
     * @param null|int $firstDescriptor
224
     * @param null|int $secondDescriptor
225
     * @return static
226
     * @throws ShellBuilderException
227
     */
228
    public function redirectDescriptor($command, bool $toLeft, int $firstDescriptor = null, int $secondDescriptor = null): self
229
    {
230
        $command = $this->parseCommand($command);
231
        $this->commandList[] = Redirection::redirectBetweenDescriptors($command, $toLeft, $firstDescriptor, $secondDescriptor);
232
        return $this;
233
    }
234
235
    public function redirectErrorToOutput(): self
236
    {
237
        $this->commandList[] = Redirection::redirectErrorToOutput();
238
        return $this;
239
    }
240
241
    public function addCondition(BasicExpression $condition): self
242
    {
243
        $this->commandList[] = $condition;
244
        return $this;
245
    }
246
247
    /**
248
     * @param string|ShellInterface $fileEnding
249
     * @return ShellBuilder
250
     * @throws ShellBuilderException
251
     */
252
    public function addFileEnding($fileEnding): self
253
    {
254
        $tuple = CollectionTuple::create($fileEnding, '.');
255
        $tuple
256
            ->noSpaceAfterJoin(true)
257
            ->noSpaceBeforeJoin(true);
258
        $this->commandList[] = $tuple;
259
        return $this;
260
    }
261
262
    public function createGroup(bool $inSameShell = false): self
263
    {
264
        return new self($inSameShell ? GroupType::SAMESHELL_GROUP : GroupType::SUBSHELL_GROUP);
265
    }
266
267
    public function createProcessSubstition(): self
268
    {
269
        $builder = new self(GroupType::SUBSHELL_GROUP);
270
        $builder->processSubstitution = true;
271
        return $builder;
272
    }
273
274
    public function createCommandSubstition(): self
275
    {
276
        $builder = new self(GroupType::SUBSHELL_GROUP);
277
        $builder->commandSubstitution = true;
278
        return $builder;
279
    }
280
281
    /**
282
     * @param string|ShellInterface $command
283
     * @param bool $allowEmpty
284
     * @return ShellInterface
285
     * @throws ShellBuilderException
286
     */
287
    private function parseCommand($command, bool $allowEmpty = false): ShellInterface
288
    {
289
        if (is_string($command)) {
290
            $command = $this->createCommand($command);
291
        }
292
        try {
293
            $this->validateCommand($command, $allowEmpty);
294
        } catch (TypeError $typeError) {
295
            throw new ShellBuilderException('Provided the wrong type - only ShellCommand and ShellBuilder allowed');
296
        }
297
        return $command;
298
    }
299
300
    /** @noinspection PhpUnusedParameterInspection */
301
    private function validateCommand(ShellInterface $command, bool $allowEmpty): void
0 ignored issues
show
Unused Code introduced by
The parameter $command is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

301
    private function validateCommand(/** @scrutinizer ignore-unused */ ShellInterface $command, bool $allowEmpty): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
302
    {
303
        if (!$allowEmpty && empty($this->commandList)) {
304
            throw new ShellBuilderException('You have to first add a command before you can combine it');
305
        }
306
    }
307
308
    private function variablesToString(): string
309
    {
310
        $variableString = '';
311
        foreach ($this->variables as $variable) {
312
            $variableString .= $variable . ';';
313
        }
314
        if ($variableString !== '') {
315
            $variableString .= ' ';
316
        }
317
        return $variableString;
318
    }
319
320
    public function jsonSerialize(): array
321
    {
322
        return $this->__toArray();
323
    }
324
325
    /**
326
     * @return array<mixed>
327
     */
328
    public function __toArray(): array
329
    {
330
        $commands = [];
331
        foreach ($this->commandList as $item) {
332
            $commands[] = $item->__toArray();
333
        }
334
        return $commands;
335
    }
336
337
    public function __toString(): string
338
    {
339
        $result = '';
340
        if ($this->asynchronously !== null) {
341
            $result = sprintf('coproc %s%s', $this->asynchronously, $this->asynchronously !== '' ? ' ' : '');
342
        }
343
        foreach ($this->commandList as $command) {
344
            $result .= $command;
345
        }
346
        if ($this->groupType === GroupType::SAMESHELL_GROUP) {
347
            return sprintf(
348
                '%s%s;%s',
349
                ControlOperator::CURLY_BLOCK_DEFINITON_OPEN,
350
                $result,
351
                ControlOperator::CURLY_BLOCK_DEFINITON_CLOSE
352
            );
353
        }
354
        if ($this->groupType === GroupType::SUBSHELL_GROUP) {
355
            $substitionType = '';
356
            if ($this->commandSubstitution) {
357
                $substitionType = '$';
358
            }
359
            if ($this->processSubstitution) {
360
                $substitionType = '<';
361
            }
362
            return sprintf(
363
                '%s%s%s%s',
364
                $substitionType,
365
                ControlOperator::BLOCK_DEFINITON_OPEN,
366
                $result,
367
                ControlOperator::BLOCK_DEFINITON_CLOSE
368
            );
369
        }
370
        return rtrim(sprintf('%s%s', $this->variablesToString(), $result));
371
    }
372
}
373