Skip to content

Commit 67b45f1

Browse files
author
Christian Benthake
committed
✨ adding variables to the shellbuilder
1 parent 908c0bb commit 67b45f1

8 files changed

Lines changed: 280 additions & 10 deletions

File tree

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,10 @@ Let's look at two examples:
375375

376376
# 2:
377377
a=6; [[ "$a" -gt "5" ]] && echo "hello";
378-
```
379378

380-
> Note: the "a=6;" part is currently not available
379+
# 3:
380+
a=`cat file.txt`; [[ "$a" -gt "5" ]] && echo "hello";
381+
```
381382

382383
```php
383384

@@ -393,10 +394,30 @@ ShellBuilder::new()
393394

394395
# 2:
395396
ShellBuilder::new()
397+
// adding a variable "a" with the value "6"
398+
// the third argument replaces $() through backticks --> a=$(cat) ~> a=`cat`
399+
// the fourth argument sets escpaing to false.
400+
// Escaping is disabled for commands as value.
401+
->addVariable('a', '6', false, false)
396402
->add(ArithmeticExpression::create()->greater('$a', '5'))
397403
->and(ShellBuilder::command('echo')->addArgument('hello'))
398404
;
399405

406+
# 3:
407+
408+
ShellBuilder::new()
409+
->addVariable('a',
410+
ShellBuilder::new()
411+
->createCommand('cat')
412+
->addNoSpaceArgument('file')
413+
->addToBuilder()
414+
->addFileEnding('txt'),
415+
true // enable backticks
416+
)
417+
->add(ArithmeticExpression::create()->greater('$a', '5')->escapeValue(true))
418+
->and(ShellBuilder::command('echo')->addArgument('hello'))
419+
;
420+
400421
```
401422

402423
#### Coprocess

src/Literal/ShellEnvironmentVariable.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PHPSu\ShellCommandBuilder\Literal;
66

7+
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
78
use PHPSu\ShellCommandBuilder\ShellInterface;
89

