Skip to content

Commit

Permalink
Merge pull request #11 from phpsu/feature/implement-shell-variables
Browse files Browse the repository at this point in the history
✨ adding variables to the shellbuilder
  • Loading branch information
ChrisB9 committed May 31, 2020
2 parents 908c0bb + 67b45f1 commit 5086b87
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 10 deletions.
25 changes: 23 additions & 2 deletions README.md
Expand Up @@ -375,9 +375,10 @@ Let's look at two examples:

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

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

```php

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

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

# 3:

ShellBuilder::new()
->addVariable('a',
ShellBuilder::new()
->createCommand('cat')
->addNoSpaceArgument('file')
->addToBuilder()
->addFileEnding('txt'),
true // enable backticks
)
->add(ArithmeticExpression::create()->greater('$a', '5')->escapeValue(true))
->and(ShellBuilder::command('echo')->addArgument('hello'))
;

```

#### Coprocess
Expand Down
2 changes: 2 additions & 0 deletions src/Literal/ShellEnvironmentVariable.php
Expand Up @@ -4,6 +4,7 @@

namespace PHPSu\ShellCommandBuilder\Literal;

use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
use PHPSu\ShellCommandBuilder\ShellInterface;

/**
Expand All @@ -20,6 +21,7 @@ final class ShellEnvironmentVariable extends ShellWord
* ShellArgument constructor.
* @param string $option
* @param ShellInterface|string $value
* @throws ShellBuilderException
*/
public function __construct(string $option, $value)
{
Expand Down
36 changes: 36 additions & 0 deletions src/Literal/ShellVariable.php
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace PHPSu\ShellCommandBuilder\Literal;

use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
use PHPSu\ShellCommandBuilder\ShellInterface;

final class ShellVariable extends ShellWord
{
protected $isVariable = true;
protected $useAssignOperator = true;
protected $wrapAsSubcommand = true;
protected $spaceAfterValue = false;

/**
* ShellVariable constructor.
* @param string $option
* @param ShellInterface|string $value
* @throws ShellBuilderException
*/
public function __construct(string $option, $value)
{
parent::__construct($option, $value);
if ($this->value instanceof ShellInterface) {
$this->setEscape(false);
}
}

public function wrapWithBackticks(bool $enable): self
{
$this->wrapWithBacktricks = $enable;
return $this;
}
}
37 changes: 32 additions & 5 deletions src/Literal/ShellWord.php
Expand Up @@ -20,14 +20,31 @@ class ShellWord implements ShellInterface
protected const SHORT_OPTION_CONTROL = '-';
protected const EQUAL_CONTROL = '=';

/** @var bool */
/**
* @var bool
* @psalm-readonly
*/
protected $isShortOption = false;
/** @var bool */
/**
* @var bool
* @psalm-readonly
*/
protected $isOption = false;
/** @var bool */
/**
* @var bool
* @psalm-readonly
*/
protected $isArgument = false;
/** @var bool */
/**
* @var bool
* @psalm-readonly
*/
protected $isEnvironmentVariable = false;
/**
* @var bool
* @psalm-readonly
*/
protected $isVariable = false;
/** @var bool */
protected $isEscaped = true;
/** @var bool */
Expand All @@ -36,6 +53,10 @@ class ShellWord implements ShellInterface
protected $useAssignOperator = false;
/** @var bool */
protected $nameUpperCase = false;
/** @var bool */
protected $wrapAsSubcommand = false;
/** @var bool */
protected $wrapWithBacktricks = false;
/** @var string */
protected $prefix = '';
/** @var string */
Expand All @@ -56,7 +77,9 @@ class ShellWord implements ShellInterface
protected function __construct(string $argument, $value = '')
{
if (!empty($argument) && !$this->validShellWord($argument)) {
throw new ShellBuilderException('A Shell Argument has to be a valid Shell word and cannot contain e.g whitespace');
throw new ShellBuilderException(
'A Shell Argument has to be a valid Shell word and cannot contain e.g whitespace'
);
}
$this->argument = $argument;
$this->value = $value;
Expand Down Expand Up @@ -149,6 +172,7 @@ public function __toArray(): array
'isShortOption' => $this->isShortOption,
'isOption' => $this->isOption,
'isEnvironmentVariable' => $this->isEnvironmentVariable,
'isVariable' => $this->isVariable,
'escaped' => $this->isEscaped,
'withAssign' => $this->useAssignOperator,
'spaceAfterValue' => $this->spaceAfterValue,
Expand All @@ -162,6 +186,9 @@ public function __toString(): string
$this->prepare();
/** @var string $value */
$value = $this->getValue();
if ($this->value instanceof ShellInterface && $this->wrapAsSubcommand) {
$value = $this->wrapWithBacktricks ? "`$value`" : "$($value)";
}
return sprintf(
'%s%s%s%s%s',
$this->prefix,
Expand Down
49 changes: 46 additions & 3 deletions src/ShellBuilder.php
Expand Up @@ -12,11 +12,12 @@
use PHPSu\ShellCommandBuilder\Definition\ControlOperator;
use PHPSu\ShellCommandBuilder\Definition\GroupType;
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
use PHPSu\ShellCommandBuilder\Literal\ShellVariable;
use TypeError;

final class ShellBuilder implements ShellInterface, \JsonSerializable
{
/** @var array<ShellInterface|CollectionTuple> */
/** @var array<ShellInterface> */
private $commandList = [];
/** @var int */
private $groupType;
Expand All @@ -29,6 +30,8 @@ final class ShellBuilder implements ShellInterface, \JsonSerializable
private $processSubstitution = false;
/** @var bool */
private $commandSubstitution = false;
/** @var array<string, ShellVariable> */
private $variables = [];

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

/**
* @param string $variable
* @param string|ShellInterface $value
* @param bool $useBackticks
* @param bool $escape is the value instance of ShellInterface, then this variable is automatically false
* @return $this
* @throws ShellBuilderException
*/
public function addVariable(string $variable, $value, bool $useBackticks = false, bool $escape = true): self
{
if (isset($this->variables[$variable])) {
throw new ShellBuilderException('Variable has already been declared.');
}
$shellVariable = new ShellVariable($variable, $value);
$shellVariable->wrapWithBackticks($useBackticks);
if (is_string($value)) {
$shellVariable->setEscape($escape);
}
$this->variables[$variable] = $shellVariable;
return $this;
}

public function removeVariable(string $variable): self
{
unset($this->variables[$variable]);
return $this;
}

/**
* @param string|ShellInterface $command
* @return $this
Expand Down Expand Up @@ -274,6 +305,18 @@ private function validateCommand(ShellInterface $command, bool $allowEmpty): voi
}
}

private function variablesToString(): string
{
$variableString = '';
foreach ($this->variables as $variable) {
$variableString .= $variable . ';';
}
if ($variableString !== '') {
$variableString .= ' ';
}
return $variableString;
}

public function jsonSerialize(): array
{
return $this->__toArray();
Expand All @@ -286,7 +329,7 @@ public function __toArray(): array
{
$commands = [];
foreach ($this->commandList as $item) {
$commands[] = $item instanceof ShellInterface ? $item->__toArray() : $item;
$commands[] = $item->__toArray();
}
return $commands;
}
Expand Down Expand Up @@ -324,6 +367,6 @@ public function __toString(): string
ControlOperator::BLOCK_DEFINITON_CLOSE
);
}
return rtrim($result);
return rtrim(sprintf('%s%s', $this->variablesToString(), $result));
}
}
74 changes: 74 additions & 0 deletions tests/ShellBuilderTest.php
Expand Up @@ -10,6 +10,7 @@
use PHPSu\ShellCommandBuilder\Definition\GroupType;
use PHPSu\ShellCommandBuilder\Exception\ShellBuilderException;
use PHPSu\ShellCommandBuilder\ShellBuilder;
use PHPSu\ShellCommandBuilder\ShellCommand;
use PHPUnit\Framework\TestCase;

final class ShellBuilderTest extends TestCase
Expand Down Expand Up @@ -697,4 +698,77 @@ public function testSimpleAsyncShellBuilder(): void
ShellBuilder::new()->add('./import-script')->async('./import-script2')->async()
);
}

public function testAddVariableToShellBuilder(): void
{
$this->assertEquals(
"a='6';",
(string)ShellBuilder::new()->addVariable('a', '6')
);

$this->assertEquals(
"a='6';b=7;",
(string)ShellBuilder::new()->addVariable('a', '6')->addVariable('b', '7', false, false)
);

$this->assertEquals(
"a='6';b=$(cat);",
(string)ShellBuilder::new()->addVariable('a', '6')->addVariable('b', ShellBuilder::command('cat'))
);

$this->assertEquals(
"a='6';b=`cat`;",
(string)ShellBuilder::new()->addVariable('a', '6')->addVariable('b', ShellBuilder::command('cat'), true)
);
}

public function testAddDuplicateVariableException(): void
{
$this->expectException(ShellBuilderException::class);
$this->expectExceptionMessage('Variable has already been declared.');
ShellBuilder::new()
->addVariable('a', 'b')
->addVariable('a', 'c')
;
}

public function testAddAndRemoveVariablesFromList(): void
{
$builder = ShellBuilder::new()
->addVariable('a', 'b')
->addVariable('b', 'c')
->addVariable('c', 'd')
->addVariable('d', 'e')
->removeVariable('b')
;
$this->assertEquals("a='b';c='d';d='e';", (string)$builder);
}

public function testVariablesWithConditionalAndCommand(): void
{
$builder = ShellBuilder::new()
->addVariable('a', '6', false, false)
->add(ArithmeticExpression::create()->greater('$a', '5')->escapeValue(true))
->and(ShellBuilder::command('echo')->addArgument('hello'))
;
$this->assertEquals('a=6; [[ "$a" -gt "5" ]] && echo \'hello\'', $builder->__toString());
}

public function testCommandVariableWithConditionalAndCommand(): void
{
$builder = ShellBuilder::new()
->addVariable(
'a',
ShellBuilder::new()
->createCommand('cat')
->addNoSpaceArgument('file')
->addToBuilder()
->addFileEnding('txt'),
true
)
->add(ArithmeticExpression::create()->greater('$a', '5')->escapeValue(true))
->and(ShellBuilder::command('echo')->addArgument('hello'))
;
$this->assertEquals('a=`cat file.txt`; [[ "$a" -gt "5" ]] && echo \'hello\'', $builder->__toString());
}
}
4 changes: 4 additions & 0 deletions tests/ShellCommandTest.php
Expand Up @@ -93,6 +93,7 @@ public function testShellCommandToArray(): void
'isShortOption' => false,
'isOption' => true,
'isEnvironmentVariable' => false,
'isVariable' => false,
'escaped' => true,
'withAssign' => true,
'spaceAfterValue' => true,
Expand All @@ -112,6 +113,7 @@ public function testShellCommandArgumentToArray(): void
'isShortOption' => false,
'isOption' => false,
'isEnvironmentVariable' => false,
'isVariable' => false,
'escaped' => false,
'withAssign' => false,
'spaceAfterValue' => true,
Expand Down Expand Up @@ -145,6 +147,7 @@ public function testShellCommandWithCommandSubstitutionToArray(): void
'isShortOption' => false,
'isOption' => false,
'isEnvironmentVariable' => true,
'isVariable' => false,
'escaped' => true,
'withAssign' => true,
'spaceAfterValue' => true,
Expand All @@ -158,6 +161,7 @@ public function testShellCommandWithCommandSubstitutionToArray(): void
'isShortOption' => false,
'isOption' => true,
'isEnvironmentVariable' => false,
'isVariable' => false,
'escaped' => true,
'withAssign' => true,
'spaceAfterValue' => true,
Expand Down

0 comments on commit 5086b87

Please sign in to comment.