Add pear modules, mail and net_smtp via composer (#93)

Add pear modules, mail and net_smtp via composer, remove php 5.6 build due to phpunit 6
This commit is contained in:
Thilina Hasantha
2018-01-08 23:13:43 +01:00
committed by GitHub
parent 359e3f8382
commit e7792e7d79
2349 changed files with 117270 additions and 83170 deletions
+19 -2
View File
@@ -3,8 +3,6 @@ namespace Robo;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
class Application extends SymfonyApplication
@@ -25,6 +23,11 @@ class Application extends SymfonyApplication
->addOption(
new InputOption('--progress-delay', null, InputOption::VALUE_REQUIRED, 'Number of seconds before progress bar is displayed in long-running task collections. Default: 2s.', Config::DEFAULT_PROGRESS_DELAY)
);
$this->getDefinition()
->addOption(
new InputOption('--define', '-D', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Define a configuration item value.', [])
);
}
/**
@@ -53,4 +56,18 @@ class Application extends SymfonyApplication
});
$this->add($createRoboFile);
}
/**
* Add self update command, do nothing if null is provided
*
* @param string $repository GitHub Repository for self update
*/
public function addSelfUpdateCommand($repository = null)
{
if (!$repository) {
return;
}
$selfUpdateCommand = new SelfUpdateCommand($this->getName(), $this->getVersion(), $repository);
$this->add($selfUpdateCommand);
}
}
@@ -3,7 +3,8 @@ namespace Robo\Collection;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Collection\Collection;
use Robo\State\StateAwareInterface;
use Robo\State\Data;
/**
* Creates a task wrapper that converts any Callable into an
@@ -35,7 +36,7 @@ class CallableTask implements TaskInterface
*/
public function run()
{
$result = call_user_func($this->fn);
$result = call_user_func($this->fn, $this->getState());
// If the function returns no result, then count it
// as a success.
if (!isset($result)) {
@@ -50,4 +51,12 @@ class CallableTask implements TaskInterface
return $result;
}
public function getState()
{
if ($this->reference instanceof StateAwareInterface) {
return $this->reference->getState();
}
return new Data();
}
}
@@ -2,6 +2,7 @@
namespace Robo\Collection;
use Robo\Result;
use Robo\State\Data;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
use Robo\Task\StackBasedTask;
@@ -12,9 +13,9 @@ use Robo\Exception\TaskException;
use Robo\Exception\TaskExitException;
use Robo\Contract\CommandInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\InflectionInterface;
use Robo\State\StateAwareInterface;
use Robo\State\StateAwareTrait;
/**
* Group tasks into a collection that run together. Supports
@@ -30,8 +31,10 @@ use Robo\Contract\InflectionInterface;
* called. Here, taskDeleteDir is used to remove partial results
* of an unfinished task.
*/
class Collection extends BaseTask implements CollectionInterface, CommandInterface
class Collection extends BaseTask implements CollectionInterface, CommandInterface, StateAwareInterface
{
use StateAwareTrait;
/**
* @var \Robo\Collection\Element[]
*/
@@ -52,11 +55,22 @@ class Collection extends BaseTask implements CollectionInterface, CommandInterfa
*/
protected $parentCollection;
/**
* @var callable[]
*/
protected $deferredCallbacks = [];
/**
* @var string[]
*/
protected $messageStoreKeys = [];
/**
* Constructor.
*/
public function __construct()
{
$this->resetState();
}
public function setProgressBarAutoDisplayInterval($interval)
@@ -165,6 +179,7 @@ class Collection extends BaseTask implements CollectionInterface, CommandInterfa
$context += TaskInfo::getTaskContext($this);
return $this->addCode(
function () use ($level, $text, $context) {
$context += $this->getState()->getData();
$this->printTaskOutput($level, $text, $context);
}
);
@@ -552,6 +567,8 @@ class Collection extends BaseTask implements CollectionInterface, CommandInterfa
// the incremental results, if they wish.
$key = Result::isUnnamed($taskName) ? $name : $taskName;
$result->accumulate($key, $taskResult);
// The result message will be the message of the last task executed.
$result->setMessage($taskResult->getMessage());
}
} catch (TaskExitException $exitException) {
$this->fail();
@@ -632,10 +649,79 @@ class Collection extends BaseTask implements CollectionInterface, CommandInterfa
if ($original instanceof InflectionInterface) {
$original->inflect($this);
}
if ($original instanceof StateAwareInterface) {
$original->setState($this->getState());
}
$this->doDeferredInitialization($original);
$taskResult = $task->run();
$taskResult = Result::ensureResult($task, $taskResult);
$this->doStateUpdates($original, $taskResult);
return $taskResult;
}
protected function doStateUpdates($task, Data $taskResult)
{
$this->updateState($taskResult);
$key = spl_object_hash($task);
if (array_key_exists($key, $this->messageStoreKeys)) {
$state = $this->getState();
list($stateKey, $sourceKey) = $this->messageStoreKeys[$key];
$value = empty($sourceKey) ? $taskResult->getMessage() : $taskResult[$sourceKey];
$state[$stateKey] = $value;
}
}
public function storeState($task, $key, $source = '')
{
$this->messageStoreKeys[spl_object_hash($task)] = [$key, $source];
return $this;
}
public function deferTaskConfiguration($task, $functionName, $stateKey)
{
return $this->defer(
$task,
function ($task, $state) use ($functionName, $stateKey) {
$fn = [$task, $functionName];
$value = $state[$stateKey];
$fn($value);
}
);
}
/**
* Defer execution of a callback function until just before a task
* runs. Use this time to provide more settings for the task, e.g. from
* the collection's shared state, which is populated with the results
* of previous test runs.
*/
public function defer($task, $callback)
{
$this->deferredCallbacks[spl_object_hash($task)][] = $callback;
return $this;
}
protected function doDeferredInitialization($task)
{
// If the task is a state consumer, then call its receiveState method
if ($task instanceof \Robo\State\Consumer) {
$task->receiveState($this->getState());
}
// Check and see if there are any deferred callbacks for this task.
$key = spl_object_hash($task);
if (!array_key_exists($key, $this->deferredCallbacks)) {
return;
}
// Call all of the deferred callbacks
foreach ($this->deferredCallbacks[$key] as $fn) {
$fn($task, $this->getState());
}
}
/**
* @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
* @param $parentCollection
@@ -1,26 +1,22 @@
<?php
namespace Robo\Collection;
use Guzzle\Inflection\InflectorInterface;
use Robo\Config;
use Robo\Common\Timer;
use Consolidation\Config\Inject\ConfigForSetters;
use Robo\Config\Config;
use Psr\Log\LogLevel;
use Robo\Contract\InflectionInterface;
use Robo\Contract\TaskInterface;
use Robo\Contract\CompletionInterface;
use Robo\Contract\WrappedTaskInterface;
use Robo\Collection\NestedCollectionInterface;
use Robo\LoadAllTasks;
use Robo\Task\Simulator;
use Robo\Collection\CompletionWrapper;
use Robo\Collection\Temporary;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
use ReflectionClass;
use Robo\Task\BaseTask;
use Robo\Contract\BuilderAwareInterface;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\State\StateAwareInterface;
use Robo\State\StateAwareTrait;
use Robo\Result;
/**
* Creates a collection, and adds tasks to it. The collection builder
@@ -49,8 +45,10 @@ use Robo\Exception\TaskException;
* In the example above, the `taskDeleteDir` will be called if
* ```
*/
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface, StateAwareInterface
{
use StateAwareTrait;
/**
* @var \Robo\Tasks
*/
@@ -77,6 +75,19 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
public function __construct($commandFile)
{
$this->commandFile = $commandFile;
$this->resetState();
}
public static function create($container, $commandFile)
{
$builder = new self($commandFile);
$builder->setLogger($container->get('logger'));
$builder->setProgressIndicator($container->get('progressIndicator'));
$builder->setConfig($container->get('config'));
$builder->setOutputAdapter($container->get('outputAdapter'));
return $builder;
}
/**
@@ -147,9 +158,18 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
return $this;
}
public function addCode(callable $code)
/**
* Add arbitrary code to execute as a task.
*
* @see \Robo\Collection\CollectionInterface::addCode
*
* @param callable $code
* @param int|string $name
* @return $this
*/
public function addCode(callable $code, $name = \Robo\Collection\CollectionInterface::UNNAMEDTASK)
{
$this->getCollection()->addCode($code);
$this->getCollection()->addCode($code, $name);
return $this;
}
@@ -245,6 +265,51 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
return $this;
}
public function getState()
{
$collection = $this->getCollection();
return $collection->getState();
}
public function storeState($key, $source = '')
{
return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
}
public function deferTaskConfiguration($functionName, $stateKey)
{
return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
}
public function defer($callback)
{
return $this->callCollectionStateFuntion(__FUNCTION__, func_get_args());
}
protected function callCollectionStateFuntion($functionName, $args)
{
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
array_unshift($args, $currentTask);
$collection = $this->getCollection();
$fn = [$collection, $functionName];
call_user_func_array($fn, $args);
return $this;
}
public function setVerbosityThreshold($verbosityThreshold)
{
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
if ($currentTask) {
$currentTask->setVerbosityThreshold($verbosityThreshold);
return $this;
}
parent::setVerbosityThreshold($verbosityThreshold);
return $this;
}
/**
* Return the current task for this collection builder.
* TODO: Not needed?
@@ -266,6 +331,9 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
$collectionBuilder = new self($this->commandFile);
$collectionBuilder->inflect($this);
$collectionBuilder->simulated($this->isSimulated());
$collectionBuilder->setVerbosityThreshold($this->verbosityThreshold());
$collectionBuilder->setState($this->getState());
return $collectionBuilder;
}
@@ -355,6 +423,7 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
throw new RuntimeException("Can not construct task $name");
}
$task = $this->fixTask($task, $args);
$this->configureTask($name, $task);
return $this->addTaskToCollection($task);
}
@@ -366,10 +435,15 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
*/
protected function fixTask($task, $args)
{
$task->inflect($this);
if ($task instanceof InflectionInterface) {
$task->inflect($this);
}
if ($task instanceof BuilderAwareInterface) {
$task->setBuilder($this);
}
if ($task instanceof VerbosityThresholdInterface) {
$task->setVerbosityThreshold($this->verbosityThreshold());
}
// Do not wrap our wrappers.
if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
@@ -402,6 +476,25 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
return $task;
}
/**
* Check to see if there are any setter methods defined in configuration
* for this task.
*/
protected function configureTask($taskClass, $task)
{
$taskClass = static::configClassIdentifier($taskClass);
$configurationApplier = new ConfigForSetters($this->getConfig(), $taskClass, 'task.');
$configurationApplier->apply($task, 'settings');
// TODO: If we counted each instance of $taskClass that was called from
// this builder, then we could also apply configuration from
// "task.{$taskClass}[$N].settings"
// TODO: If the builder knew what the current command name was,
// then we could also search for task configuration under
// command-specific keys such as "command.{$commandname}.task.{$taskClass}.settings".
}
/**
* When we run the collection builder, run everything in the collection.
*
@@ -413,6 +506,7 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
$result = $this->runTasks();
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
$result->mergeData($this->getState()->getData());
return $result;
}
@@ -425,7 +519,8 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
protected function runTasks()
{
if (!$this->collection && $this->currentTask) {
return $this->currentTask->run();
$result = $this->currentTask->run();
return Result::ensureResult($this->currentTask, $result);
}
return $this->getCollection()->run();
}
@@ -464,6 +559,7 @@ class CollectionBuilder extends BaseTask implements NestedCollectionInterface, W
if (!isset($this->collection)) {
$this->collection = new Collection();
$this->collection->inflect($this);
$this->collection->setState($this->getState());
$this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
if (isset($this->currentTask)) {
@@ -141,7 +141,8 @@ interface CollectionInterface extends NestedCollectionInterface
* message will not be printed.
*
* @param string $text Message to print.
* @param array $context Extra context data for use by the logger.
* @param array $context Extra context data for use by the logger. Note
* that the data from the collection state is merged with the provided context.
* @param \Psr\Log\LogLevel|string $level The log level to print the information at. Default is NOTICE.
*
* @return $this
@@ -1,9 +1,6 @@
<?php
namespace Robo\Collection;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
interface NestedCollectionInterface
{
/**
@@ -1,11 +1,9 @@
<?php
namespace Robo\Collection;
use Robo\Collection\NestedCollectionInterface;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
@@ -2,8 +2,6 @@
namespace Robo\Collection;
use Robo\Contract\TaskInterface;
/**
* The temporary collection keeps track of the global collection of
* temporary cleanup tasks in instances where temporary-generating
@@ -2,7 +2,6 @@
namespace Robo\Common;
use Robo\Robo;
use Robo\Collection\CollectionBuilder;
trait BuilderAwareTrait
@@ -1,7 +1,7 @@
<?php
namespace Robo\Common;
use Symfony\Component\Process\ProcessUtils;
use Robo\Common\ProcessUtils;
/**
* Use this to add arguments and options to the $arguments property.
@@ -46,10 +46,14 @@ trait CommandArguments
* Pass the provided string in its raw (as provided) form as an argument to executable.
*
* @param string $arg
*
* @return $this
*/
public function rawArg($arg)
{
$this->arguments .= " $arg";
return $this;
}
/**
@@ -74,36 +78,51 @@ trait CommandArguments
*
* @param string $option
* @param string $value
* @param string $separator
*
* @return $this
*/
public function option($option, $value = null)
public function option($option, $value = null, $separator = ' ')
{
if ($option !== null and strpos($option, '-') !== 0) {
$option = "--$option";
}
$this->arguments .= null == $option ? '' : " " . $option;
$this->arguments .= null == $value ? '' : " " . static::escape($value);
$this->arguments .= null == $value ? '' : $separator . static::escape($value);
return $this;
}
/**
* Pass multiple options to executable. Value can be a string or array.
* Pass multiple options to executable. The associative array contains
* the key:value pairs that become `--key value`, for each item in the array.
* Values are automatically escaped.
*/
public function options(array $options, $separator = ' ')
{
foreach ($options as $option => $value) {
$this->option($option, $value, $separator);
}
return $this;
}
/**
* Pass an option with multiple values to executable. Value can be a string or array.
* Option values are automatically escaped.
*
* @param string $option
* @param string|array $value
* @param string $separator
*
* @return $this
*/
public function optionList($option, $value = array())
public function optionList($option, $value = array(), $separator = ' ')
{
if (is_array($value)) {
foreach ($value as $item) {
$this->optionList($option, $item);
$this->optionList($option, $item, $separator);
}
} else {
$this->option($option, $value);
$this->option($option, $value, $separator);
}
return $this;
@@ -3,25 +3,25 @@
namespace Robo\Common;
use Robo\Robo;
use Robo\Config;
use Consolidation\Config\ConfigInterface;
trait ConfigAwareTrait
{
/**
* @var \Robo\Config
* @var ConfigInterface
*/
protected $config;
/**
* Set the config management object.
*
* @param \Robo\Config $config
* @param ConfigInterface $config
*
* @return $this
*
* @see \Robo\Contract\ConfigAwareInterface::setConfig()
*/
public function setConfig(Config $config)
public function setConfig(ConfigInterface $config)
{
$this->config = $config;
@@ -31,7 +31,7 @@ trait ConfigAwareTrait
/**
* Get the config management object.
*
* @return \Robo\Config
* @return ConfigInterface
*
* @see \Robo\Contract\ConfigAwareInterface::getConfig()
*/
@@ -40,6 +40,32 @@ trait ConfigAwareTrait
return $this->config;
}
/**
* Any class that uses ConfigAwareTrait SHOULD override this method
* , and define a prefix for its configuration items. This is usually
* done in a base class. When used, this method should return a string
* that ends with a "."; see BaseTask::configPrefix().
*
* @return string
*/
protected static function configPrefix()
{
return '';
}
protected static function configClassIdentifier($classname)
{
$configIdentifier = strtr($classname, '\\', '.');
$configIdentifier = preg_replace('#^(.*\.Task\.|\.)#', '', $configIdentifier);
return $configIdentifier;
}
protected static function configPostfix()
{
return '';
}
/**
* @param string $key
*
@@ -47,18 +73,24 @@ trait ConfigAwareTrait
*/
private static function getClassKey($key)
{
return sprintf('%s.%s', get_called_class(), $key);
$configPrefix = static::configPrefix(); // task.
$configClass = static::configClassIdentifier(get_called_class()); // PARTIAL_NAMESPACE.CLASSNAME
$configPostFix = static::configPostfix(); // .settings
return sprintf('%s%s%s.%s', $configPrefix, $configClass, $configPostFix, $key);
}
/**
* @param string $key
* @param mixed $value
*
* @deprecated
* @param Config|null $config
*/
public static function configure($key, $value)
public static function configure($key, $value, $config = null)
{
Robo::config()->set(static::getClassKey($key), $value);
if (!$config) {
$config = Robo::config();
}
$config->setDefault(static::getClassKey($key), $value);
}
/**
@@ -11,15 +11,7 @@ use Symfony\Component\Process\Process;
*/
trait ExecCommand
{
/**
* @var bool
*/
protected $isPrinted = true;
/**
* @var string
*/
protected $workingDirectory;
use ExecTrait;
/**
* @var \Robo\Common\TimeKeeper
@@ -37,45 +29,6 @@ trait ExecCommand
return $this->execTimer;
}
/**
* Is command printing its output to screen
*
* @return bool
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* Changes working directory of command
*
* @param string $dir
*
* @return $this
*/
public function dir($dir)
{
$this->workingDirectory = $dir;
return $this;
}
/**
* Should command output be printed
*
* @param bool $arg
*
* @return $this
*/
public function printed($arg)
{
if (is_bool($arg)) {
$this->isPrinted = $arg;
}
return $this;
}
/**
* Look for a "{$cmd}.phar" in the current working
* directory; return a string to exec it if it is
@@ -135,7 +88,8 @@ trait ExecCommand
*/
protected function findProjectBin()
{
$candidates = [ __DIR__ . '/../../vendor/bin', __DIR__ . '/../../bin' ];
$cwd = getcwd();
$candidates = [ __DIR__ . '/../../vendor/bin', __DIR__ . '/../../bin', $cwd . '/vendor/bin' ];
// If this project is inside a vendor directory, give highest priority
// to that directory.
@@ -170,6 +124,11 @@ trait ExecCommand
return $cmd;
}
protected function getCommandDescription()
{
return $this->process->getCommandLine();
}
/**
* @param string $command
*
@@ -177,21 +136,13 @@ trait ExecCommand
*/
protected function executeCommand($command)
{
$process = new Process($command);
$process->setTimeout(null);
if ($this->workingDirectory) {
$process->setWorkingDirectory($this->workingDirectory);
}
$this->getExecTimer()->start();
if ($this->isPrinted) {
$process->run(function ($type, $buffer) {
print $buffer;
});
} else {
$process->run();
}
$this->getExecTimer()->stop();
return new Result($this, $process->getExitCode(), $process->getOutput(), ['time' => $this->getExecTimer()->elapsed()]);
// TODO: Symfony 4 requires that we supply the working directory.
$result_data = $this->execute(new Process($command, getcwd()));
return new Result(
$this,
$result_data->getExitCode(),
$result_data->getMessage(),
$result_data->getData()
);
}
}
@@ -0,0 +1,408 @@
<?php
namespace Robo\Common;
use Robo\ResultData;
use Symfony\Component\Process\Process;
/**
* Class ExecTrait
* @package Robo\Common
*/
trait ExecTrait
{
/**
* @var bool
*/
protected $background = false;
/**
* @var null|int
*/
protected $timeout = null;
/**
* @var null|int
*/
protected $idleTimeout = null;
/**
* @var null|array
*/
protected $env = null;
/**
* @var Process
*/
protected $process;
/**
* @var resource|string
*/
protected $input;
/**
* @var boolean
*/
protected $interactive = null;
/**
* @var bool
*/
protected $isPrinted = true;
/**
* @var bool
*/
protected $isMetadataPrinted = true;
/**
* @var string
*/
protected $workingDirectory;
/**
* @return string
*/
abstract public function getCommandDescription();
/** Typically provided by Timer trait via ProgressIndicatorAwareTrait. */
abstract public function startTimer();
abstract public function stopTimer();
abstract public function getExecutionTime();
/**
* Typically provided by TaskIO Trait.
*/
abstract public function hideTaskProgress();
abstract public function showTaskProgress($inProgress);
abstract public function printTaskInfo($text, $context = null);
/**
* Typically provided by VerbosityThresholdTrait.
*/
abstract public function verbosityMeetsThreshold();
abstract public function writeMessage($message);
/**
* Sets $this->interactive() based on posix_isatty().
*
* @return $this
*/
public function detectInteractive()
{
// If the caller did not explicity set the 'interactive' mode,
// and output should be produced by this task (verbosityMeetsThreshold),
// then we will automatically set interactive mode based on whether
// or not output was redirected when robo was executed.
if (!isset($this->interactive) && function_exists('posix_isatty') && $this->verbosityMeetsThreshold()) {
$this->interactive = posix_isatty(STDOUT);
}
return $this;
}
/**
* Executes command in background mode (asynchronously)
*
* @return $this
*/
public function background($arg = true)
{
$this->background = $arg;
return $this;
}
/**
* Stop command if it runs longer then $timeout in seconds
*
* @param int $timeout
*
* @return $this
*/
public function timeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Stops command if it does not output something for a while
*
* @param int $timeout
*
* @return $this
*/
public function idleTimeout($timeout)
{
$this->idleTimeout = $timeout;
return $this;
}
/**
* Set a single environment variable, or multiple.
*/
public function env($env, $value = null)
{
if (!is_array($env)) {
$env = [$env => ($value ? $value : true)];
}
return $this->envVars($env);
}
/**
* Sets the environment variables for the command
*
* @param array $env
*
* @return $this
*/
public function envVars(array $env)
{
$this->env = $env;
return $this;
}
/**
* Pass an input to the process. Can be resource created with fopen() or string
*
* @param resource|string $input
*
* @return $this
*/
public function setInput($input)
{
$this->input = $input;
return $this;
}
/**
* Attach tty to process for interactive input
*
* @param $interactive bool
*
* @return $this
*/
public function interactive($interactive = true)
{
$this->interactive = $interactive;
return $this;
}
/**
* Is command printing its output to screen
*
* @return bool
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* Changes working directory of command
*
* @param string $dir
*
* @return $this
*/
public function dir($dir)
{
$this->workingDirectory = $dir;
return $this;
}
/**
* Shortcut for setting isPrinted() and isMetadataPrinted() to false.
*
* @param bool $arg
*
* @return $this
*/
public function silent($arg)
{
if (is_bool($arg)) {
$this->isPrinted = !$arg;
$this->isMetadataPrinted = !$arg;
}
return $this;
}
/**
* Should command output be printed
*
* @param bool $arg
*
* @return $this
*
* @deprecated
*/
public function printed($arg)
{
$this->logger->warning("printed() is deprecated. Please use printOutput().");
return $this->printOutput($arg);
}
/**
* Should command output be printed
*
* @param bool $arg
*
* @return $this
*/
public function printOutput($arg)
{
if (is_bool($arg)) {
$this->isPrinted = $arg;
}
return $this;
}
/**
* Should command metadata be printed. I,e., command and timer.
*
* @param bool $arg
*
* @return $this
*/
public function printMetadata($arg)
{
if (is_bool($arg)) {
$this->isMetadataPrinted = $arg;
}
return $this;
}
/**
* @param Process $process
* @param callable $output_callback
*
* @return \Robo\ResultData
*/
protected function execute($process, $output_callback = null)
{
$this->process = $process;
if (!$output_callback) {
$output_callback = function ($type, $buffer) {
$progressWasVisible = $this->hideTaskProgress();
$this->writeMessage($buffer);
$this->showTaskProgress($progressWasVisible);
};
}
$this->detectInteractive();
if ($this->isMetadataPrinted) {
$this->printAction();
}
$this->process->setTimeout($this->timeout);
$this->process->setIdleTimeout($this->idleTimeout);
if ($this->workingDirectory) {
$this->process->setWorkingDirectory($this->workingDirectory);
}
if ($this->input) {
$this->process->setInput($this->input);
}
if ($this->interactive && $this->isPrinted) {
$this->process->setTty(true);
}
if (isset($this->env)) {
$this->process->setEnv($this->env);
}
if (!$this->background && !$this->isPrinted) {
$this->startTimer();
$this->process->run();
$this->stopTimer();
$output = rtrim($this->process->getOutput());
return new ResultData(
$this->process->getExitCode(),
$output,
$this->getResultData()
);
}
if (!$this->background && $this->isPrinted) {
$this->startTimer();
$this->process->run($output_callback);
$this->stopTimer();
return new ResultData(
$this->process->getExitCode(),
$this->process->getOutput(),
$this->getResultData()
);
}
try {
$this->process->start();
} catch (\Exception $e) {
return new ResultData(
$this->process->getExitCode(),
$e->getMessage(),
$this->getResultData()
);
}
return new ResultData($this->process->getExitCode());
}
/**
*
*/
protected function stop()
{
if ($this->background && isset($this->process) && $this->process->isRunning()) {
$this->process->stop();
$this->printTaskInfo(
"Stopped {command}",
['command' => $this->getCommandDescription()]
);
}
}
/**
* @param array $context
*/
protected function printAction($context = [])
{
$command = $this->getCommandDescription();
$formatted_command = $this->formatCommandDisplay($command);
$dir = $this->workingDirectory ? " in {dir}" : "";
$this->printTaskInfo("Running {command}$dir", [
'command' => $formatted_command,
'dir' => $this->workingDirectory
] + $context);
}
/**
* @param $command
*
* @return mixed
*/
protected function formatCommandDisplay($command)
{
$formatted_command = str_replace("&&", "&&\n", $command);
$formatted_command = str_replace("||", "||\n", $formatted_command);
return $formatted_command;
}
/**
* Gets the data array to be passed to Result().
*
* @return array
* The data array passed to Result().
*/
protected function getResultData()
{
if ($this->isMetadataPrinted) {
return ['time' => $this->getExecutionTime()];
}
return [];
}
}
+4 -9
View File
@@ -1,12 +1,7 @@
<?php
namespace Robo\Common;
use Robo\Robo;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -76,7 +71,7 @@ trait IO
* @param int $length
* @param string $format
*/
private function formattedOutput($text, $length, $format)
protected function formattedOutput($text, $length, $format)
{
$lines = explode("\n", trim($text, "\n"));
$maxLineLength = array_reduce(array_map('strlen', $lines), 'max');
@@ -143,7 +138,7 @@ trait IO
*
* @return string
*/
private function doAsk(Question $question)
protected function doAsk(Question $question)
{
return $this->getDialog()->ask($this->input(), $this->output(), $question);
}
@@ -153,7 +148,7 @@ trait IO
*
* @return string
*/
private function formatQuestion($message)
protected function formatQuestion($message)
{
return "<question>? $message</question> ";
}
@@ -169,7 +164,7 @@ trait IO
/**
* @param $text
*/
private function writeln($text)
protected function writeln($text)
{
$this->output()->writeln($text);
}
@@ -0,0 +1,38 @@
<?php
namespace Robo\Common;
use Robo\Contract\OutputAdapterInterface;
use Robo\Contract\OutputAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Adapt OutputInterface or other output function to the VerbosityThresholdInterface.
*/
class OutputAdapter implements OutputAdapterInterface, OutputAwareInterface
{
use OutputAwareTrait;
protected $verbosityMap = [
VerbosityThresholdInterface::VERBOSITY_NORMAL => OutputInterface::VERBOSITY_NORMAL,
VerbosityThresholdInterface::VERBOSITY_VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
VerbosityThresholdInterface::VERBOSITY_DEBUG => OutputInterface::VERBOSITY_DEBUG,
];
public function verbosityMeetsThreshold($verbosityThreshold)
{
if (!isset($this->verbosityMap[$verbosityThreshold])) {
return true;
}
$verbosityThreshold = $this->verbosityMap[$verbosityThreshold];
$verbosity = $this->output()->getVerbosity();
return $verbosity >= $verbosityThreshold;
}
public function writeMessage($message)
{
$this->output()->write($message);
}
}
@@ -0,0 +1,51 @@
<?php
namespace Robo\Common;
use Psr\Log\LoggerAwareInterface;
use Robo\Contract\ConfigAwareInterface;
use Robo\Contract\OutputAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Symfony\Component\Process\Process;
class ProcessExecutor implements ConfigAwareInterface, LoggerAwareInterface, OutputAwareInterface, VerbosityThresholdInterface
{
use ExecTrait;
use TaskIO; // uses LoggerAwareTrait and ConfigAwareTrait
use ProgressIndicatorAwareTrait;
use OutputAwareTrait;
/**
* @param Process $process
* @return type
*/
public function __construct(Process $process)
{
$this->process = $process;
}
public static function create($container, $process)
{
$processExecutor = new self($process);
$processExecutor->setLogger($container->get('logger'));
$processExecutor->setProgressIndicator($container->get('progressIndicator'));
$processExecutor->setConfig($container->get('config'));
$processExecutor->setOutputAdapter($container->get('outputAdapter'));
return $processExecutor;
}
/**
* @return string
*/
protected function getCommandDescription()
{
return $this->process->getCommandLine();
}
public function run()
{
return $this->execute($this->process);
}
}
@@ -0,0 +1,79 @@
<?php
/*
* This file is derived from part of the Symfony package, which is
* (c) Fabien Potencier <fabien@symfony.com>
*/
namespace Robo\Common;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* ProcessUtils is a bunch of utility methods. We want to allow Robo 1.x
* to work with Symfony 4.x while remaining backwards compatibility. This
* requires us to replace some deprecated functionality removed in Symfony.
*/
class ProcessUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Escapes a string to be used as a shell argument.
*
* @param string $argument The argument that will be escaped
*
* @return string The escaped argument
*
* @deprecated since version 3.3, to be removed in 4.0. Use a command line array or give env vars to the `Process::start/run()` method instead.
*/
public static function escapeArgument($argument)
{
@trigger_error('The '.__METHOD__.'() method is a copy of a method that was deprecated by Symfony 3.3 and removed in Symfony 4; it will be removed in Robo 2.0.', E_USER_DEPRECATED);
//Fix for PHP bug #43784 escapeshellarg removes % from given string
//Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
//@see https://bugs.php.net/bug.php?id=43784
//@see https://bugs.php.net/bug.php?id=49446
if ('\\' === DIRECTORY_SEPARATOR) {
if ('' === $argument) {
return escapeshellarg($argument);
}
$escapedArgument = '';
$quote = false;
foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
if ('"' === $part) {
$escapedArgument .= '\\"';
} elseif (self::isSurroundedBy($part, '%')) {
// Avoid environment variable expansion
$escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
} else {
// escape trailing backslash
if ('\\' === substr($part, -1)) {
$part .= '\\';
}
$quote = true;
$escapedArgument .= $part;
}
}
if ($quote) {
$escapedArgument = '"'.$escapedArgument.'"';
}
return $escapedArgument;
}
return "'".str_replace("'", "'\\''", $argument)."'";
}
private static function isSurroundedBy($arg, $char)
{
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
}
}
@@ -1,6 +1,9 @@
<?php
namespace Robo\Common;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
trait ProgressIndicatorAwareTrait
{
use Timer;
@@ -20,10 +23,14 @@ trait ProgressIndicatorAwareTrait
/**
* @param null|\Robo\Common\ProgressIndicator $progressIndicator
*
* @return ProgressIndicatorAwareInterface
*/
public function setProgressIndicator($progressIndicator)
{
$this->progressIndicator = $progressIndicator;
return $this;
}
/**
@@ -70,6 +77,10 @@ trait ProgressIndicatorAwareTrait
protected function startProgressIndicator()
{
$this->startTimer();
if ($this instanceof VerbosityThresholdInterface
&& !$this->verbosityMeetsThreshold()) {
return;
}
if (!$this->progressIndicator) {
return;
}
@@ -4,9 +4,7 @@ namespace Robo\Common;
use Robo\Robo;
use Robo\TaskInfo;
use Consolidation\Log\ConsoleLogLevel;
use Robo\Common\ConfigAwareTrait;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Robo\Contract\ProgressIndicatorAwareInterface;
@@ -21,6 +19,7 @@ trait TaskIO
{
use LoggerAwareTrait;
use ConfigAwareTrait;
use VerbosityThresholdTrait;
/**
* @return mixed|null|\Psr\Log\LoggerInterface
@@ -137,6 +136,9 @@ trait TaskIO
*/
protected function printTaskOutput($level, $text, $context)
{
if (!$this->verbosityMeetsThreshold()) {
return;
}
$logger = $this->logger();
if (!$logger) {
return;
@@ -0,0 +1,79 @@
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\TaskInfo;
use Robo\Contract\OutputAdapterInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Consolidation\Log\ConsoleLogLevel;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LogLevel;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Task input/output methods. TaskIO is 'used' in BaseTask, so any
* task that extends this class has access to all of the methods here.
* printTaskInfo, printTaskSuccess, and printTaskError are the three
* primary output methods that tasks are encouraged to use. Tasks should
* avoid using the IO trait output methods.
*/
trait VerbosityThresholdTrait
{
/** var OutputAdapterInterface */
protected $outputAdapter;
protected $verbosityThreshold = 0;
/**
* Required verbocity level before any TaskIO output will be produced.
* e.g. OutputInterface::VERBOSITY_VERBOSE
*/
public function setVerbosityThreshold($verbosityThreshold)
{
$this->verbosityThreshold = $verbosityThreshold;
return $this;
}
public function verbosityThreshold()
{
return $this->verbosityThreshold;
}
public function setOutputAdapter(OutputAdapterInterface $outputAdapter)
{
$this->outputAdapter = $outputAdapter;
}
/**
* @return OutputAdapterInterface
*/
public function outputAdapter()
{
return $this->outputAdapter;
}
public function hasOutputAdapter()
{
return isset($this->outputAdapter);
}
public function verbosityMeetsThreshold()
{
if ($this->hasOutputAdapter()) {
return $this->outputAdapter()->verbosityMeetsThreshold($this->verbosityThreshold());
}
return true;
}
/**
* Print a message if the selected verbosity level is over this task's
* verbosity threshhold.
*/
public function writeMessage($message)
{
if (!$this->verbosityMeetsThreshold()) {
return;
}
$this->outputAdapter()->writeMessage($message);
}
}
+4 -117
View File
@@ -1,122 +1,9 @@
<?php
namespace Robo;
class Config
/**
* @deprecated Use \Robo\Config\Config
*/
class Config extends \Robo\Config\Config
{
const PROGRESS_BAR_AUTO_DISPLAY_INTERVAL = 'progress-delay';
const DEFAULT_PROGRESS_DELAY = 2;
const SIMULATE = 'simulate';
const DECORATED = 'decorated';
/**
* @var array
*/
protected $config = [];
/**
* Fet a configuration value
*
* @param string $key Which config item to look up
* @param string|null $defaultOverride Override usual default value with a different default
*
* @return mixed
*/
public function get($key, $defaultOverride = null)
{
if (isset($this->config[$key])) {
return $this->config[$key];
}
return $this->getDefault($key, $defaultOverride);
}
/**
* Set a config value
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function set($key, $value)
{
$this->config[$key] = $value;
return $this;
}
/**
* Return an associative array containing all of the global configuration
* options and their default values.
*
* @return array
*/
public function getGlobalOptionDefaultValues()
{
$globalOptions =
[
self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL => self::DEFAULT_PROGRESS_DELAY,
self::SIMULATE => false,
];
return $globalOptions;
}
/**
* Return the default value for a given configuration item.
*
* @param string $key
* @param mixed $defaultOverride
*
* @return mixed
*/
public function getDefault($key, $defaultOverride = null)
{
$globalOptions = $this->getGlobalOptionDefaultValues();
return isset($globalOptions[$key]) ? $globalOptions[$key] : $defaultOverride;
}
/**
* @return bool
*/
public function isSimulated()
{
return $this->get(self::SIMULATE);
}
/**
* @param bool $simulated
*
* @return $this
*/
public function setSimulated($simulated = true)
{
return $this->set(self::SIMULATE, $simulated);
}
/**
* @return bool
*/
public function isDecorated()
{
return $this->get(self::DECORATED);
}
/**
* @param bool $decorated
*
* @return $this
*/
public function setDecorated($decorated = true)
{
return $this->set(self::DECORATED, $decorated);
}
/**
* @param int $interval
*
* @return $this
*/
public function setProgressBarAutoDisplayInterval($interval)
{
return $this->set(self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval);
}
}
@@ -0,0 +1,130 @@
<?php
namespace Robo\Config;
class Config extends \Consolidation\Config\Config implements GlobalOptionDefaultValuesInterface
{
const PROGRESS_BAR_AUTO_DISPLAY_INTERVAL = 'options.progress-delay';
const DEFAULT_PROGRESS_DELAY = 2;
const SIMULATE = 'options.simulate';
// Read-only configuration properties; changing these has no effect.
const INTERACTIVE = 'options.interactive';
const DECORATED = 'options.decorated';
/**
* Create a new configuration object, and initialize it with
* the provided nested array containing configuration data.
*/
public function __construct(array $data = null)
{
parent::__construct($data);
$this->defaults = $this->getGlobalOptionDefaultValues();
}
/**
* Return an associative array containing all of the global configuration
* options and their default values.
*
* @return array
*/
public function getGlobalOptionDefaultValues()
{
$globalOptions =
[
self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL => self::DEFAULT_PROGRESS_DELAY,
self::SIMULATE => false,
];
return $this->trimPrefixFromGlobalOptions($globalOptions);
}
/**
* Remove the 'options.' prefix from the global options list.
*/
protected function trimPrefixFromGlobalOptions($globalOptions)
{
$result = [];
foreach ($globalOptions as $option => $value) {
$option = str_replace('options.', '', $option);
$result[$option] = $value;
}
return $result;
}
/**
* @deprecated Use $config->get(Config::SIMULATE)
*
* @return bool
*/
public function isSimulated()
{
return $this->get(self::SIMULATE);
}
/**
* @deprecated Use $config->set(Config::SIMULATE, true)
*
* @param bool $simulated
*
* @return $this
*/
public function setSimulated($simulated = true)
{
return $this->set(self::SIMULATE, $simulated);
}
/**
* @deprecated Use $config->get(Config::INTERACTIVE)
*
* @return bool
*/
public function isInteractive()
{
return $this->get(self::INTERACTIVE);
}
/**
* @deprecated Use $config->set(Config::INTERACTIVE, true)
*
* @param bool $interactive
*
* @return $this
*/
public function setInteractive($interactive = true)
{
return $this->set(self::INTERACTIVE, $interactive);
}
/**
* @deprecated Use $config->get(Config::DECORATED)
*
* @return bool
*/
public function isDecorated()
{
return $this->get(self::DECORATED);
}
/**
* @deprecated Use $config->set(Config::DECORATED, true)
*
* @param bool $decorated
*
* @return $this
*/
public function setDecorated($decorated = true)
{
return $this->set(self::DECORATED, $decorated);
}
/**
* @deprecated Use $config->set(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval)
*
* @param int $interval
*
* @return $this
*/
public function setProgressBarAutoDisplayInterval($interval)
{
return $this->set(self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval);
}
}
@@ -0,0 +1,17 @@
<?php
namespace Robo\Config;
/**
* @deprecated Use robo.yml instead
*
* robo.yml:
*
* options:
* simulated: false
* progress-delay: 2
*
* etc.
*/
interface GlobalOptionDefaultValuesInterface extends \Consolidation\Config\GlobalOptionDefaultValuesInterface
{
}
@@ -2,23 +2,23 @@
namespace Robo\Contract;
use Robo\Config;
use Consolidation\Config\ConfigInterface;
interface ConfigAwareInterface
{
/**
* Set the config reference
*
* @param \Robo\Config $config
* @param ConfigInterface $config
*
* @return $this
*/
public function setConfig(Config $config);
public function setConfig(ConfigInterface $config);
/**
* Get the config reference
*
* @return \Robo\Config
* @return ConfigInterface
*/
public function getConfig();
}
@@ -0,0 +1,11 @@
<?php
namespace Robo\Contract;
/**
* Adapt OutputInterface or other output function to the VerbosityThresholdInterface.
*/
interface OutputAdapterInterface
{
public function verbosityMeetsThreshold($verbosityThreshold);
public function writeMessage($message);
}
@@ -0,0 +1,24 @@
<?php
namespace Robo\Contract;
use Robo\Contract\OutputAdapterInterface;
/**
* Record and determine whether the current verbosity level exceeds the
* desired threshold level to produce output.
*/
interface VerbosityThresholdInterface
{
const VERBOSITY_NORMAL = 1;
const VERBOSITY_VERBOSE = 2;
const VERBOSITY_VERY_VERBOSE = 3;
const VERBOSITY_DEBUG = 4;
public function setVerbosityThreshold($verbosityThreshold);
public function verbosityThreshold();
public function setOutputAdapter(OutputAdapterInterface $outputAdapter);
public function outputAdapter();
public function hasOutputAdapter();
public function verbosityMeetsThreshold();
public function writeMessage($message);
}
@@ -3,21 +3,63 @@ namespace Robo;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
use Robo\Config\GlobalOptionDefaultValuesInterface;
class GlobalOptionsEventListener implements EventSubscriberInterface, ConfigAwareInterface
{
use ConfigAwareTrait;
/** @var Application */
protected $application;
/** @var string */
protected $prefix;
/**
* GlobalOptionsEventListener listener
*/
public function __construct()
{
$this->prefix = 'options';
}
/**
* Add a reference to the Symfony Console application object.
*/
public function setApplication($application)
{
$this->application = $application;
return $this;
}
/**
* Stipulate the prefix to use for option injection.
* @param string $prefix
*/
public function setGlobalOptionsPrefix($prefix)
{
$this->prefix = $prefix;
return $this;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'setGlobalOptions'];
return [ConsoleEvents::COMMAND => 'handleCommandEvent'];
}
/**
* Run all of our individual operations when a command event is received.
*/
public function handleCommandEvent(ConsoleCommandEvent $event)
{
$this->setGlobalOptions($event);
$this->setConfigurationValues($event);
}
/**
@@ -31,15 +73,74 @@ class GlobalOptionsEventListener implements EventSubscriberInterface, ConfigAwar
{
$config = $this->getConfig();
$input = $event->getInput();
$globalOptions = $config->getGlobalOptionDefaultValues();
$globalOptions = $config->get($this->prefix, []);
if ($config instanceof \Consolidation\Config\GlobalOptionDefaultValuesInterface) {
$globalOptions += $config->getGlobalOptionDefaultValues();
}
$globalOptions += $this->applicationOptionDefaultValues();
// Set any config value that has a defined global option (e.g. --simulate)
foreach ($globalOptions as $option => $default) {
$value = $input->hasOption($option) ? $input->getOption($option) : null;
// Unfortunately, the `?:` operator does not differentate between `0` and `null`
if (!isset($value)) {
$value = $default;
}
$config->set($option, $value);
$config->set($this->prefix . '.' . $option, $value);
}
}
/**
* Examine the commandline --define / -D options, and apply the provided
* values to the active configuration.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function setConfigurationValues(ConsoleCommandEvent $event)
{
$config = $this->getConfig();
$input = $event->getInput();
// Also set any `-D config.key=value` options from the commandline.
if ($input->hasOption('define')) {
$configDefinitions = $input->getOption('define');
foreach ($configDefinitions as $value) {
list($key, $value) = $this->splitConfigKeyValue($value);
$config->set($key, $value);
}
}
}
/**
* Split up the key=value config setting into its component parts. If
* the input string contains no '=' character, then the value will be 'true'.
*
* @param string $value
* @return array
*/
protected function splitConfigKeyValue($value)
{
$parts = explode('=', $value, 2);
$parts[] = true;
return $parts;
}
/**
* Get default option values from the Symfony Console application, if
* it is available.
*/
protected function applicationOptionDefaultValues()
{
if (!$this->application) {
return [];
}
$result = [];
foreach ($this->application->getDefinition()->getOptions() as $key => $option) {
$result[$key] = $option->acceptValue() ? $option->getDefault() : null;
}
return $result;
}
}
@@ -2,13 +2,12 @@
namespace Robo\Log;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Contract\PrintedInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Consolidation\Log\ConsoleLogLevel;
@@ -32,6 +31,10 @@ class ResultPrinter implements LoggerAwareInterface, ProgressIndicatorAwareInter
*/
public function printResult(Result $result)
{
$task = $result->getTask();
if ($task instanceof VerbosityThresholdInterface && !$task->verbosityMeetsThreshold()) {
return;
}
if (!$result->wasSuccessful()) {
return $this->printError($result);
} else {
@@ -3,8 +3,6 @@ namespace Robo\Log;
use Robo\Common\TimeKeeper;
use Consolidation\Log\LogOutputStyler;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
/**
* Robo Log Styler.
@@ -1,17 +1,10 @@
<?php
namespace Robo\Log;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Contract\PrintedInterface;
use Robo\Contract\LogResultInterface;
use Consolidation\Log\ConsoleLogLevel;
use Consolidation\Log\Logger;
use Psr\Log\LogLevel;
use Psr\Log\AbstractLogger;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
/**
* Robo's default logger
+26 -2
View File
@@ -2,8 +2,8 @@
namespace Robo;
use Robo\Contract\TaskInterface;
use Robo\Contract\LogResultInterface;
use Robo\Exception\TaskExitException;
use Robo\State\Data;
class Result extends ResultData
{
@@ -34,6 +34,30 @@ class Result extends ResultData
}
}
/**
* Tasks should always return a Result. However, they are also
* allowed to return NULL or an array to indicate success.
*/
public static function ensureResult($task, $result)
{
if ($result instanceof Result) {
return $result;
}
if (!isset($result)) {
return static::success($task);
}
if ($result instanceof Data) {
return static::success($task, $result->getMessage(), $result->getData());
}
if ($result instanceof ResultData) {
return new Result($task, $result->getExitCode(), $result->getMessage(), $result->getData());
}
if (is_array($result)) {
return static::success($task, '', $result);
}
throw new \Exception(sprintf('Task %s returned a %s instead of a \Robo\Result.', get_class($task), get_class($result)));
}
protected function printResult()
{
// For historic reasons, the Result constructor is responsible
@@ -45,7 +69,7 @@ class Result extends ResultData
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
if ($resultPrinter->printResult($this)) {
$this->data['already-printed'] = true;
$this->alreadyPrinted();
}
}
}
+15 -57
View File
@@ -1,22 +1,17 @@
<?php
namespace Robo;
use Robo\Contract\LogResultInterface;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\OutputDataInterface;
use Robo\State\Data;
class ResultData extends \ArrayObject implements ExitCodeInterface, OutputDataInterface
class ResultData extends Data implements ExitCodeInterface, OutputDataInterface
{
/**
* @var int
*/
protected $exitCode;
/**
* @var string
*/
protected $message;
const EXITCODE_OK = 0;
const EXITCODE_ERROR = 1;
/** Symfony Console handles these conditions; Robo returns the status
@@ -38,9 +33,7 @@ class ResultData extends \ArrayObject implements ExitCodeInterface, OutputDataIn
public function __construct($exitCode = self::EXITCODE_OK, $message = '', $data = [])
{
$this->exitCode = $exitCode;
$this->message = $message;
parent::__construct($data);
parent::__construct($message, $data);
}
/**
@@ -65,14 +58,6 @@ class ResultData extends \ArrayObject implements ExitCodeInterface, OutputDataIn
return new ResultData(self::EXITCODE_USER_CANCEL, $message, $data);
}
/**
* @return array
*/
public function getData()
{
return $this->getArrayCopy();
}
/**
* @return int
*/
@@ -86,17 +71,25 @@ class ResultData extends \ArrayObject implements ExitCodeInterface, OutputDataIn
*/
public function getOutputData()
{
if (!empty($this->message) && !isset($this['already-printed'])) {
if (!empty($this->message) && !isset($this['already-printed']) && isset($this['provide-outputdata'])) {
return $this->message;
}
}
/**
* @return string
* Indicate that the message in this data has already been displayed.
*/
public function getMessage()
public function alreadyPrinted()
{
return $this->message;
$this['already-printed'] = true;
}
/**
* Opt-in to providing the result message as the output data
*/
public function provideOutputdata()
{
$this['provide-outputdata'] = true;
}
/**
@@ -114,39 +107,4 @@ class ResultData extends \ArrayObject implements ExitCodeInterface, OutputDataIn
{
return $this->exitCode == self::EXITCODE_USER_CANCEL;
}
/**
* Merge another result into this result. Data already
* existing in this result takes precedence over the
* data in the Result being merged.
*
* @param \Robo\ResultData $result
*
* @return $this
*/
public function merge(ResultData $result)
{
$mergedData = $this->getArrayCopy() + $result->getArrayCopy();
$this->exchangeArray($mergedData);
return $this;
}
/**
* @return bool
*/
public function hasExecutionTime()
{
return isset($this['time']);
}
/**
* @return null|float
*/
public function getExecutionTime()
{
if (!$this->hasExecutionTime()) {
return null;
}
return $this['time'];
}
}
+70 -13
View File
@@ -3,8 +3,13 @@ namespace Robo;
use League\Container\Container;
use League\Container\ContainerInterface;
use Robo\Common\ProcessExecutor;
use Consolidation\Config\ConfigInterface;
use Consolidation\Config\Loader\ConfigProcessor;
use Consolidation\Config\Loader\YamlConfigLoader;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Process\Process;
/**
* Manages the container reference and other static data. Favor
@@ -14,7 +19,7 @@ use Symfony\Component\Console\Application as SymfonyApplication;
class Robo
{
const APPLICATION_NAME = 'Robo';
const VERSION = '1.0.4';
const VERSION = '1.2.1';
/**
* The currently active container object, or NULL if not initialized yet.
@@ -34,9 +39,10 @@ class Robo
*
* @return int
*/
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null)
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null, $repository = null)
{
$runner = new \Robo\Runner($commandClasses);
$runner->setSelfUpdateRepository($repository);
$statusCode = $runner->execute($argv, $appName, $appVersion, $output);
return $statusCode;
}
@@ -85,6 +91,33 @@ class Robo
return static::$container !== null;
}
/**
* Create a config object and load it from the provided paths.
*/
public static function createConfiguration($paths)
{
$config = new \Robo\Config\Config();
static::loadConfiguration($paths, $config);
return $config;
}
/**
* Use a simple config loader to load configuration values from specified paths
*/
public static function loadConfiguration($paths, $config = null)
{
if ($config == null) {
$config = static::config();
}
$loader = new YamlConfigLoader();
$processor = new ConfigProcessor();
$processor->add($config->export());
foreach ($paths as $path) {
$processor->extend($loader->load($path));
}
$config->import($processor->export());
}
/**
* Create a container and initiailze it. If you wish to *change*
* anything defined in the container, then you should call
@@ -93,7 +126,7 @@ class Robo
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Robo\Application $app
* @param null|\Robo\Config $config
* @param null|ConfigInterface $config
*
* @return \League\Container\Container|\League\Container\ContainerInterface
*/
@@ -109,7 +142,7 @@ class Robo
}
if (!$config) {
$config = new Config();
$config = new \Robo\Config\Config();
}
// Set up our dependency injection container.
@@ -139,11 +172,11 @@ class Robo
*
* @param \League\Container\ContainerInterface $container
* @param \Symfony\Component\Console\Application $app
* @param \Robo\Config $config
* @param ConfigInterface $config
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*/
public static function configureContainer(ContainerInterface $container, SymfonyApplication $app, Config $config, $input = null, $output = null)
public static function configureContainer(ContainerInterface $container, SymfonyApplication $app, ConfigInterface $config, $input = null, $output = null)
{
// Self-referential container refernce for the inflector
$container->add('container', $container);
@@ -156,12 +189,14 @@ class Robo
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
$config->setDecorated($output->isDecorated());
$config->set(Config::DECORATED, $output->isDecorated());
$config->set(Config::INTERACTIVE, $input->isInteractive());
$container->share('application', $app);
$container->share('config', $config);
$container->share('input', $input);
$container->share('output', $output);
$container->share('outputAdapter', \Robo\Common\OutputAdapter::class);
// Register logging and related services.
$container->share('logStyler', \Robo\Log\RoboLogStyle::class);
@@ -175,22 +210,30 @@ class Robo
->withArgument('output');
$container->share('resultPrinter', \Robo\Log\ResultPrinter::class);
$container->add('simulator', \Robo\Task\Simulator::class);
$container->share('globalOptionsEventListener', \Robo\GlobalOptionsEventListener::class);
$container->share('globalOptionsEventListener', \Robo\GlobalOptionsEventListener::class)
->withMethodCall('setApplication', ['application']);
$container->share('injectConfigEventListener', \Consolidation\Config\Inject\ConfigForCommand::class)
->withArgument('config')
->withMethodCall('setApplication', ['application']);
$container->share('collectionProcessHook', \Robo\Collection\CollectionProcessHook::class);
$container->share('hookManager', \Consolidation\AnnotatedCommand\Hooks\HookManager::class)
->withMethodCall('addResultProcessor', ['collectionProcessHook', '*']);
$container->share('alterOptionsCommandEvent', \Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent::class)
->withArgument('application');
$container->share('hookManager', \Consolidation\AnnotatedCommand\Hooks\HookManager::class)
->withMethodCall('addCommandEvent', ['alterOptionsCommandEvent'])
->withMethodCall('addCommandEvent', ['injectConfigEventListener'])
->withMethodCall('addCommandEvent', ['globalOptionsEventListener'])
->withMethodCall('addResultProcessor', ['collectionProcessHook', '*']);
$container->share('eventDispatcher', \Symfony\Component\EventDispatcher\EventDispatcher::class)
->withMethodCall('addSubscriber', ['globalOptionsEventListener'])
->withMethodCall('addSubscriber', ['alterOptionsCommandEvent'])
->withMethodCall('addSubscriber', ['hookManager']);
$container->share('formatterManager', \Consolidation\OutputFormatters\FormatterManager::class)
->withMethodCall('addDefaultFormatters', [])
->withMethodCall('addDefaultSimplifiers', []);
$container->share('prepareTerminalWidthOption', \Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption::class)
->withMethodCall('setApplication', ['application']);
$container->share('commandProcessor', \Consolidation\AnnotatedCommand\CommandProcessor::class)
->withArgument('hookManager')
->withMethodCall('setFormatterManager', ['formatterManager'])
->withMethodCall('addPrepareFormatter', ['prepareTerminalWidthOption'])
->withMethodCall(
'setDisplayErrorFunction',
[
@@ -202,8 +245,13 @@ class Robo
);
$container->share('commandFactory', \Consolidation\AnnotatedCommand\AnnotatedCommandFactory::class)
->withMethodCall('setCommandProcessor', ['commandProcessor']);
// Deprecated: favor using collection builders to direct use of collections.
$container->add('collection', \Robo\Collection\Collection::class);
// Deprecated: use CollectionBuilder::create() instead -- or, better
// yet, BuilderAwareInterface::collectionBuilder() if available.
$container->add('collectionBuilder', \Robo\Collection\CollectionBuilder::class);
static::addInflectors($container);
// Make sure the application is appropriately initialized.
@@ -246,6 +294,10 @@ class Robo
->invokeMethod('setOutput', ['output']);
$container->inflector(\Robo\Contract\ProgressIndicatorAwareInterface::class)
->invokeMethod('setProgressIndicator', ['progressIndicator']);
$container->inflector(\Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface::class)
->invokeMethod('setHookManager', ['hookManager']);
$container->inflector(\Robo\Contract\VerbosityThresholdInterface::class)
->invokeMethod('setOutputAdapter', ['outputAdapter']);
}
/**
@@ -292,7 +344,7 @@ class Robo
}
/**
* @return \Robo\Config
* @return ConfigInterface
*/
public static function config()
{
@@ -334,4 +386,9 @@ class Robo
{
return static::service('input');
}
public static function process(Process $process)
{
return ProcessExecutor::create(static::getContainer(), $process);
}
}
+59 -13
View File
@@ -1,15 +1,12 @@
<?php
namespace Robo;
use League\Container\Container;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\StringInput;
use Consolidation\AnnotatedCommand\PassThroughArgsInput;
use Robo\Contract\BuilderAwareInterface;
use Robo\Collection\CollectionBuilder;
use Robo\Common\IO;
use Robo\Exception\TaskExitException;
use League\Container\ContainerInterface;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
@@ -36,6 +33,16 @@ class Runner implements ContainerAwareInterface
*/
protected $dir;
/**
* @var string[]
*/
protected $errorConditions = [];
/**
* @var string GitHub Repo for SelfUpdate
*/
protected $selfUpdateRepository = null;
/**
* Class Constructor
*
@@ -50,6 +57,11 @@ class Runner implements ContainerAwareInterface
$this->dir = getcwd();
}
protected function errorCondtion($msg, $errorType)
{
$this->errorConditions[$msg] = $errorType;
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
@@ -70,7 +82,7 @@ class Runner implements ContainerAwareInterface
return true;
}
if (!file_exists($this->dir)) {
$output->writeln("<error>Path `{$this->dir}` is invalid; please provide a valid absolute path to the Robofile to load.</error>");
$this->errorCondtion("Path `{$this->dir}` is invalid; please provide a valid absolute path to the Robofile to load.", 'red');
return false;
}
@@ -79,13 +91,13 @@ class Runner implements ContainerAwareInterface
$roboFilePath = $realDir . DIRECTORY_SEPARATOR . $this->roboFile;
if (!file_exists($roboFilePath)) {
$requestedRoboFilePath = $this->dir . DIRECTORY_SEPARATOR . $this->roboFile;
$output->writeln("<error>Requested RoboFile `$requestedRoboFilePath` is invalid, please provide valid absolute path to load Robofile</error>");
$this->errorCondtion("Requested RoboFile `$requestedRoboFilePath` is invalid, please provide valid absolute path to load Robofile.", 'red');
return false;
}
require_once $roboFilePath;
if (!class_exists($this->roboClass)) {
$output->writeln("<error>Class ".$this->roboClass." was not loaded</error>");
$this->errorCondtion("Class {$this->roboClass} was not loaded.", 'red');
return false;
}
return true;
@@ -136,7 +148,10 @@ class Runner implements ContainerAwareInterface
// If we were not provided a container, then create one
if (!$this->getContainer()) {
$container = Robo::createDefaultContainer($input, $output, $app);
$userConfig = 'robo.yml';
$roboAppConfig = dirname(__DIR__) . '/robo.yml';
$config = Robo::createConfiguration([$userConfig, $roboAppConfig]);
$container = Robo::createDefaultContainer($input, $output, $app, $config);
$this->setContainer($container);
// Automatically register a shutdown function and
// an error handler when we provide the container.
@@ -146,10 +161,13 @@ class Runner implements ContainerAwareInterface
if (!$app) {
$app = Robo::application();
}
if (!isset($commandFiles)) {
$this->yell("Robo is not initialized here. Please run `robo init` to create a new RoboFile", 40, 'yellow');
$app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
$commandFiles = [];
if ($app instanceof \Robo\Application) {
$app->addSelfUpdateCommand($this->getSelfUpdateRepository());
if (!isset($commandFiles)) {
$this->errorCondtion("Robo is not initialized here. Please run `robo init` to create a new RoboFile.", 'yellow');
$app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
$commandFiles = [];
}
}
$this->registerCommandClasses($app, $commandFiles);
@@ -158,6 +176,15 @@ class Runner implements ContainerAwareInterface
} catch (TaskExitException $e) {
$statusCode = $e->getCode() ?: 1;
}
// If there were any error conditions in bootstrapping Robo,
// print them only if the requested command did not complete
// successfully.
if ($statusCode) {
foreach ($this->errorConditions as $msg => $color) {
$this->yell($msg, 40, $color);
}
}
return $statusCode;
}
@@ -222,6 +249,9 @@ class Runner implements ContainerAwareInterface
// If the command class is already an instantiated object, then
// just use it exactly as it was provided to us.
if (is_string($commandClass)) {
if (!class_exists($commandClass)) {
return;
}
$reflectionClass = new \ReflectionClass($commandClass);
if ($reflectionClass->isAbstract()) {
return;
@@ -235,7 +265,7 @@ class Runner implements ContainerAwareInterface
// ensure that it has a builder. Every command class needs
// its own collection builder, as they have references to each other.
if ($commandClass instanceof BuilderAwareInterface) {
$builder = $container->get('collectionBuilder', [$commandClass]);
$builder = CollectionBuilder::create($container, $commandClass);
$commandClass->setBuilder($builder);
}
if ($commandClass instanceof ContainerAwareInterface) {
@@ -416,4 +446,20 @@ class Runner implements ContainerAwareInterface
}
return false;
}
/**
* @return string
*/
public function getSelfUpdateRepository()
{
return $this->selfUpdateRepository;
}
/**
* @param string $selfUpdateRepository
*/
public function setSelfUpdateRepository($selfUpdateRepository)
{
$this->selfUpdateRepository = $selfUpdateRepository;
}
}
@@ -0,0 +1,152 @@
<?php
namespace Robo;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
/**
* Update the robo.phar from the latest github release
*
* @author Alexander Menk <alex.menk@gmail.com>
*/
class SelfUpdateCommand extends Command
{
const SELF_UPDATE_COMMAND_NAME = 'self:update';
protected $gitHubRepository;
protected $currentVersion;
protected $applicationName;
public function __construct($applicationName = null, $currentVersion = null, $gitHubRepository = null)
{
parent::__construct(self::SELF_UPDATE_COMMAND_NAME);
$this->applicationName = $applicationName;
$this->currentVersion = $currentVersion;
$this->gitHubRepository = $gitHubRepository;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setAliases(array('update'))
->setDescription('Updates the robo.phar to the latest version.')
->setHelp(
<<<EOT
The <info>self-update</info> command checks github for newer
versions of robo and if found, installs the latest.
EOT
);
}
protected function getLatestReleaseFromGithub()
{
$opts = [
'http' => [
'method' => 'GET',
'header' => [
'User-Agent: ' . $this->applicationName . ' (' . $this->gitHubRepository . ')' . ' Self-Update (PHP)'
]
]
];
$context = stream_context_create($opts);
$releases = file_get_contents('https://api.github.com/repos/' . $this->gitHubRepository . '/releases', false, $context);
$releases = json_decode($releases);
if (! isset($releases[0])) {
throw new \Exception('API error - no release found at GitHub repository ' . $this->gitHubRepository);
}
$version = $releases[0]->tag_name;
$url = $releases[0]->assets[0]->browser_download_url;
return [ $version, $url ];
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (empty(\Phar::running())) {
throw new \Exception(self::SELF_UPDATE_COMMAND_NAME . ' only works when running the phar version of ' . $this->applicationName . '.');
}
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
$programName = basename($localFilename);
$tempFilename = dirname($localFilename) . '/' . basename($localFilename, '.phar') . '-temp.phar';
// check for permissions in local filesystem before start connection process
if (! is_writable($tempDirectory = dirname($tempFilename))) {
throw new \Exception(
$programName . ' update failed: the "' . $tempDirectory .
'" directory used to download the temp file could not be written'
);
}
if (! is_writable($localFilename)) {
throw new \Exception(
$programName . ' update failed: the "' . $localFilename . '" file could not be written (execute with sudo)'
);
}
list( $latest, $downloadUrl ) = $this->getLatestReleaseFromGithub();
if ($this->currentVersion == $latest) {
$output->writeln('No update available');
return;
}
$fs = new sfFilesystem();
$output->writeln('Downloading ' . $this->applicationName . ' (' . $this->gitHubRepository . ') ' . $latest);
$fs->copy($downloadUrl, $tempFilename);
$output->writeln('Download finished');
try {
\error_reporting(E_ALL); // supress notices
@chmod($tempFilename, 0777 & ~umask());
// test the phar validity
$phar = new \Phar($tempFilename);
// free the variable to unlock the file
unset($phar);
@rename($tempFilename, $localFilename);
$output->writeln('<info>Successfully updated ' . $programName . '</info>');
$this->_exit();
} catch (\Exception $e) {
@unlink($tempFilename);
if (! $e instanceof \UnexpectedValueException && ! $e instanceof \PharException) {
throw $e;
}
$output->writeln('<error>The download is corrupted (' . $e->getMessage() . ').</error>');
$output->writeln('<error>Please re-run the self-update command to try again.</error>');
}
}
/**
* Stop execution
*
* This is a workaround to prevent warning of dispatcher after replacing
* the phar file.
*
* @return void
*/
protected function _exit()
{
exit;
}
}
@@ -0,0 +1,12 @@
<?php
namespace Robo\State;
use Robo\State\Data;
interface Consumer
{
/**
* @return Data
*/
public function receiveState(Data $state);
}
@@ -0,0 +1,148 @@
<?php
namespace Robo\State;
/**
* A State\Data object contains a "message" (the primary result) and a
* data array (the persistent state). The message is transient, and does
* not move into the persistent state unless explicitly copied there.
*/
class Data extends \ArrayObject
{
/**
* @var string
*/
protected $message;
/**
* @param string $message
* @param array $data
*/
public function __construct($message = '', $data = [])
{
$this->message = $message;
parent::__construct($data);
}
/**
* @return array
*/
public function getData()
{
return $this->getArrayCopy();
}
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* @param string message
*/
public function setMessage($message)
{
$this->message = $message;
}
/**
* Merge another result into this result. Data already
* existing in this result takes precedence over the
* data in the Result being merged.
*
* @param \Robo\ResultData $result
*
* @return $this
*/
public function merge(Data $result)
{
$mergedData = $this->getArrayCopy() + $result->getArrayCopy();
$this->exchangeArray($mergedData);
return $this;
}
/**
* Update the current data with the data provided in the parameter.
* Provided data takes precedence.
*
* @param \ArrayObject $update
*
* @return $this
*/
public function update(\ArrayObject $update)
{
$iterator = $update->getIterator();
while ($iterator->valid()) {
$this[$iterator->key()] = $iterator->current();
$iterator->next();
}
return $this;
}
/**
* Merge another result into this result. Data already
* existing in this result takes precedence over the
* data in the Result being merged.
*
* $data['message'] is handled specially, and is appended
* to $this->message if set.
*
* @param array $data
*
* @return array
*/
public function mergeData(array $data)
{
$mergedData = $this->getArrayCopy() + $data;
$this->exchangeArray($mergedData);
return $mergedData;
}
/**
* @return bool
*/
public function hasExecutionTime()
{
return isset($this['time']);
}
/**
* @return null|float
*/
public function getExecutionTime()
{
if (!$this->hasExecutionTime()) {
return null;
}
return $this['time'];
}
/**
* Accumulate execution time
*/
public function accumulateExecutionTime($duration)
{
// Convert data arrays to scalar
if (is_array($duration)) {
$duration = isset($duration['time']) ? $duration['time'] : 0;
}
$this['time'] = $this->getExecutionTime() + $duration;
return $this->getExecutionTime();
}
/**
* Accumulate the message.
*/
public function accumulateMessage($message)
{
if (!empty($this->message)) {
$this->message .= "\n";
}
$this->message .= $message;
return $this->getMessage();
}
}
@@ -0,0 +1,30 @@
<?php
namespace Robo\State;
use Robo\State\Data;
interface StateAwareInterface
{
/**
* @return Data
*/
public function getState();
/**
* @param Data state
*/
public function setState(Data $state);
/**
* @param $key
* @param value
*/
public function setStateValue($key, $value);
/**
* @param Data update state takes precedence over current state.
*/
public function updateState(Data $update);
public function resetState();
}
@@ -0,0 +1,49 @@
<?php
namespace Robo\State;
use Robo\State\Data;
trait StateAwareTrait
{
protected $state;
/**
* {@inheritdoc}
*/
public function getState()
{
return $this->state;
}
/**
* {@inheritdoc}
*/
public function setState(Data $state)
{
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public function setStateValue($key, $value)
{
$this->state[$key] = $value;
}
/**
* {@inheritdoc}
*/
public function updateState(Data $update)
{
$this->state->update($update);
}
/**
* {@inheritdoc}
*/
public function resetState()
{
$this->state = new Data();
}
}
@@ -12,7 +12,8 @@ use Traversable;
* ``` php
* <?php
* // ApiGen Command
* $this->taskApiGen('./apigen.neon')
* $this->taskApiGen('./vendor/apigen/apigen.phar')
* ->config('./apigen.neon')
* ->templateConfig('vendor/apigen/apigen/templates/bootstrap/config.neon')
* ->wipeout(true)
* ->run();
@@ -30,6 +31,7 @@ class ApiGen extends BaseTask implements CommandInterface
* @var string
*/
protected $command;
protected $operation = 'generate';
/**
* @param null|string $pathToApiGen
@@ -39,6 +41,15 @@ class ApiGen extends BaseTask implements CommandInterface
public function __construct($pathToApiGen = null)
{
$this->command = $pathToApiGen;
$command_parts = [];
preg_match('/((?:.+)?apigen(?:\.phar)?) ?( \w+)? ?(.+)?/', $this->command, $command_parts);
if (count($command_parts) === 3) {
list(, $this->command, $this->operation) = $command_parts;
}
if (count($command_parts) === 4) {
list(, $this->command, $this->operation, $arg) = $command_parts;
$this->arg($arg);
}
if (!$this->command) {
$this->command = $this->findExecutablePhar('apigen');
}
@@ -47,6 +58,31 @@ class ApiGen extends BaseTask implements CommandInterface
}
}
/**
* Pass methods parameters as arguments to executable. Argument values
* are automatically escaped.
*
* @param string|string[] $args
*
* @return $this
*/
public function args($args)
{
if (!is_array($args)) {
$args = func_get_args();
}
$args = array_map(function ($arg) {
if (preg_match('/^\w+$/', trim($arg)) === 1) {
$this->operation = $arg;
return null;
}
return $arg;
}, $args);
$args = array_filter($args);
$this->arguments .= ' ' . implode(' ', array_map('static::escape', $args));
return $this;
}
/**
* @param array|Traversable|string $arg a single object or something traversable
*
@@ -468,7 +504,7 @@ class ApiGen extends BaseTask implements CommandInterface
*/
public function getCommand()
{
return $this->command . $this->arguments;
return "$this->command $this->operation$this->arguments";
}
/**
@@ -31,8 +31,6 @@ use Robo\Common\BuilderAwareTrait;
* ->run();
* ?>
* ```
*
* @method to(string) location to store extracted files
*/
class Extract extends BaseTask implements BuilderAwareInterface
{
+30 -129
View File
@@ -1,6 +1,7 @@
<?php
namespace Robo\Task\Base;
use Robo\Common\ExecTrait;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Contract\SimulatedInterface;
@@ -41,31 +42,6 @@ class Exec extends BaseTask implements CommandInterface, PrintedInterface, Simul
*/
protected $command;
/**
* @var bool
*/
protected $background = false;
/**
* @var null|int
*/
protected $timeout = null;
/**
* @var null|int
*/
protected $idleTimeout = null;
/**
* @var null|array
*/
protected $env = null;
/**
* @var Process
*/
protected $process;
/**
* @param string|\Robo\Contract\CommandInterface $command
*/
@@ -75,11 +51,11 @@ class Exec extends BaseTask implements CommandInterface, PrintedInterface, Simul
}
/**
* {@inheritdoc}
*
*/
public function getCommand()
public function __destruct()
{
return trim($this->command . $this->arguments);
$this->stop();
}
/**
@@ -87,116 +63,26 @@ class Exec extends BaseTask implements CommandInterface, PrintedInterface, Simul
*
* @return $this
*/
public function background()
public function background($arg = true)
{
self::$instances[] = $this;
$this->background = true;
$this->background = $arg;
return $this;
}
/**
* Stop command if it runs longer then $timeout in seconds
*
* @param int $timeout
*
* @return $this
*/
public function timeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Stops command if it does not output something for a while
*
* @param int $timeout
*
* @return $this
*/
public function idleTimeout($timeout)
{
$this->idleTimeout = $timeout;
return $this;
}
/**
* Sets the environment variables for the command
*
* @param array $env
*
* @return $this
*/
public function env(array $env)
{
$this->env = $env;
return $this;
}
public function __destruct()
{
$this->stop();
}
protected function stop()
{
if ($this->background && $this->process->isRunning()) {
$this->process->stop();
$this->printTaskInfo("Stopped {command}", ['command' => $this->getCommand()]);
}
}
/**
* @param array $context
*/
protected function printAction($context = [])
{
$command = $this->getCommand();
$dir = $this->workingDirectory ? " in {dir}" : "";
$this->printTaskInfo("Running {command}$dir", ['command' => $command, 'dir' => $this->workingDirectory] + $context);
}
/**
* {@inheritdoc}
*/
public function run()
protected function getCommandDescription()
{
$this->printAction();
$this->process = new Process($this->getCommand());
$this->process->setTimeout($this->timeout);
$this->process->setIdleTimeout($this->idleTimeout);
$this->process->setWorkingDirectory($this->workingDirectory);
if (isset($this->env)) {
$this->process->setEnv($this->env);
}
if (!$this->background and !$this->isPrinted) {
$this->startTimer();
$this->process->run();
$this->stopTimer();
return new Result($this, $this->process->getExitCode(), $this->process->getOutput(), ['time' => $this->getExecutionTime()]);
}
if (!$this->background and $this->isPrinted) {
$this->startTimer();
$this->process->run(
function ($type, $buffer) {
$progressWasVisible = $this->hideTaskProgress();
print($buffer);
$this->showTaskProgress($progressWasVisible);
}
);
$this->stopTimer();
return new Result($this, $this->process->getExitCode(), $this->process->getOutput(), ['time' => $this->getExecutionTime()]);
}
try {
$this->process->start();
} catch (\Exception $e) {
return Result::fromException($this, $e);
}
return Result::success($this);
return $this->getCommand();
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return trim($this->command . $this->arguments);
}
/**
@@ -215,6 +101,21 @@ class Exec extends BaseTask implements CommandInterface, PrintedInterface, Simul
}
}
}
/**
* {@inheritdoc}
*/
public function run()
{
// TODO: Symfony 4 requires that we supply the working directory.
$result_data = $this->execute(new Process($this->getCommand(), getcwd()));
return new Result(
$this,
$result_data->getExitCode(),
$result_data->getMessage(),
$result_data->getData()
);
}
}
if (function_exists('pcntl_signal')) {
@@ -2,7 +2,6 @@
namespace Robo\Task\Base;
use Robo\Task\CommandStack;
use Robo\Task\Base;
/**
* Execute commands one by one in stack.
@@ -18,8 +17,6 @@ use Robo\Task\Base;
*
* ?>
* ```
*
* @method $this stopOnFail()
*/
class ExecStack extends CommandStack
{
@@ -1,8 +1,6 @@
<?php
namespace Robo\Task\Base;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Result;
@@ -22,10 +20,6 @@ use Symfony\Component\Process\Process;
* ->run();
* ?>
* ```
*
*
* @method \Robo\Task\Base\ParallelExec timeout(int $timeout) stops process if it runs longer then `$timeout` (seconds)
* @method \Robo\Task\Base\ParallelExec idleTimeout(int $timeout) stops process if it does not output for time longer then `$timeout` (seconds)
*/
class ParallelExec extends BaseTask implements CommandInterface, PrintedInterface
{
@@ -46,6 +40,11 @@ class ParallelExec extends BaseTask implements CommandInterface, PrintedInterfac
*/
protected $idleTimeout = null;
/**
* @var null|int
*/
protected $waitInterval = 0;
/**
* @var bool
*/
@@ -77,11 +76,14 @@ class ParallelExec extends BaseTask implements CommandInterface, PrintedInterfac
*/
public function process($command)
{
$this->processes[] = new Process($this->receiveCommand($command));
// TODO: Symfony 4 requires that we supply the working directory.
$this->processes[] = new Process($this->receiveCommand($command), getcwd());
return $this;
}
/**
* Stops process if it runs longer then `$timeout` (seconds).
*
* @param int $timeout
*
* @return $this
@@ -93,6 +95,8 @@ class ParallelExec extends BaseTask implements CommandInterface, PrintedInterfac
}
/**
* Stops process if it does not output for time longer then `$timeout` (seconds).
*
* @param int $idleTimeout
*
* @return $this
@@ -103,6 +107,20 @@ class ParallelExec extends BaseTask implements CommandInterface, PrintedInterfac
return $this;
}
/**
* Parallel processing will wait `$waitInterval` seconds after launching each process and before
* the next one.
*
* @param int $waitInterval
*
* @return $this
*/
public function waitInterval($waitInterval)
{
$this->waitInterval = $waitInterval;
return $this;
}
/**
* {@inheritdoc}
*/
@@ -124,16 +142,20 @@ class ParallelExec extends BaseTask implements CommandInterface, PrintedInterfac
*/
public function run()
{
foreach ($this->processes as $process) {
$process->setIdleTimeout($this->idleTimeout);
$process->setTimeout($this->timeout);
$process->start();
$this->printTaskInfo($process->getCommandLine());
}
$this->startProgressIndicator();
$running = $this->processes;
$running = [];
$queue = $this->processes;
$nextTime = time();
while (true) {
if (($nextTime <= time()) && !empty($queue)) {
$process = array_shift($queue);
$process->setIdleTimeout($this->idleTimeout);
$process->setTimeout($this->timeout);
$process->start();
$this->printTaskInfo($process->getCommandLine());
$running[] = $process;
$nextTime = time() + $this->waitInterval;
}
foreach ($running as $k => $process) {
try {
$process->checkTimeout();
@@ -152,7 +174,7 @@ class ParallelExec extends BaseTask implements CommandInterface, PrintedInterfac
unset($running[$k]);
}
}
if (empty($running)) {
if (empty($running) && empty($queue)) {
break;
}
usleep(1000);
@@ -6,7 +6,6 @@ use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
/**
* Executes Symfony Command
@@ -13,6 +13,9 @@ trait loadTasks
return $this->task(Exec::class, $command);
}
/**
* @return ExecStack
*/
protected function taskExecStack()
{
return $this->task(ExecStack::class);
@@ -7,16 +7,38 @@ use Robo\Contract\InflectionInterface;
use Robo\Common\TaskIO;
use Robo\Contract\TaskInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\ConfigAwareInterface;
use Psr\Log\LoggerAwareInterface;
use Robo\Contract\OutputAwareInterface;
abstract class BaseTask implements TaskInterface, LoggerAwareInterface, ConfigAwareInterface, ProgressIndicatorAwareInterface, InflectionInterface
abstract class BaseTask implements TaskInterface, LoggerAwareInterface, VerbosityThresholdInterface, ConfigAwareInterface, ProgressIndicatorAwareInterface, InflectionInterface
{
use TaskIO; // uses LoggerAwareTrait and ConfigAwareTrait
use TaskIO; // uses LoggerAwareTrait, VerbosityThresholdTrait and ConfigAwareTrait
use ProgressIndicatorAwareTrait;
use InflectionTrait;
/**
* ConfigAwareInterface uses this to decide where configuration
* items come from. Default is this prefix + class name + key,
* e.g. `task.Remote.Ssh.remoteDir`.
*/
protected static function configPrefix()
{
return 'task.';
}
/**
* ConfigAwareInterface uses this to decide where configuration
* items come from. Default is this prefix + class name + key,
* e.g. `task.Ssh.remoteDir`.
*/
protected static function configPostfix()
{
return '.settings';
}
/**
* {@inheritdoc}
*/
@@ -31,5 +53,8 @@ abstract class BaseTask implements TaskInterface, LoggerAwareInterface, ConfigAw
if ($child instanceof ConfigAwareInterface && $this->getConfig()) {
$child->setConfig($this->getConfig());
}
if ($child instanceof VerbosityThresholdInterface && $this->outputAdapter()) {
$child->setOutputAdapter($this->outputAdapter());
}
}
}
@@ -1,7 +1,6 @@
<?php
namespace Robo\Task\Bower;
use Robo\Task\Bower;
use Robo\Contract\CommandInterface;
/**
@@ -1,8 +1,6 @@
<?php
namespace Robo\Task\Bower;
use Robo\Task\Bower;
/**
* Bower Update
*
@@ -6,7 +6,6 @@ use Robo\Common\ExecCommand;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Contract\CommandInterface;
use Robo\Common\DynamicParams;
use Robo\Exception\TaskException;
abstract class CommandStack extends BaseTask implements CommandInterface, PrintedInterface
@@ -60,8 +59,8 @@ abstract class CommandStack extends BaseTask implements CommandInterface, Printe
$command = implode(' ', array_filter($command));
}
$command = $this->executable . ' ' . $this->stripExecutableFromCommand($command);
array_push($this->exec, trim($command));
$command = $this->executable . ' ' . $this->stripExecutableFromCommand($command);
$this->exec[] = trim($command);
return $this;
}
@@ -105,19 +104,31 @@ abstract class CommandStack extends BaseTask implements CommandInterface, Printe
if (empty($this->exec)) {
throw new TaskException($this, 'You must add at least one command');
}
if (!$this->stopOnFail) {
// If 'stopOnFail' is not set, or if there is only one command to run,
// then execute the single command to run.
if (!$this->stopOnFail || (count($this->exec) == 1)) {
$this->printTaskInfo('{command}', ['command' => $this->getCommand()]);
return $this->executeCommand($this->getCommand());
}
// When executing multiple commands in 'stopOnFail' mode, run them
// one at a time so that the result will have the exact command
// that failed available to the caller. This is at the expense of
// losing the output from all successful commands.
$data = [];
$message = '';
$result = null;
foreach ($this->exec as $command) {
$this->printTaskInfo("Executing {command}", ['command' => $command]);
$result = $this->executeCommand($command);
$result->accumulateExecutionTime($data);
$message = $result->accumulateMessage($message);
$data = $result->mergeData($data);
if (!$result->wasSuccessful()) {
return $result;
}
}
return Result::success($this);
return $result;
}
}
@@ -1,11 +1,11 @@
<?php
namespace Robo\Task\Composer;
use Robo\Robo;
use Robo\Task\BaseTask;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
abstract class Base extends BaseTask
abstract class Base extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
@@ -14,6 +14,11 @@ abstract class Base extends BaseTask
*/
protected $command = '';
/**
* @var boolena
*/
protected $built = false;
/**
* @var string
*/
@@ -24,11 +29,6 @@ abstract class Base extends BaseTask
*/
protected $dev;
/**
* @var string
*/
protected $optimizeAutoloader;
/**
* @var string
*/
@@ -37,7 +37,7 @@ abstract class Base extends BaseTask
/**
* @var string
*/
protected $dir;
protected $nointeraction;
/**
* Action to use
@@ -46,72 +46,6 @@ abstract class Base extends BaseTask
*/
protected $action = '';
/**
* adds `prefer-dist` option to composer
*
* @return $this
*/
public function preferDist()
{
$this->prefer = '--prefer-dist';
return $this;
}
/**
* adds `prefer-source` option to composer
*
* @return $this
*/
public function preferSource()
{
$this->prefer = '--prefer-source';
return $this;
}
/**
* adds `no-dev` option to composer
*
* @return $this
*/
public function noDev()
{
$this->dev = '--no-dev';
return $this;
}
/**
* adds `no-ansi` option to composer
*
* @return $this
*/
public function noAnsi()
{
$this->ansi = '--no-ansi';
return $this;
}
/**
* adds `ansi` option to composer
*
* @return $this
*/
public function ansi()
{
$this->ansi = '--ansi';
return $this;
}
/**
* adds `optimize-autoloader` option to composer
*
* @return $this
*/
public function optimizeAutoloader()
{
$this->optimizeAutoloader = '--optimize-autoloader';
return $this;
}
/**
* @param null|string $pathToComposer
*
@@ -128,18 +62,187 @@ abstract class Base extends BaseTask
}
}
/**
* adds `prefer-dist` option to composer
*
* @return $this
*/
public function preferDist($preferDist = true)
{
if (!$preferDist) {
return $this->preferSource();
}
$this->prefer = '--prefer-dist';
return $this;
}
/**
* adds `prefer-source` option to composer
*
* @return $this
*/
public function preferSource()
{
$this->prefer = '--prefer-source';
return $this;
}
/**
* adds `dev` option to composer
*
* @return $this
*/
public function dev($dev = true)
{
if (!$dev) {
return $this->noDev();
}
$this->dev = '--dev';
return $this;
}
/**
* adds `no-dev` option to composer
*
* @return $this
*/
public function noDev()
{
$this->dev = '--no-dev';
return $this;
}
/**
* adds `ansi` option to composer
*
* @return $this
*/
public function ansi($ansi = true)
{
if (!$ansi) {
return $this->noAnsi();
}
$this->ansi = '--ansi';
return $this;
}
/**
* adds `no-ansi` option to composer
*
* @return $this
*/
public function noAnsi()
{
$this->ansi = '--no-ansi';
return $this;
}
public function interaction($interaction = true)
{
if (!$interaction) {
return $this->noInteraction();
}
return $this;
}
/**
* adds `no-interaction` option to composer
*
* @return $this
*/
public function noInteraction()
{
$this->nointeraction = '--no-interaction';
return $this;
}
/**
* adds `optimize-autoloader` option to composer
*
* @return $this
*/
public function optimizeAutoloader($optimize = true)
{
if ($optimize) {
$this->option('--optimize-autoloader');
}
return $this;
}
/**
* adds `ignore-platform-reqs` option to composer
*
* @return $this
*/
public function ignorePlatformRequirements($ignore = true)
{
$this->option('--ignore-platform-reqs');
return $this;
}
/**
* disable plugins
*
* @return $this
*/
public function disablePlugins($disable = true)
{
if ($disable) {
$this->option('--no-plugins');
}
return $this;
}
/**
* skip scripts
*
* @return $this
*/
public function noScripts($disable = true)
{
if ($disable) {
$this->option('--no-scripts');
}
return $this;
}
/**
* adds `--working-dir $dir` option to composer
*
* @return $this
*/
public function workingDir($dir)
{
$this->option("--working-dir", $dir);
return $this;
}
/**
* Copy class fields into command options as directed.
*/
public function buildCommand()
{
if (!isset($this->ansi) && $this->getConfig()->get(\Robo\Config\Config::DECORATED)) {
$this->ansi();
}
if (!isset($this->nointeraction) && !$this->getConfig()->get(\Robo\Config\Config::INTERACTIVE)) {
$this->noInteraction();
}
$this->option($this->prefer)
->option($this->dev)
->option($this->nointeraction)
->option($this->ansi);
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
if (!isset($this->ansi) && $this->getConfig()->isDecorated()) {
$this->ansi();
if (!$this->built) {
$this->buildCommand();
$this->built = true;
}
$this->option($this->prefer)
->option($this->dev)
->option($this->optimizeAutoloader)
->option($this->ansi);
return "{$this->command} {$this->action}{$this->arguments}";
}
}
@@ -0,0 +1,93 @@
<?php
namespace Robo\Task\Composer;
/**
* Composer Config
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerConfig()->set('bin-dir', 'bin/')->run();
* ?>
* ```
*/
class Config extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'config';
/**
* Set a configuration value
* @return $this
*/
public function set($key, $value)
{
$this->arg($key);
$this->arg($value);
return $this;
}
/**
* Operate on the global repository
* @return $this
*/
public function useGlobal($useGlobal = true)
{
if ($useGlobal) {
$this->option('global');
}
return $this;
}
/**
* @return $this
*/
public function repository($id, $uri, $repoType = 'vcs')
{
$this->arg("repositories.$id");
$this->arg($repoType);
$this->arg($uri);
return $this;
}
/**
* @return $this
*/
public function removeRepository($id)
{
$this->option('unset', "repositories.$id");
return $this;
}
/**
* @return $this
*/
public function disableRepository($id)
{
$this->arg("repositories.$id");
$this->arg('false');
return $this;
}
/**
* @return $this
*/
public function enableRepository($id)
{
$this->arg("repositories.$id");
$this->arg('true');
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Configuring composer.json: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
@@ -0,0 +1,112 @@
<?php
namespace Robo\Task\Composer;
/**
* Composer CreateProject
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerCreateProject()->source('foo/bar')->target('myBar')->run();
* ?>
* ```
*/
class CreateProject extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'create-project';
protected $source;
protected $target = '';
protected $version = '';
/**
* @return $this
*/
public function source($source)
{
$this->source = $source;
return $this;
}
/**
* @return $this
*/
public function target($target)
{
$this->target = $target;
return $this;
}
/**
* @return $this
*/
public function version($version)
{
$this->version = $version;
return $this;
}
public function keepVcs($keep = true)
{
if ($keep) {
$this->option('--keep-vcs');
}
return $this;
}
public function noInstall($noInstall = true)
{
if ($noInstall) {
$this->option('--no-install');
}
return $this;
}
/**
* @return $this
*/
public function repository($repository)
{
if (!empty($repository)) {
$this->option('repository', $repository);
}
return $this;
}
/**
* @return $this
*/
public function stability($stability)
{
if (!empty($stability)) {
$this->option('stability', $stability);
}
return $this;
}
public function buildCommand()
{
$this->arg($this->source);
if (!empty($this->target)) {
$this->arg($this->target);
}
if (!empty($this->version)) {
$this->arg($this->version);
}
return parent::buildCommand();
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Creating project: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
@@ -42,21 +42,14 @@ class DumpAutoload extends Base
/**
* @return $this
*/
public function optimize()
public function optimize($optimize = true)
{
$this->optimize = "--optimize";
if ($optimize) {
$this->option("--optimize");
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
$this->option($this->optimize);
return parent::getCommand();
}
/**
* {@inheritdoc}
*/
@@ -0,0 +1,115 @@
<?php
namespace Robo\Task\Composer;
/**
* Composer Init
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerInit()->run();
* ?>
* ```
*/
class Init extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'init';
/**
* @return $this
*/
public function projectName($projectName)
{
$this->option('name', $projectName);
return $this;
}
/**
* @return $this
*/
public function description($description)
{
$this->option('description', $description);
return $this;
}
/**
* @return $this
*/
public function author($author)
{
$this->option('author', $author);
return $this;
}
/**
* @return $this
*/
public function projectType($type)
{
$this->option('type', $type);
return $this;
}
/**
* @return $this
*/
public function homepage($homepage)
{
$this->option('homepage', $homepage);
return $this;
}
/**
* 'require' is a keyword, so it cannot be a method name.
* @return $this
*/
public function dependency($project, $version = null)
{
if (isset($version)) {
$project .= ":$version";
}
$this->option('require', $project);
return $this;
}
/**
* @return $this
*/
public function stability($stability)
{
$this->option('stability', $stability);
return $this;
}
/**
* @return $this
*/
public function license($license)
{
$this->option('license', $license);
return $this;
}
/**
* @return $this
*/
public function repository($repository)
{
$this->option('repository', $repository);
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Creating composer.json: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
@@ -2,12 +2,12 @@
namespace Robo\Task\Composer;
/**
* Composer Validate
* Composer Remove
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerValidate()->run();
* $this->taskComposerRemove()->run();
* ?>
* ```
*/
@@ -21,45 +21,55 @@ class Remove extends Base
/**
* @return $this
*/
public function dev()
public function dev($dev = true)
{
$this->option('--dev');
if ($dev) {
$this->option('--dev');
}
return $this;
}
/**
* @return $this
*/
public function noProgress()
public function noProgress($noProgress = true)
{
$this->option('--no-progress');
if ($noProgress) {
$this->option('--no-progress');
}
return $this;
}
/**
* @return $this
*/
public function noUpdate()
public function noUpdate($noUpdate = true)
{
$this->option('--no-update');
if ($noUpdate) {
$this->option('--no-update');
}
return $this;
}
/**
* @return $this
*/
public function updateNoDev()
public function updateNoDev($updateNoDev = true)
{
$this->option('--update-no-dev');
if ($updateNoDev) {
$this->option('--update-no-dev');
}
return $this;
}
/**
* @return $this
*/
public function noUpdateWithDependencies()
public function noUpdateWithDependencies($updateWithDependencies = true)
{
$this->option('--no-update-with-dependencies');
if ($updateWithDependencies) {
$this->option('--no-update-with-dependencies');
}
return $this;
}
@@ -0,0 +1,50 @@
<?php
namespace Robo\Task\Composer;
/**
* Composer Require
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerRequire()->dependency('foo/bar', '^.2.4.8')->run();
* ?>
* ```
*/
class RequireDependency extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'require';
/**
* 'require' is a keyword, so it cannot be a method name.
* @return $this
*/
public function dependency($project, $version = null)
{
$project = (array)$project;
if (isset($version)) {
$project = array_map(
function ($item) use ($version) {
return "$item:$version";
},
$project
);
}
$this->args($project);
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Requiring packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
@@ -18,90 +18,61 @@ class Validate extends Base
*/
protected $action = 'validate';
/**
* @var string
*/
protected $noCheckAll;
/**
* @var string
*/
protected $noCheckLock;
/**
* @var string
*/
protected $noCheckPublish;
/**
* @var string
*/
protected $withDependencies;
/**
* @var string
*/
protected $strict;
/**
* @return $this
*/
public function noCheckAll()
public function noCheckAll($noCheckAll = true)
{
$this->noCheckAll = '--no-check-all';
if ($noCheckAll) {
$this->option('--no-check-all');
}
return $this;
}
/**
* @return $this
*/
public function noCheckLock()
public function noCheckLock($noCheckLock = true)
{
$this->noCheckLock = '--no-check-lock';
if ($noCheckLock) {
$this->option('--no-check-lock');
}
return $this;
}
/**
* @return $this
*/
public function noCheckPublish()
public function noCheckPublish($noCheckPublish = true)
{
$this->noCheckPublish = '--no-check-publish';
if ($noCheckPublish) {
$this->option('--no-check-publish');
}
return $this;
}
/**
* @return $this
*/
public function withDependencies()
public function withDependencies($withDependencies = true)
{
$this->withDependencies = '--with-dependencies';
if ($withDependencies) {
$this->option('--with-dependencies');
}
return $this;
}
/**
* @return $this
*/
public function strict()
public function strict($strict = true)
{
$this->strict = '--strict';
if ($strict) {
$this->option('--strict');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
$this->option($this->noCheckAll);
$this->option($this->noCheckLock);
$this->option($this->noCheckPublish);
$this->option($this->withDependencies);
$this->option($this->strict);
return parent::getCommand();
}
/**
* {@inheritdoc}
*/
@@ -33,6 +33,26 @@ trait loadTasks
return $this->task(DumpAutoload::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Init
*/
protected function taskComposerInit($pathToComposer = null)
{
return $this->task(Init::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Init
*/
protected function taskComposerConfig($pathToComposer = null)
{
return $this->task(Config::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
@@ -52,4 +72,24 @@ trait loadTasks
{
return $this->task(Remove::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Remove
*/
protected function taskComposerRequire($pathToComposer = null)
{
return $this->task(RequireDependency::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return Remove
*/
protected function taskComposerCreateProject($pathToComposer = null)
{
return $this->task(CreateProject::class, $pathToComposer);
}
}
@@ -2,10 +2,7 @@
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Task\File\Replace;
use Robo\Task\Filesystem;
use Robo\Result;
use Robo\Task\Development;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
@@ -33,10 +30,6 @@ use Robo\Common\BuilderAwareTrait;
* ->run();
* ?>
* ```
*
* @method Development\Changelog filename(string $filename)
* @method Development\Changelog anchor(string $anchor)
* @method Development\Changelog version(string $version)
*/
class Changelog extends BaseTask implements BuilderAwareInterface
{
@@ -62,6 +55,16 @@ class Changelog extends BaseTask implements BuilderAwareInterface
*/
protected $version = "";
/**
* @var string
*/
protected $body = "";
/**
* @var string
*/
protected $header = "";
/**
* @param string $filename
*
@@ -73,6 +76,32 @@ class Changelog extends BaseTask implements BuilderAwareInterface
return $this;
}
/**
* Sets the changelog body text.
*
* This method permits the raw changelog text to be set directly If this is set, $this->log changes will be ignored.
*
* @param string $body
*
* @return $this
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
/**
* @param string $header
*
* @return $this
*/
public function setHeader($header)
{
$this->header = $header;
return $this;
}
/**
* @param string $item
*
@@ -149,20 +178,17 @@ class Changelog extends BaseTask implements BuilderAwareInterface
*/
public function run()
{
if (empty($this->log)) {
return Result::error($this, "Changelog is empty");
if (empty($this->body)) {
if (empty($this->log)) {
return Result::error($this, "Changelog is empty");
}
$this->body = $this->generateBody();
}
$text = implode(
"\n",
array_map(
function ($i) {
return "* $i *" . date('Y-m-d') . "*";
},
$this->log
)
) . "\n";
$ver = "#### {$this->version}\n\n";
$text = $ver . $text;
if (empty($this->header)) {
$this->header = $this->generateHeader();
}
$text = $this->header . $this->body;
if (!file_exists($this->filename)) {
$this->printTaskInfo('Creating {filename}', ['filename' => $this->filename]);
@@ -174,13 +200,13 @@ class Changelog extends BaseTask implements BuilderAwareInterface
/** @var \Robo\Result $result */
// trying to append to changelog for today
$result = $this->collectionBuilder()->taskReplace($this->filename)
->from($ver)
$result = $this->collectionBuilder()->taskReplaceInFile($this->filename)
->from($this->header)
->to($text)
->run();
if (!isset($result['replaced']) || !$result['replaced']) {
$result = $this->collectionBuilder()->taskReplace($this->filename)
$result = $this->collectionBuilder()->taskReplaceInFile($this->filename)
->from($this->anchor)
->to($this->anchor . "\n\n" . $text)
->run();
@@ -188,4 +214,33 @@ class Changelog extends BaseTask implements BuilderAwareInterface
return new Result($this, $result->getExitCode(), $result->getMessage(), $this->log);
}
/**
* @return \Robo\Result|string
*/
protected function generateBody()
{
$text = implode("\n", array_map([$this, 'processLogRow'], $this->log));
$text .= "\n";
return $text;
}
/**
* @return string
*/
protected function generateHeader()
{
return "#### {$this->version}\n\n";
}
/**
* @param $i
*
* @return string
*/
public function processLogRow($i)
{
return "* $i *" . date('Y-m-d') . "*";
}
}
@@ -2,10 +2,7 @@
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Task\File\Write;
use Robo\Task\Filesystem;
use Robo\Result;
use Robo\Task\Development;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
@@ -41,24 +38,6 @@ use Robo\Common\BuilderAwareTrait;
* return strpos($r->name, 'save')===0 ? "[Saves to the database]\n" . $text : $text;
* })->run();
* ```
*
* @method \Robo\Task\Development\GenerateMarkdownDoc docClass(string $classname) put a class you want to be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc filterMethods(\Closure $func) using callback function filter out methods that won't be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc filterClasses(\Closure $func) using callback function filter out classes that won't be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc filterProperties(\Closure $func) using callback function filter out properties that won't be documented
* @method \Robo\Task\Development\GenerateMarkdownDoc processClass(\Closure $func) post-process class documentation
* @method \Robo\Task\Development\GenerateMarkdownDoc processClassSignature(\Closure $func) post-process class signature. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processClassDocBlock(\Closure $func) post-process class docblock contents. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processMethod(\Closure $func) post-process method documentation. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processMethodSignature(\Closure $func) post-process method signature. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processMethodDocBlock(\Closure $func) post-process method docblock contents. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processProperty(\Closure $func) post-process property documentation. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processPropertySignature(\Closure $func) post-process property signature. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc processPropertyDocBlock(\Closure $func) post-process property docblock contents. Provide *false* to skip.
* @method \Robo\Task\Development\GenerateMarkdownDoc reorder(\Closure $func) use a function to reorder classes
* @method \Robo\Task\Development\GenerateMarkdownDoc reorderMethods(\Closure $func) use a function to reorder methods in class
* @method \Robo\Task\Development\GenerateMarkdownDoc prepend($text) inserts text into beginning of markdown file
* @method \Robo\Task\Development\GenerateMarkdownDoc append($text) inserts text in the end of markdown file
*/
class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
{
@@ -190,6 +169,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Put a class you want to be documented.
*
* @param string $item
*
* @return $this
@@ -201,6 +182,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Using a callback function filter out methods that won't be documented.
*
* @param callable $filterMethods
*
* @return $this
@@ -212,6 +195,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Using a callback function filter out classes that won't be documented.
*
* @param callable $filterClasses
*
* @return $this
@@ -223,6 +208,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Using a callback function filter out properties that won't be documented.
*
* @param callable $filterProperties
*
* @return $this
@@ -234,6 +221,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process class documentation.
*
* @param callable $processClass
*
* @return $this
@@ -245,6 +234,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process class signature. Provide *false* to skip.
*
* @param callable|false $processClassSignature
*
* @return $this
@@ -256,6 +247,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process class docblock contents. Provide *false* to skip.
*
* @param callable|false $processClassDocBlock
*
* @return $this
@@ -267,6 +260,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process method documentation. Provide *false* to skip.
*
* @param callable|false $processMethod
*
* @return $this
@@ -278,6 +273,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process method signature. Provide *false* to skip.
*
* @param callable|false $processMethodSignature
*
* @return $this
@@ -289,6 +286,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process method docblock contents. Provide *false* to skip.
*
* @param callable|false $processMethodDocBlock
*
* @return $this
@@ -300,6 +299,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process property documentation. Provide *false* to skip.
*
* @param callable|false $processProperty
*
* @return $this
@@ -311,6 +312,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process property signature. Provide *false* to skip.
*
* @param callable|false $processPropertySignature
*
* @return $this
@@ -322,6 +325,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Post-process property docblock contents. Provide *false* to skip.
*
* @param callable|false $processPropertyDocBlock
*
* @return $this
@@ -333,6 +338,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Use a function to reorder classes.
*
* @param callable $reorder
*
* @return $this
@@ -344,6 +351,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Use a function to reorder methods in class.
*
* @param callable $reorderMethods
*
* @return $this
@@ -377,6 +386,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Inserts text at the beginning of markdown file.
*
* @param string $prepend
*
* @return $this
@@ -388,6 +399,8 @@ class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
}
/**
* Inserts text at the end of markdown file.
*
* @param string $append
*
* @return $this
@@ -2,7 +2,6 @@
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Symfony\Component\Process\ProcessUtils;
use Robo\Result;
/**
@@ -4,13 +4,9 @@ namespace Robo\Task\Development;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
/**
* @method \Robo\Task\Development\GitHub repo(string)
* @method \Robo\Task\Development\GitHub owner(string)
*/
abstract class GitHub extends BaseTask
{
const GITHUB_URL = 'https://Api.github.com';
const GITHUB_URL = 'https://api.github.com';
/**
* @var string
@@ -32,6 +28,11 @@ abstract class GitHub extends BaseTask
*/
protected $owner;
/**
* @var string
*/
protected $accessToken;
/**
* @param string $repo
*
@@ -95,6 +96,17 @@ abstract class GitHub extends BaseTask
return $this;
}
/**
* @param $accessToken
*
* @return $this
*/
public function accessToken($token)
{
$this->accessToken = $token;
return $this;
}
/**
* @param string $uri
* @param array $params
@@ -119,6 +131,10 @@ abstract class GitHub extends BaseTask
curl_setopt($ch, CURLOPT_USERPWD, $this->user . ':' . $this->password);
}
if (!empty($this->accessToken)) {
$url .= "?access_token=" . $this->accessToken;
}
curl_setopt_array(
$ch,
array(
@@ -190,7 +190,7 @@ class GitHubRelease extends GitHub
[
"tag_name" => $this->tag,
"target_commitish" => $this->comittish,
"name" => $this->tag,
"name" => $this->name,
"body" => $this->getBody(),
"draft" => $this->draft,
"prerelease" => $this->prerelease
@@ -2,7 +2,7 @@
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Symfony\Component\Process\ProcessUtils;
use Robo\Common\ProcessUtils;
use Robo\Result;
/**
@@ -2,7 +2,6 @@
namespace Robo\Task\Development;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
@@ -23,6 +23,8 @@ class SemVer implements TaskInterface
const REGEX = "/^\-\-\-\n:major:\s(0|[1-9]\d*)\n:minor:\s(0|[1-9]\d*)\n:patch:\s(0|[1-9]\d*)\n:special:\s'([a-zA-z0-9]*\.?(?:0|[1-9]\d*)?)'\n:metadata:\s'((?:0|[1-9]\d*)?(?:\.[a-zA-z0-9\.]*)?)'/";
const REGEX_STRING = '/^(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<patch>[0-9]+)(|-(?<special>[0-9a-zA-Z.]+))(|\+(?<metadata>[0-9a-zA-Z.]+))$/';
/**
* @var string
*/
@@ -62,7 +64,8 @@ class SemVer implements TaskInterface
$this->path = $filename;
if (file_exists($this->path)) {
$this->parse();
$semverFileContents = file_get_contents($this->path);
$this->parseFile($semverFileContents);
}
}
@@ -85,6 +88,12 @@ class SemVer implements TaskInterface
return str_replace($search, $replace, $this->format);
}
public function version($version)
{
$this->parseString($version);
return $this;
}
/**
* @param string $format
*
@@ -208,6 +217,9 @@ class SemVer implements TaskInterface
*/
protected function dump()
{
if (empty($this->path)) {
return true;
}
extract($this->version);
$semver = sprintf(self::SEMVER, $major, $minor, $patch, $special, $metadata);
if (is_writeable($this->path) === false || file_put_contents($this->path, $semver) === false) {
@@ -216,14 +228,24 @@ class SemVer implements TaskInterface
return true;
}
protected function parseString($semverString)
{
if (!preg_match_all(self::REGEX_STRING, $semverString, $matches)) {
throw new TaskException($this, 'Bad semver value: ' . $semverString);
}
$this->version = array_intersect_key($matches, $this->version);
$this->version = array_map(function ($item) {
return $item[0];
}, $this->version);
}
/**
* @throws \Robo\Exception\TaskException
*/
protected function parse()
protected function parseFile($semverFileContents)
{
$output = file_get_contents($this->path);
if (!preg_match_all(self::REGEX, $output, $matches)) {
if (!preg_match_all(self::REGEX, $semverFileContents, $matches)) {
throw new TaskException($this, 'Bad semver file.');
}
@@ -2,10 +2,11 @@
namespace Robo\Task\Docker;
use Robo\Common\ExecOneCommand;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
abstract class Base extends BaseTask implements PrintedInterface
abstract class Base extends BaseTask implements CommandInterface, PrintedInterface
{
use ExecOneCommand;
@@ -20,7 +21,6 @@ abstract class Base extends BaseTask implements PrintedInterface
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Running {command}', ['command' => $command]);
return $this->executeCommand($command);
}
@@ -64,12 +64,14 @@ class Exec extends Base
}
/**
* @return $this
* {@inheritdoc)}
*/
public function interactive()
public function interactive($interactive = true)
{
$this->option('-i');
return $this;
if ($interactive) {
$this->option('-i');
}
return parent::interactive($interactive);
}
/**
@@ -115,12 +115,14 @@ class Run extends Base
}
/**
* @return $this
* {@inheritdoc)}
*/
public function interactive()
public function interactive($interactive = true)
{
$this->option('-i');
return $this;
if ($interactive) {
$this->option('-i');
}
return parent::interactive($interactive);
}
/**
@@ -147,13 +149,29 @@ class Run extends Base
return $this;
}
/**
* Set environment variables.
* n.b. $this->env($variable, $value) also available here,
* inherited from ExecTrait.
*
* @param array $env
* @return type
*/
public function envVars(array $env)
{
foreach ($env as $variable => $value) {
$this->setDockerEnv($variable, $value);
}
return $this;
}
/**
* @param string $variable
* @param null|string $value
*
* @return $this
*/
public function env($variable, $value = null)
protected function setDockerEnv($variable, $value = null)
{
$env = $value ? "$variable=$value" : $variable;
return $this->option("-e", $env);
@@ -30,10 +30,6 @@ use Robo\Task\BaseTask;
* ->run();
* ?>
* ```
*
* @method regex(string) regex to match string to be replaced
* @method from(string|array) string(s) to be replaced
* @method to(string|array) value(s) to be set as a replacement
*/
class Replace extends BaseTask
{
@@ -43,12 +39,12 @@ class Replace extends BaseTask
protected $filename;
/**
* @var string[]
* @var string|string[]
*/
protected $from;
/**
* @var string[]
* @var string|string[]
*/
protected $to;
@@ -77,7 +73,9 @@ class Replace extends BaseTask
}
/**
* @param string $from
* String(s) to be replaced.
*
* @param string|string[] $from
*
* @return $this
*/
@@ -88,7 +86,9 @@ class Replace extends BaseTask
}
/**
* @param string $to
* Value(s) to be set as a replacement.
*
* @param string|string[] $to
*
* @return $this
*/
@@ -99,6 +99,8 @@ class Replace extends BaseTask
}
/**
* Regex to match string to be replaced.
*
* @param string $regex
*
* @return $this
@@ -2,7 +2,6 @@
namespace Robo\Task\File;
use Robo\Collection\Collection;
use Robo\Contract\CompletionInterface;
/**
@@ -36,15 +35,15 @@ class TmpFile extends Write implements CompletionInterface
*/
public function __construct($filename = 'tmp', $extension = '', $baseDir = '', $includeRandomPart = true)
{
if (empty($base)) {
$base = sys_get_temp_dir();
if (empty($baseDir)) {
$baseDir = sys_get_temp_dir();
}
if ($includeRandomPart) {
$random = static::randomString();
$filename = "{$filename}_{$random}";
}
$filename .= $extension;
parent::__construct("{$base}/{$filename}");
parent::__construct("{$baseDir}/{$filename}");
}
/**
@@ -17,8 +17,6 @@ use Robo\Task\BaseTask;
* ->run();
* ?>
* ```
*
* @method append()
*/
class Write extends BaseTask
{
@@ -1,8 +1,6 @@
<?php
namespace Robo\Task\File;
use Robo\Collection\Temporary;
trait loadTasks
{
/**
@@ -20,6 +20,15 @@ class CopyDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* Explicitly declare our consturctor, so that
* our copyDir() method does not look like a php4 constructor.
*/
public function __construct($dirs)
{
parent::__construct($dirs);
}
/**
* @var int
*/
@@ -32,6 +41,11 @@ class CopyDir extends BaseDir
*/
protected $exclude = [];
/**
* Overwrite destination files newer than source files.
*/
protected $overwrite = true;
/**
* {@inheritdoc}
*/
@@ -73,7 +87,20 @@ class CopyDir extends BaseDir
*/
public function exclude($exclude = [])
{
$this->exclude = $exclude;
$this->exclude = $this->simplifyForCompare($exclude);
return $this;
}
/**
* Destination files newer than source files are overwritten.
*
* @param bool $overwrite
*
* @return $this
*/
public function overwrite($overwrite)
{
$this->overwrite = $overwrite;
return $this;
}
@@ -82,10 +109,11 @@ class CopyDir extends BaseDir
*
* @param string $src Source directory
* @param string $dst Destination directory
* @param string $parent Parent directory
*
* @throws \Robo\Exception\TaskException
*/
protected function copyDir($src, $dst)
protected function copyDir($src, $dst, $parent = '')
{
$dir = @opendir($src);
if (false === $dir) {
@@ -95,19 +123,40 @@ class CopyDir extends BaseDir
mkdir($dst, $this->chmod, true);
}
while (false !== ($file = readdir($dir))) {
if (in_array($file, $this->exclude)) {
continue;
// Support basename and full path exclusion.
if ($this->excluded($file, $src, $parent)) {
continue;
}
if (($file !== '.') && ($file !== '..')) {
$srcFile = $src . '/' . $file;
$destFile = $dst . '/' . $file;
if (is_dir($srcFile)) {
$this->copyDir($srcFile, $destFile);
} else {
copy($srcFile, $destFile);
}
$srcFile = $src . '/' . $file;
$destFile = $dst . '/' . $file;
if (is_dir($srcFile)) {
$this->copyDir($srcFile, $destFile, $parent . $file . DIRECTORY_SEPARATOR);
} else {
$this->fs->copy($srcFile, $destFile, $this->overwrite);
}
}
closedir($dir);
}
/**
* Check to see if the current item is excluded.
*/
protected function excluded($file, $src, $parent)
{
return
($file == '.') ||
($file == '..') ||
in_array($file, $this->exclude) ||
in_array($this->simplifyForCompare($parent . $file), $this->exclude) ||
in_array($this->simplifyForCompare($src . DIRECTORY_SEPARATOR . $file), $this->exclude);
}
/**
* Avoid problems comparing paths on Windows that may have a
* combination of DIRECTORY_SEPARATOR and /.
*/
protected function simplifyForCompare($item)
{
return str_replace(DIRECTORY_SEPARATOR, '/', $item);
}
}
@@ -1,10 +1,8 @@
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Task\StackBasedTask;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
@@ -29,16 +27,16 @@ use Robo\Common\BuilderAwareTrait;
* ?>
* ```
*
* @method $this mkdir($dir)
* @method $this touch($file)
* @method $this copy($from, $to, $force = null)
* @method $this chmod($file, $permissions, $umask = null, $recursive = null)
* @method $this chgrp($file, $group, $recursive = null)
* @method $this chown($file, $user, $recursive = null)
* @method $this remove($file)
* @method $this rename($from, $to)
* @method $this symlink($from, $to)
* @method $this mirror($from, $to)
* @method $this mkdir(string|array|\Traversable $dir, int $mode = 0777)
* @method $this touch(string|array|\Traversable $file, int $time = null, int $atime = null)
* @method $this copy(string $from, string $to, bool $force = false)
* @method $this chmod(string|array|\Traversable $file, int $permissions, int $umask = 0000, bool $recursive = false)
* @method $this chgrp(string|array|\Traversable $file, string $group, bool $recursive = false)
* @method $this chown(string|array|\Traversable $file, string $user, bool $recursive = false)
* @method $this remove(string|array|\Traversable $file)
* @method $this rename(string $from, string $to, bool $force = false)
* @method $this symlink(string $from, string $to, bool $copyOnWindows = false)
* @method $this mirror(string $from, string $to, \Traversable $iterator = null, array $options = [])
*/
class FilesystemStack extends StackBasedTask implements BuilderAwareInterface
{
@@ -3,7 +3,6 @@
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Collection\Collection;
use Robo\Contract\CompletionInterface;
/**
@@ -3,8 +3,6 @@
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Collection\Collection;
use Robo\Contract\CompletionInterface;
use Robo\Contract\RollbackInterface;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
@@ -1,8 +1,6 @@
<?php
namespace Robo\Task\Filesystem;
use Robo\Collection\Temporary;
trait loadShortcuts
{
/**
@@ -50,12 +48,13 @@ trait loadShortcuts
/**
* @param string $from
* @param string $to
* @param bool $overwrite
*
* @return \Robo\Result
*/
protected function _rename($from, $to)
protected function _rename($from, $to, $overwrite = false)
{
return $this->taskFilesystemStack()->rename($from, $to)->run();
return $this->taskFilesystemStack()->rename($from, $to, $overwrite)->run();
}
/**
@@ -1,8 +1,6 @@
<?php
namespace Robo\Task\Filesystem;
use Robo\Collection\Temporary;
trait loadTasks
{
/**
@@ -4,7 +4,7 @@ namespace Robo\Task\Gulp;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
use Symfony\Component\Process\ProcessUtils;
use Robo\Common\ProcessUtils;
abstract class Base extends BaseTask
{
@@ -1,7 +1,6 @@
<?php
namespace Robo\Task\Gulp;
use Robo\Task\Gulp;
use Robo\Contract\CommandInterface;
/**
@@ -3,7 +3,6 @@ namespace Robo\Task\Remote;
use Robo\Contract\CommandInterface;
use Robo\Task\BaseTask;
use Robo\Task\Remote;
use Robo\Exception\TaskException;
/**
@@ -44,11 +43,6 @@ use Robo\Exception\TaskException;
* $rsync->run();
* }
* ```
*
* @method \Robo\Task\Remote\Rsync fromUser(string $user)
* @method \Robo\Task\Remote\Rsync fromHost(string $hostname)
* @method \Robo\Task\Remote\Rsync toUser(string $user)
* @method \Robo\Task\Remote\Rsync toHost(string $hostname)
*/
class Rsync extends BaseTask implements CommandInterface
{
@@ -432,7 +426,6 @@ class Rsync extends BaseTask implements CommandInterface
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo("Running {command}", ['command' => $command]);
return $this->executeCommand($command);
}
@@ -2,7 +2,6 @@
namespace Robo\Task\Remote;
use Robo\Result;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
@@ -41,10 +40,6 @@ use Robo\Contract\SimulatedInterface;
* ```php
* \Robo\Task\Remote\Ssh::configure('remoteDir', '/some-dir');
* ```
*
* @method $this stopOnFail(bool $stopOnFail) Whether or not to chain commands together with &&
* and stop the chain if one command fails
* @method $this remoteDir(string $remoteWorkingDirectory) Changes to the given directory before running commands
*/
class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
{
@@ -111,6 +106,8 @@ class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
}
/**
* Whether or not to chain commands together with && and stop the chain if one command fails.
*
* @param bool $stopOnFail
*
* @return $this
@@ -122,6 +119,8 @@ class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
}
/**
* Changes to the given directory before running commands.
*
* @param string $remoteDir
*
* @return $this
@@ -269,6 +268,6 @@ class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
$hostSpec = $this->user . '@' . $hostSpec;
}
return sprintf("ssh{$sshOptions} {$hostSpec} '{$command}'");
return "ssh{$sshOptions} {$hostSpec} '{$command}'";
}
}
@@ -8,7 +8,6 @@ use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Contract\SimulatedInterface;
use Robo\Log\RoboLogLevel;
use Psr\Log\LogLevel;
use Robo\Contract\CommandInterface;
class Simulator extends BaseTask implements CommandInterface
@@ -2,8 +2,6 @@
namespace Robo\Task;
use Robo\Result;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
/**
* Extend StackBasedTask to create a Robo task that
@@ -116,7 +114,7 @@ abstract class StackBasedTask extends BaseTask
*/
protected function printTaskProgress($command, $action)
{
$this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action)]);
$this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action, JSON_UNESCAPED_SLASHES)]);
}
/**
@@ -53,7 +53,6 @@ class Behat extends BaseTask implements CommandInterface, PrintedInterface
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither composer nor phar installation of Behat found");
}
$this->arg('run');
}
/**
@@ -5,6 +5,7 @@ use Robo\Contract\PrintedInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Contract\CommandInterface;
use Symfony\Component\Process\Process;
/**
* Executes Codeception tests
@@ -27,17 +28,7 @@ use Robo\Contract\CommandInterface;
class Codecept extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $suite = '';
/**
* @var string
*/
protected $test = '';
/**
* @var string
*/
@@ -67,7 +58,7 @@ class Codecept extends BaseTask implements CommandInterface, PrintedInterface
*/
public function suite($suite)
{
$this->suite = $suite;
$this->option(null, $suite);
return $this;
}
@@ -78,7 +69,7 @@ class Codecept extends BaseTask implements CommandInterface, PrintedInterface
*/
public function test($testName)
{
$this->test = $testName;
$this->option(null, $testName);
return $this;
}
@@ -241,13 +232,30 @@ class Codecept extends BaseTask implements CommandInterface, PrintedInterface
return $this;
}
/**
* @return $this
*/
public function noRebuild()
{
$this->option("no-rebuild");
return $this;
}
/**
* @param string $failGroup
* @return $this
*/
public function failGroup($failGroup)
{
$this->option('override', "extensions: config: Codeception\\Extension\\RunFailed: fail-group: {$failGroup}");
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
$this->option(null, $this->suite)
->option(null, $this->test);
return $this->command . $this->arguments;
}
@@ -2,7 +2,7 @@
namespace Robo\Task\Vcs;
use Robo\Task\CommandStack;
use Symfony\Component\Process\ProcessUtils;
use Robo\Common\ProcessUtils;
/**
* Runs Git commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
@@ -45,9 +45,33 @@ class GitStack extends CommandStack
*
* @return $this
*/
public function cloneRepo($repo, $to = "")
public function cloneRepo($repo, $to = "", $branch = "")
{
return $this->exec(['clone', $repo, $to]);
$cmd = ['clone', $repo, $to];
if (!empty($branch)) {
$cmd[] = "--branch $branch";
}
return $this->exec($cmd);
}
/**
* Executes `git clone` with depth 1 as default
*
* @param string $repo
* @param string $to
* @param string $branch
* @param int $depth
*
* @return $this
*/
public function cloneShallow($repo, $to = '', $branch = "", $depth = 1)
{
$cmd = ["clone --depth $depth", $repo, $to];
if (!empty($branch)) {
$cmd[] = "--branch $branch";
}
return $this->exec($cmd);
}
/**