X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=vendor%2Fconsolidation%2Frobo%2Fsrc%2FCollection%2FCollection.php;fp=vendor%2Fconsolidation%2Frobo%2Fsrc%2FCollection%2FCollection.php;h=607435a48fc0f2a1510f557e48d5960cf0f91bab;hp=0000000000000000000000000000000000000000;hb=af6d1fb995500ae68849458ee10d66abbdcfb252;hpb=680c79a86e3ed402f263faeac92e89fb6d9edcc0 diff --git a/vendor/consolidation/robo/src/Collection/Collection.php b/vendor/consolidation/robo/src/Collection/Collection.php new file mode 100644 index 000000000..607435a48 --- /dev/null +++ b/vendor/consolidation/robo/src/Collection/Collection.php @@ -0,0 +1,769 @@ +resetState(); + } + + public function setProgressBarAutoDisplayInterval($interval) + { + if (!$this->progressIndicator) { + return; + } + return $this->progressIndicator->setProgressBarAutoDisplayInterval($interval); + } + + /** + * {@inheritdoc} + */ + public function add(TaskInterface $task, $name = self::UNNAMEDTASK) + { + $task = new CompletionWrapper($this, $task); + $this->addToTaskList($name, $task); + return $this; + } + + /** + * {@inheritdoc} + */ + public function addCode(callable $code, $name = self::UNNAMEDTASK) + { + return $this->add(new CallableTask($code, $this), $name); + } + + /** + * {@inheritdoc} + */ + public function addIterable($iterable, callable $code) + { + $callbackTask = (new IterationTask($iterable, $code, $this))->inflect($this); + return $this->add($callbackTask); + } + + /** + * {@inheritdoc} + */ + public function rollback(TaskInterface $rollbackTask) + { + // Rollback tasks always try as hard as they can, and never report failures. + $rollbackTask = $this->ignoreErrorsTaskWrapper($rollbackTask); + return $this->wrapAndRegisterRollback($rollbackTask); + } + + /** + * {@inheritdoc} + */ + public function rollbackCode(callable $rollbackCode) + { + // Rollback tasks always try as hard as they can, and never report failures. + $rollbackTask = $this->ignoreErrorsCodeWrapper($rollbackCode); + return $this->wrapAndRegisterRollback($rollbackTask); + } + + /** + * {@inheritdoc} + */ + public function completion(TaskInterface $completionTask) + { + $collection = $this; + $completionRegistrationTask = new CallableTask( + function () use ($collection, $completionTask) { + + $collection->registerCompletion($completionTask); + }, + $this + ); + $this->addToTaskList(self::UNNAMEDTASK, $completionRegistrationTask); + return $this; + } + + /** + * {@inheritdoc} + */ + public function completionCode(callable $completionTask) + { + $completionTask = new CallableTask($completionTask, $this); + return $this->completion($completionTask); + } + + /** + * {@inheritdoc} + */ + public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK) + { + return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd); + } + + /** + * {@inheritdoc} + */ + public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK) + { + return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd); + } + + /** + * {@inheritdoc} + */ + public function progressMessage($text, $context = [], $level = LogLevel::NOTICE) + { + $context += ['name' => 'Progress']; + $context += TaskInfo::getTaskContext($this); + return $this->addCode( + function () use ($level, $text, $context) { + $context += $this->getState()->getData(); + $this->printTaskOutput($level, $text, $context); + } + ); + } + + /** + * @param \Robo\Contract\TaskInterface $rollbackTask + * + * @return $this + */ + protected function wrapAndRegisterRollback(TaskInterface $rollbackTask) + { + $collection = $this; + $rollbackRegistrationTask = new CallableTask( + function () use ($collection, $rollbackTask) { + $collection->registerRollback($rollbackTask); + }, + $this + ); + $this->addToTaskList(self::UNNAMEDTASK, $rollbackRegistrationTask); + return $this; + } + + /** + * Add either a 'before' or 'after' function or task. + * + * @param string $method + * @param string $name + * @param callable|TaskInterface $task + * @param string $nameOfTaskToAdd + * + * @return $this + */ + protected function addBeforeOrAfter($method, $name, $task, $nameOfTaskToAdd) + { + if (is_callable($task)) { + $task = new CallableTask($task, $this); + } + $existingTask = $this->namedTask($name); + $fn = [$existingTask, $method]; + call_user_func($fn, $task, $nameOfTaskToAdd); + return $this; + } + + /** + * Wrap the provided task in a wrapper that will ignore + * any errors or exceptions that may be produced. This + * is useful, for example, in adding optional cleanup tasks + * at the beginning of a task collection, to remove previous + * results which may or may not exist. + * + * TODO: Provide some way to specify which sort of errors + * are ignored, so that 'file not found' may be ignored, + * but 'permission denied' reported? + * + * @param \Robo\Contract\TaskInterface $task + * + * @return \Robo\Collection\CallableTask + */ + public function ignoreErrorsTaskWrapper(TaskInterface $task) + { + // If the task is a stack-based task, then tell it + // to try to run all of its operations, even if some + // of them fail. + if ($task instanceof StackBasedTask) { + $task->stopOnFail(false); + } + $ignoreErrorsInTask = function () use ($task) { + $data = []; + try { + $result = $this->runSubtask($task); + $message = $result->getMessage(); + $data = $result->getData(); + $data['exitcode'] = $result->getExitCode(); + } catch (\Exception $e) { + $message = $e->getMessage(); + } + + return Result::success($task, $message, $data); + }; + // Wrap our ignore errors callable in a task. + return new CallableTask($ignoreErrorsInTask, $this); + } + + /** + * @param callable $task + * + * @return \Robo\Collection\CallableTask + */ + public function ignoreErrorsCodeWrapper(callable $task) + { + return $this->ignoreErrorsTaskWrapper(new CallableTask($task, $this)); + } + + /** + * Return the list of task names added to this collection. + * + * @return array + */ + public function taskNames() + { + return array_keys($this->taskList); + } + + /** + * Test to see if a specified task name exists. + * n.b. before() and after() require that the named + * task exist; use this function to test first, if + * unsure. + * + * @param string $name + * + * @return bool + */ + public function hasTask($name) + { + return array_key_exists($name, $this->taskList); + } + + /** + * Find an existing named task. + * + * @param string $name + * The name of the task to insert before. The named task MUST exist. + * + * @return Element + * The task group for the named task. Generally this is only + * used to call 'before()' and 'after()'. + */ + protected function namedTask($name) + { + if (!$this->hasTask($name)) { + throw new \RuntimeException("Could not find task named $name"); + } + return $this->taskList[$name]; + } + + /** + * Add a list of tasks to our task collection. + * + * @param TaskInterface[] $tasks + * An array of tasks to run with rollback protection + * + * @return $this + */ + public function addTaskList(array $tasks) + { + foreach ($tasks as $name => $task) { + $this->add($task, $name); + } + return $this; + } + + /** + * Add the provided task to our task list. + * + * @param string $name + * @param \Robo\Contract\TaskInterface $task + * + * @return \Robo\Collection\Collection + */ + protected function addToTaskList($name, TaskInterface $task) + { + // All tasks are stored in a task group so that we have a place + // to hang 'before' and 'after' tasks. + $taskGroup = new Element($task); + return $this->addCollectionElementToTaskList($name, $taskGroup); + } + + /** + * @param int|string $name + * @param \Robo\Collection\Element $taskGroup + * + * @return $this + */ + protected function addCollectionElementToTaskList($name, Element $taskGroup) + { + // If a task name is not provided, then we'll let php pick + // the array index. + if (Result::isUnnamed($name)) { + $this->taskList[] = $taskGroup; + return $this; + } + // If we are replacing an existing task with the + // same name, ensure that our new task is added to + // the end. + $this->taskList[$name] = $taskGroup; + return $this; + } + + /** + * Set the parent collection. This is necessary so that nested + * collections' rollback and completion tasks can be added to the + * top-level collection, ensuring that the rollbacks for a collection + * will run if any later task fails. + * + * @param \Robo\Collection\NestedCollectionInterface $parentCollection + * + * @return $this + */ + public function setParentCollection(NestedCollectionInterface $parentCollection) + { + $this->parentCollection = $parentCollection; + return $this; + } + + /** + * Get the appropriate parent collection to use + * + * @return CollectionInterface + */ + public function getParentCollection() + { + return $this->parentCollection ? $this->parentCollection : $this; + } + + /** + * Register a rollback task to run if there is any failure. + * + * Clients are free to add tasks to the rollback stack as + * desired; however, usually it is preferable to call + * Collection::rollback() instead. With that function, + * the rollback function will only be called if all of the + * tasks added before it complete successfully, AND some later + * task fails. + * + * One example of a good use-case for registering a callback + * function directly is to add a task that sends notification + * when a task fails. + * + * @param TaskInterface $rollbackTask + * The rollback task to run on failure. + */ + public function registerRollback(TaskInterface $rollbackTask) + { + if ($this->parentCollection) { + return $this->parentCollection->registerRollback($rollbackTask); + } + if ($rollbackTask) { + $this->rollbackStack[] = $rollbackTask; + } + } + + /** + * Register a completion task to run once all other tasks finish. + * Completion tasks run whether or not a rollback operation was + * triggered. They do not trigger rollbacks if they fail. + * + * The typical use-case for a completion function is to clean up + * temporary objects (e.g. temporary folders). The preferred + * way to do that, though, is to use Temporary::wrap(). + * + * On failures, completion tasks will run after all rollback tasks. + * If one task collection is nested inside another task collection, + * then the nested collection's completion tasks will run as soon as + * the nested task completes; they are not deferred to the end of + * the containing collection's execution. + * + * @param TaskInterface $completionTask + * The completion task to run at the end of all other operations. + */ + public function registerCompletion(TaskInterface $completionTask) + { + if ($this->parentCollection) { + return $this->parentCollection->registerCompletion($completionTask); + } + if ($completionTask) { + // Completion tasks always try as hard as they can, and never report failures. + $completionTask = $this->ignoreErrorsTaskWrapper($completionTask); + $this->completionStack[] = $completionTask; + } + } + + /** + * Return the count of steps in this collection + * + * @return int + */ + public function progressIndicatorSteps() + { + $steps = 0; + foreach ($this->taskList as $name => $taskGroup) { + $steps += $taskGroup->progressIndicatorSteps(); + } + return $steps; + } + + /** + * A Collection of tasks can provide a command via `getCommand()` + * if it contains a single task, and that task implements CommandInterface. + * + * @return string + * + * @throws \Robo\Exception\TaskException + */ + public function getCommand() + { + if (empty($this->taskList)) { + return ''; + } + + if (count($this->taskList) > 1) { + // TODO: We could potentially iterate over the items in the collection + // and concatenate the result of getCommand() from each one, and fail + // only if we encounter a command that is not a CommandInterface. + throw new TaskException($this, "getCommand() does not work on arbitrary collections of tasks."); + } + + $taskElement = reset($this->taskList); + $task = $taskElement->getTask(); + $task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task; + if ($task instanceof CommandInterface) { + return $task->getCommand(); + } + + throw new TaskException($task, get_class($task) . " does not implement CommandInterface, so can't be used to provide a command"); + } + + /** + * Run our tasks, and roll back if necessary. + * + * @return \Robo\Result + */ + public function run() + { + $result = $this->runWithoutCompletion(); + $this->complete(); + return $result; + } + + /** + * @return \Robo\Result + */ + private function runWithoutCompletion() + { + $result = Result::success($this); + + if (empty($this->taskList)) { + return $result; + } + + $this->startProgressIndicator(); + if ($result->wasSuccessful()) { + foreach ($this->taskList as $name => $taskGroup) { + $taskList = $taskGroup->getTaskList(); + $result = $this->runTaskList($name, $taskList, $result); + if (!$result->wasSuccessful()) { + $this->fail(); + return $result; + } + } + $this->taskList = []; + } + $this->stopProgressIndicator(); + $result['time'] = $this->getExecutionTime(); + + return $result; + } + + /** + * Run every task in a list, but only up to the first failure. + * Return the failing result, or success if all tasks run. + * + * @param string $name + * @param TaskInterface[] $taskList + * @param \Robo\Result $result + * + * @return \Robo\Result + * + * @throws \Robo\Exception\TaskExitException + */ + private function runTaskList($name, array $taskList, Result $result) + { + try { + foreach ($taskList as $taskName => $task) { + $taskResult = $this->runSubtask($task); + $this->advanceProgressIndicator(); + // If the current task returns an error code, then stop + // execution and signal a rollback. + if (!$taskResult->wasSuccessful()) { + return $taskResult; + } + // We accumulate our results into a field so that tasks that + // have a reference to the collection may examine and modify + // 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(); + throw $exitException; + } catch (\Exception $e) { + // Tasks typically should not throw, but if one does, we will + // convert it into an error and roll back. + return Result::fromException($task, $e, $result->getData()); + } + return $result; + } + + /** + * Force the rollback functions to run + * + * @return $this + */ + public function fail() + { + $this->disableProgressIndicator(); + $this->runRollbackTasks(); + $this->complete(); + return $this; + } + + /** + * Force the completion functions to run + * + * @return $this + */ + public function complete() + { + $this->detatchProgressIndicator(); + $this->runTaskListIgnoringFailures($this->completionStack); + $this->reset(); + return $this; + } + + /** + * Reset this collection, removing all tasks. + * + * @return $this + */ + public function reset() + { + $this->taskList = []; + $this->completionStack = []; + $this->rollbackStack = []; + return $this; + } + + /** + * Run all of our rollback tasks. + * + * Note that Collection does not implement RollbackInterface, but + * it may still be used as a task inside another task collection + * (i.e. you can nest task collections, if desired). + */ + protected function runRollbackTasks() + { + $this->runTaskListIgnoringFailures($this->rollbackStack); + // Erase our rollback stack once we have finished rolling + // everything back. This will allow us to potentially use + // a command collection more than once (e.g. to retry a + // failed operation after doing some error recovery). + $this->rollbackStack = []; + } + + /** + * @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task + * + * @return \Robo\Result + */ + protected function runSubtask($task) + { + $original = ($task instanceof WrappedTaskInterface) ? $task->original() : $task; + $this->setParentCollectionForTask($original, $this->getParentCollection()); + 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 + */ + protected function setParentCollectionForTask($task, $parentCollection) + { + if ($task instanceof NestedCollectionInterface) { + $task->setParentCollection($parentCollection); + } + } + + /** + * Run all of the tasks in a provided list, ignoring failures. + * This is used to roll back or complete. + * + * @param TaskInterface[] $taskList + */ + protected function runTaskListIgnoringFailures(array $taskList) + { + foreach ($taskList as $task) { + try { + $this->runSubtask($task); + } catch (\Exception $e) { + // Ignore rollback failures. + } + } + } + + /** + * Give all of our tasks to the provided collection builder. + * + * @param CollectionBuilder $builder + */ + public function transferTasks($builder) + { + foreach ($this->taskList as $name => $taskGroup) { + // TODO: We are abandoning all of our before and after tasks here. + // At the moment, transferTasks is only called under conditions where + // there will be none of these, but care should be taken if that changes. + $task = $taskGroup->getTask(); + $builder->addTaskToCollection($task); + } + $this->reset(); + } +}