910
/**
@@ -20,6 +21,7 @@ final class ShellEnvironmentVariable extends ShellWord
2021
* ShellArgument constructor.
2122
* @param string $option
2223
* @param ShellInterface|string $value
24+
* @throws ShellBuilderException
2325
*/
2426
public function __construct(string $option, $value)
2527
{

src/Literal/ShellVariable.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPSu\ShellCommandBuilder\Literal;
6+
7+
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
8+
use PHPSu\ShellCommandBuilder\ShellInterface;
9+
10+
final class ShellVariable extends ShellWord
11+
{
12+
protected $isVariable = true;
13+
protected $useAssignOperator = true;
14+
protected $wrapAsSubcommand = true;
15+
protected $spaceAfterValue = false;
16+
17+
/**
18+
* ShellVariable constructor.
19+
* @param string $option
20+
* @param ShellInterface|string $value
21+
* @throws ShellBuilderException
22+
*/
23+
public function __construct(string $option, $value)
24+
{
25+
parent::__construct($option, $value);
26+
if ($this->value instanceof ShellInterface) {
27+
$this->setEscape(false);
28+
}
29+
}
30+
31+
public function wrapWithBackticks(bool $enable): self
32+
{
33+
$this->wrapWithBacktricks = $enable;
34+
return $this;
35+
}
36+
}

src/Literal/ShellWord.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,31 @@ class ShellWord implements ShellInterface
2020
protected const SHORT_OPTION_CONTROL = '-';
2121
protected const EQUAL_CONTROL = '=';
2222

23-
/** @var bool */
23+
/**
24+
* @var bool
25+
* @psalm-readonly
26+
*/
2427
protected $isShortOption = false;
25-
/** @var bool */
28+
/**
29+
* @var bool
30+
* @psalm-readonly
31+
*/
2632
protected $isOption = false;
27-
/** @var bool */
33+
/**
34+
* @var bool
35+
* @psalm-readonly
36+
*/
2837
protected $isArgument = false;
29-
/** @var bool */
38+
/**
39+
* @var bool
40+
* @psalm-readonly
41+
*/
3042
protected $isEnvironmentVariable = false;
43+
/**
44+
* @var bool
45+
* @psalm-readonly
46+
*/
47+
protected $isVariable = false;
3148
/** @var bool */
3249
protected $isEscaped = true;
3350
/** @var bool */
@@ -36,6 +53,10 @@ class ShellWord implements ShellInterface
3653
protected $useAssignOperator = false;
3754
/** @var bool */
3855
protected $nameUpperCase = false;
56+
/** @var bool */
57+
protected $wrapAsSubcommand = false;
58+
/** @var bool */
59+
protected $wrapWithBacktricks = false;
3960
/** @var string */
4061
protected $prefix = '';
4162
/** @var string */
@@ -56,7 +77,9 @@ class ShellWord implements ShellInterface
5677
protected function __construct(string $argument, $value = '')
5778
{
5879
if (!empty($argument) && !$this->validShellWord($argument)) {
59-
throw new ShellBuilderException('A Shell Argument has to be a valid Shell word and cannot contain e.g whitespace');
80+
throw new ShellBuilderException(
81+
'A Shell Argument has to be a valid Shell word and cannot contain e.g whitespace'
82+
);
6083
}
6184
$this->argument = $argument;
6285
$this->value = $value;
@@ -149,6 +172,7 @@ public function __toArray(): array
149172
'isShortOption' => $this->isShortOption,
150173
'isOption' => $this->isOption,
151174
'isEnvironmentVariable' => $this->isEnvironmentVariable,
175+
'isVariable' => $this->isVariable,
152176
'escaped' => $this->isEscaped,
153177
'withAssign' => $this->useAssignOperator,
154178
'spaceAfterValue' => $this->spaceAfterValue,
@@ -162,6 +186,9 @@ public function __toString(): string
162186
$this->prepare();
163187
/** @var string $value */
164188
$value = $this->getValue();
189+
if ($this->value instanceof ShellInterface && $this->wrapAsSubcommand) {
190+
$value = $this->wrapWithBacktricks ? "`$value`" : "$($value)";
191+
}
165192
return sprintf(
166193
'%s%s%s%s%s',
167194
$this->prefix,

src/ShellBuilder.php

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
use PHPSu\ShellCommandBuilder\Definition\ControlOperator;
1313
use PHPSu\ShellCommandBuilder\Definition\GroupType;
1414
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
15+
use PHPSu\ShellCommandBuilder\Literal\ShellVariable;
1516
use TypeError;
1617

1718
final class ShellBuilder implements ShellInterface, \JsonSerializable
1819
{
19-
/** @var array<ShellInterface|CollectionTuple> */
20+
/** @var array<ShellInterface> */
2021
private $commandList = [];
2122
/** @var int */
2223
private $groupType;
@@ -29,6 +30,8 @@ final class ShellBuilder implements ShellInterface, \JsonSerializable
2930
private $processSubstitution = false;
3031
/** @var bool */
3132
private $commandSubstitution = false;
33+
/** @var array<string, ShellVariable> */
34+
private $variables = [];
3235

3336
/**
3437
* This is a shortcut for quicker fluid access to the shell builder
@@ -65,6 +68,34 @@ public function runAsynchronously(bool $isAsync = true, string $name = ''): self
6568
return $this;
6669
}
6770

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+
6899
/**
69100
* @param string|ShellInterface $command
70101
* @return $this
@@ -274,6 +305,18 @@ private function validateCommand(ShellInterface $command, bool $allowEmpty): voi
274305
}
275306
}
276307

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+
277320
public function jsonSerialize(): array
278321
{
279322
return $this->__toArray();
@@ -286,7 +329,7 @@ public function __toArray(): array
286329
{
287330
$commands = [];
288331
foreach ($this->commandList as $item) {
289-
$commands[] = $item instanceof ShellInterface ? $item->__toArray() : $item;
332+
$commands[] = $item->__toArray();
290333
}
291334
return $commands;
292335
}
@@ -324,6 +367,6 @@ public function __toString(): string
324367
ControlOperator::BLOCK_DEFINITON_CLOSE
325368
);
326369
}
327-
return rtrim($result);
370+
return rtrim(sprintf('%s%s', $this->variablesToString(), $result));
328371
}
329372
}

tests/ShellBuilderTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPSu\ShellCommandBuilder\Definition\GroupType;
1111
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
1212
use PHPSu\ShellCommandBuilder\ShellBuilder;
13+
use PHPSu\ShellCommandBuilder\ShellCommand;
1314
use PHPUnit\Framework\TestCase;
1415

1516
final class ShellBuilderTest extends TestCase
@@ -697,4 +698,77 @@ public function testSimpleAsyncShellBuilder(): void
697698
ShellBuilder::new()->add('./import-script')->async('./import-script2')->async()
698699
);
699700
}
701+
702+
public function testAddVariableToShellBuilder(): void
703+
{
704+
$this->assertEquals(
705+
"a='6';",
706+
(string)ShellBuilder::new()->addVariable('a', '6')
707+
);
708+
709+
$this->assertEquals(
710+
"a='6';b=7;",
711+
(string)ShellBuilder::new()->addVariable('a', '6')->addVariable('b', '7', false, false)
712+
);
713+
714+
$this->assertEquals(
715+
"a='6';b=$(cat);",
716+
(string)ShellBuilder::new()->addVariable('a', '6')->addVariable('b', ShellBuilder::command('cat'))
717+
);
718+
719+
$this->assertEquals(
720+
"a='6';b=`cat`;",
721+
(string)ShellBuilder::new()->addVariable('a', '6')->addVariable('b', ShellBuilder::command('cat'), true)
722+
);
723+
}
724+
725+
public function testAddDuplicateVariableException(): void
726+
{
727+
$this->expectException(ShellBuilderException::class);
728+
$this->expectExceptionMessage('Variable has already been declared.');
729+
ShellBuilder::new()
730+
->addVariable('a', 'b')
731+
->addVariable('a', 'c')
732+
;
733+
}
734+
735+
public function testAddAndRemoveVariablesFromList(): void
736+
{
737+
$builder = ShellBuilder::new()
738+
->addVariable('a', 'b')
739+
->addVariable('b', 'c')
740+
->addVariable('c', 'd')
741+
->addVariable('d', 'e')
742+
->removeVariable('b')
743+
;
744+
$this->assertEquals("a='b';c='d';d='e';", (string)$builder);
745+
}
746+
747+
public function testVariablesWithConditionalAndCommand(): void
748+
{
749+
$builder = ShellBuilder::new()
750+
->addVariable('a', '6', false, false)
751+
->add(ArithmeticExpression::create()->greater('$a', '5')->escapeValue(true))
752+
->and(ShellBuilder::command('echo')->addArgument('hello'))
753+
;
754+
$this->assertEquals('a=6; [[ "$a" -gt "5" ]] && echo \'hello\'', $builder->__toString());
755+
}
756+
757+
public function testCommandVariableWithConditionalAndCommand(): void
758+
{
759+
$builder = ShellBuilder::new()
760+
->addVariable(
761+
'a',
762+
ShellBuilder::new()
763+
->createCommand('cat')
764+
->addNoSpaceArgument('file')
765+
->addToBuilder()
766+
->addFileEnding('txt'),
767+
true
768+
)
769+
->add(ArithmeticExpression::create()->greater('$a', '5')->escapeValue(true))
770+
->and(ShellBuilder::command('echo')->addArgument('hello'))
771+
;
772+
$this->assertEquals('a=`cat file.txt`; [[ "$a" -gt "5" ]] && echo \'hello\'', $builder->__toString());
773+
}
700774
}

tests/ShellCommandTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public function testShellCommandToArray(): void
9393
'isShortOption' => false,
9494
'isOption' => true,
9595
'isEnvironmentVariable' => false,
96+
'isVariable' => false,
9697
'escaped' => true,
9798
'withAssign' => true,
9899
'spaceAfterValue' => true,
@@ -112,6 +113,7 @@ public function testShellCommandArgumentToArray(): void
112113
'isShortOption' => false,
113114
'isOption' => false,
114115
'isEnvironmentVariable' => false,
116+
'isVariable' => false,
115117
'escaped' => false,
116118
'withAssign' => false,
117119
'spaceAfterValue' => true,
@@ -145,6 +147,7 @@ public function testShellCommandWithCommandSubstitutionToArray(): void
145147
'isShortOption' => false,
146148
'isOption' => false,
147149
'isEnvironmentVariable' => true,
150+
'isVariable' => false,
148151
'escaped' => true,
149152
'withAssign' => true,
150153
'spaceAfterValue' => true,
@@ -158,6 +161,7 @@ public function testShellCommandWithCommandSubstitutionToArray(): void
158161
'isShortOption' => false,
159162
'isOption' => true,
160163
'isEnvironmentVariable' => false,
164+
'isVariable' => false,
161165
'escaped' => true,
162166
'withAssign' => true,
163167
'spaceAfterValue' => true,

0 commit comments

Comments
 (0)