ShellCommand::addSubCommand()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPSu\ShellCommandBuilder;
6
7
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
8
use PHPSu\ShellCommandBuilder\Literal\ShellArgument;
9
use PHPSu\ShellCommandBuilder\Literal\ShellEnvironmentVariable;
10
use PHPSu\ShellCommandBuilder\Literal\ShellExecutable;
11
use PHPSu\ShellCommandBuilder\Literal\ShellOption;
12
use PHPSu\ShellCommandBuilder\Literal\ShellShortOption;
13
use PHPSu\ShellCommandBuilder\Literal\ShellWord;
14
15
/**
16
 * @internal
17
 * @psalm-internal PHPSu\ShellCommandBuilder
18
 */
19
final class ShellCommand implements ShellInterface
20
{
21
    /**
22
     * @var ShellWord
23
     * @psalm-readonly
24
     */
25
    private $executable;
26
    /** @var array<ShellWord> */
27
    private $arguments = [];
28
    /** @var array<ShellWord> */
29
    private $environmentVariables = [];
30
    /** @var bool  */
31
    private $isCommandSubstitution = false;
32
    /** @var bool  */
33
    private $isProcessSubstitution = false;
34
    /** @var ShellBuilder|null */
35
    private $parentBuilder;
36
    /** @var bool */
37
    private $invertOutput = false;
38
39
    public function __construct(string $name, ShellBuilder $builder = null)
40
    {
41
        $this->executable = new ShellExecutable($name);
42
        $this->parentBuilder = $builder;
43
    }
44
45
    public function addToBuilder(): ShellBuilder
46
    {
47
        if ($this->parentBuilder === null) {
48
            throw new ShellBuilderException('You need to create a ShellBuilder first before you can use it within a command');
49
        }
50
        return $this->parentBuilder->add($this);
51
    }
52
53
    public function toggleCommandSubstitution(): self
54
    {
55
        $this->isCommandSubstitution = !$this->isCommandSubstitution;
56
        return $this;
57
    }
58
59
    public function isProcessSubstitution(bool $enable = true): self
60
    {
61
        $this->isProcessSubstitution = $enable;
62
        return $this;
63
    }
64
65
    public function invert(bool $invert = true): self
66
    {
67
        $this->invertOutput = $invert;
68
        return $this;
69
    }
70
71
    /**
72
     * @param string $option
73
     * @param ShellInterface|string|mixed $value
74
     * @param bool $escapeArgument
75
     * @param bool $withAssignOperator
76
     * @return self
77
     * @throws ShellBuilderException
78
     */
79
    public function addShortOption(string $option, $value = '', bool $escapeArgument = true, bool $withAssignOperator = false): self
80
    {
81
        if (!($value instanceof ShellInterface || is_string($value))) {
82
            throw new ShellBuilderException('Provided the wrong type - only ShellCommand and ShellBuilder allowed');
83
        }
84
        $word = new ShellShortOption($option, $value);
85
        return $this->add($word, $escapeArgument, $withAssignOperator);
86
    }
87
88
    /**
89
     * @param string $option
90
     * @param ShellInterface|string|mixed $value
91
     * @param bool $escapeArgument
92
     * @param bool $withAssignOperator
93
     * @return self
94
     * @throws ShellBuilderException
95
     */
96
    public function addOption(string $option, $value = '', bool $escapeArgument = true, bool $withAssignOperator = false): self
97
    {
98
        if (!($value instanceof ShellInterface || is_string($value))) {
99
            throw new ShellBuilderException('Provided the wrong type - only ShellCommand and ShellBuilder allowed');
100
        }
101
        $word = new ShellOption($option, $value);
102
        return $this->add($word, $escapeArgument, $withAssignOperator);
103
    }
104
105
    /**
106
     * @param ShellInterface|string|mixed $argument
107
     * @param bool $escapeArgument
108
     * @return self
109
     * @throws ShellBuilderException
110
     */
111
    public function addArgument($argument, bool $escapeArgument = true): self
112
    {
113
        if (!($argument instanceof ShellInterface || is_string($argument))) {
114
            throw new ShellBuilderException('Provided the wrong type - only ShellCommand and ShellBuilder allowed');
115
        }
116
        $word = new ShellArgument($argument);
117
        return $this->add($word, $escapeArgument);
118
    }
119
120
    /**
121
     * This is an alias for argument, that automatically escapes the argument.
122
     * It does in the end does not provide any additional functionality
123
     *
124
     * @param ShellInterface $argument
125
     * @return $this
126
     * @throws ShellBuilderException
127
     */
128
    public function addSubCommand(ShellInterface $argument): self
129
    {
130
        return $this->addArgument($argument, true);
131
    }
132
133
    /**
134
     * @param ShellInterface|string|mixed $argument
135
     * @return self
136
     * @throws ShellBuilderException
137
     */
138
    public function addNoSpaceArgument($argument): self
139
    {
140
        if (!($argument instanceof ShellInterface || is_string($argument))) {
141
            throw new ShellBuilderException('Provided the wrong type - only ShellCommand and ShellBuilder allowed');
142
        }
143
        $word = new ShellArgument($argument);
144
        $word->setSpaceAfterValue(false);
145
        return $this->add($word, false);
146
    }
147
148
    private function add(ShellWord $word, bool $escapeArgument, bool $withAssignOperator = false): self
149
    {
150
        $word->setEscape($escapeArgument);
151
        $word->setAssignOperator($withAssignOperator);
152
        $this->arguments[] = $word;
153
        return $this;
154
    }
155
156
    public function addEnv(string $name, string $value): self
157
    {
158
        $word = new ShellEnvironmentVariable($name, $value);
159
        $this->environmentVariables[$name] = $word;
160
        return $this;
161
    }
162
163
    private function argumentsToString(): string
164
    {
165
        $result = [];
166
        foreach ($this->arguments as $part) {
167
            $result[] = $part;
168
        }
169
        return trim(implode('', $result));
170
    }
171
172
    private function environmentVariablesToString(): string
173
    {
174
        $result = [];
175
        foreach ($this->environmentVariables as $part) {
176
            $result[] = $part;
177
        }
178
        return implode('', $result);
179
    }
180
181
    /**
182
     * @return array<string, mixed>
183
     */
184
    public function __toArray(): array
185
    {
186
        $commands = [];
187
        foreach ($this->arguments as $item) {
188
            $commands[] = $item->__toArray();
189
        }
190
        $envs = [];
191
        foreach ($this->environmentVariables as $item) {
192
            $envs[] = $item->__toArray();
193
        }
194
        return [
195
            'executable' => $this->executable->__toString(),
196
            'arguments' => $commands,
197
            'isCommandSubstitution' => $this->isCommandSubstitution,
198
            'environmentVariables' => $envs,
199
        ];
200
    }
201
202
    public function __toString(): string
203
    {
204
        /** @psalm-suppress ImplicitToStringCast */
205
        $result = (sprintf(
206
            '%s%s%s%s',
207
            $this->invertOutput ? '! ' : '',
208
            empty($this->environmentVariables) ? '' : $this->environmentVariablesToString(),
209
            $this->executable,
210
            empty($this->arguments) ? '' : ' ' . $this->argumentsToString()
211
        ));
212
        if ($this->isCommandSubstitution && !$this->isProcessSubstitution) {
213
            return sprintf("\$(%s)", $result);
214
        }
215
        if ($this->isProcessSubstitution && !$this->isCommandSubstitution) {
216
            return sprintf("<(%s)", $result);
217
        }
218
        return $result;
219
    }
220
}
221