--- /dev/null
+<?php
+namespace Robo\Task;
+
+use Robo\Contract\WrappedTaskInterface;
+use Robo\Exception\TaskException;
+use Robo\TaskInfo;
+use Robo\Result;
+use Robo\Contract\TaskInterface;
+use Robo\Contract\SimulatedInterface;
+use Robo\Log\RoboLogLevel;
+use Robo\Contract\CommandInterface;
+
+class Simulator extends BaseTask implements CommandInterface
+{
+ /**
+ * @var \Robo\Contract\TaskInterface
+ */
+ protected $task;
+
+ /**
+ * @var array
+ */
+ protected $constructorParameters;
+
+ /**
+ * @var array
+ */
+ protected $stack = [];
+
+ /**
+ * @param \Robo\Contract\TaskInterface $task
+ * @param array $constructorParameters
+ */
+ public function __construct(TaskInterface $task, $constructorParameters)
+ {
+ // TODO: If we ever want to convert the simulated task back into
+ // an executable task, then we should save the wrapped task.
+ $this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
+ $this->constructorParameters = $constructorParameters;
+ }
+
+ /**
+ * @param string $function
+ * @param array $args
+ *
+ * @return \Robo\Result|\Robo\Task\Simulator
+ */
+ public function __call($function, $args)
+ {
+ $this->stack[] = array_merge([$function], $args);
+ $result = call_user_func_array([$this->task, $function], $args);
+ return $result == $this->task ? $this : $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run()
+ {
+ $callchain = '';
+ foreach ($this->stack as $action) {
+ $command = array_shift($action);
+ $parameters = $this->formatParameters($action);
+ $callchain .= "\n ->$command(<fg=green>$parameters</>)";
+ }
+ $context = $this->getTaskContext(
+ [
+ '_level' => RoboLogLevel::SIMULATED_ACTION,
+ 'simulated' => TaskInfo::formatTaskName($this->task),
+ 'parameters' => $this->formatParameters($this->constructorParameters),
+ '_style' => ['simulated' => 'fg=blue;options=bold'],
+ ]
+ );
+
+ // RoboLogLevel::SIMULATED_ACTION
+ $this->printTaskInfo(
+ "Simulating {simulated}({parameters})$callchain",
+ $context
+ );
+
+ $result = null;
+ if ($this->task instanceof SimulatedInterface) {
+ $result = $this->task->simulate($context);
+ }
+ if (!isset($result)) {
+ $result = Result::success($this);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Danger: reach through the simulated wrapper and pull out the command
+ * to be executed. This is used when using a simulated task with another
+ * simulated task that runs commands, e.g. the Remote\Ssh task. Using
+ * a simulated CommandInterface task with a non-simulated task may produce
+ * unexpected results (e.g. execution!).
+ *
+ * @return string
+ *
+ * @throws \Robo\Exception\TaskException
+ */
+ public function getCommand()
+ {
+ if (!$this->task instanceof CommandInterface) {
+ throw new TaskException($this->task, 'Simulated task that is not a CommandInterface used as a CommandInterface.');
+ }
+ return $this->task->getCommand();
+ }
+
+ /**
+ * @param string $action
+ *
+ * @return string
+ */
+ protected function formatParameters($action)
+ {
+ $parameterList = array_map([$this, 'convertParameter'], $action);
+ return implode(', ', $parameterList);
+ }
+
+ /**
+ * @param mixed $item
+ *
+ * @return string
+ */
+ protected function convertParameter($item)
+ {
+ if (is_callable($item)) {
+ return 'inline_function(...)';
+ }
+ if (is_array($item)) {
+ return $this->shortenParameter(var_export($item, true));
+ }
+ if (is_object($item)) {
+ return '[' . get_class($item). ' object]';
+ }
+ if (is_string($item)) {
+ return $this->shortenParameter("'$item'");
+ }
+ if (is_null($item)) {
+ return 'null';
+ }
+ return $item;
+ }
+
+ /**
+ * @param string $item
+ * @param string $shortForm
+ *
+ * @return string
+ */
+ protected function shortenParameter($item, $shortForm = '')
+ {
+ $maxLength = 80;
+ $tailLength = 20;
+ if (strlen($item) < $maxLength) {
+ return $item;
+ }
+ if (!empty($shortForm)) {
+ return $shortForm;
+ }
+ $item = trim($item);
+ $tail = preg_replace("#.*\n#ms", '', substr($item, -$tailLength));
+ $head = preg_replace("#\n.*#ms", '', substr($item, 0, $maxLength - (strlen($tail) + 5)));
+ return "$head ... $tail";
+ }
+}