Refactoring

This commit is contained in:
gamonoid
2017-09-03 20:39:22 +02:00
parent af40881847
commit a7274d3cfd
5075 changed files with 238202 additions and 16291 deletions

View File

@@ -0,0 +1,15 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[**.php]
indent_style = space
indent_size = 4

View File

@@ -0,0 +1,11 @@
### Steps to reproduce
What did you do?
### Expected behavior
Tell us what should happen
### Actual behavior
Tell us what happens instead
### System Configuration
Which O.S. and PHP version are you using?

View File

@@ -0,0 +1,13 @@
### Overview
This pull request:
- [ ] Fixes a bug
- [ ] Adds a feature
- [ ] Breaks backwards compatibility
- [ ] Needs tests
### Summary
Short overview of what changed.
### Description
Any additional information.

View File

@@ -0,0 +1,9 @@
vendor/
.idea/
build
site/
robotheme/
tests/_log/*
tests/_helpers/_generated/*
composer.phar
robo.phar

View File

@@ -0,0 +1,3 @@
filter:
excluded_paths:
- "RoboFile.php"

View File

@@ -0,0 +1,47 @@
language: php
branches:
# Only test the master branch and SemVer tags.
only:
- master
- /^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+.*$/
matrix:
include:
- php: 7.0
env: dependencies=highest
- php: 5.6
- php: 5.5
- php: 5.5
env: dependencies=lowest
sudo: false
cache:
directories:
- $HOME/.composer/cache
before_script:
- if [ -z "$dependencies" ]; then composer install --prefer-dist; fi;
- if [ "$dependencies" = "lowest" ]; then composer update --prefer-dist --prefer-lowest -n; fi;
- if [ "$dependencies" = "highest" ]; then composer update --prefer-dist -n; fi;
script: "./robo test --coverage"
after_success:
- travis_retry php vendor/bin/coveralls -v
# Prior to a deploy, build a fresh robo.phar
before_deploy:
- ./robo phar:build
# Deploy instructions set up via `travis setup releases` per
# https://docs.travis-ci.com/user/deployment/releases
deploy:
provider: releases
api_key:
secure: EdmB1nW5gj5nggYfmHv20enSgvRIAl1PIWV5GKmkxAJwuummh3UqdI7z0ecTGdw2IBgJx9lizNvqhcWjXbpNhE9VaaT1sHFCKv4Zust6sLb9bneK3oLRdJk2wemfrrZQpdH900zA0o7b3CHVth8UhkrCB4FXVDjUW13K061EXG8=
file: robo.phar
skip_cleanup: true
on:
tags: true

View File

@@ -0,0 +1,240 @@
# Changelog
#### 1.0.4
* Updated to latest changes in `master` branch. Phar and tag issues.
#### 1.0.0
* [Collection] Add tasks to a collection, and implement them as a group with rollback
* Tasks may be added to a collection via `$collection->add($task);`
* `$collection->run();` runs all tasks in the collection
* `$collection->addCode(function () { ... } );` to add arbitrary code to a collection
* `$collection->progressMessage(...);` will log a message
* `$collection->rollback($task);` and `$collection->rollbackCode($callable);` add a rollback function to clean up after a failed task
* `$collection->completion($task);` and `$collection->completionCode($callable);` add a function that is called once the collection completes or rolls back.
* `$collection->before();` and `$collection->after();` can be used to add a task or function that runs before or after (respectively) the specified named task. To use this feature, tasks must be given names via an optional `$taskName` parameter when they are added.
* Collections may be added to collections, if desired.
* [CollectionBuilder] Create tasks and add them to a collection in a single operation.
* `$this->collectionBuilder()->taskExec('pwd')->taskExec('ls')->run()`
* Add output formatters
* If a Robo command returns a string, or a `Result` object with a `$message`, then it will be printed
* Commands may be annotated to describe output formats that may be used
* Structured arrays returned from function results may be converted into different formats, such as a table, yml, json, etc.
* Tasks must `use TaskIO` for output methods. It is no longer possible to `use IO` from a task. For direct access use `Robo::output()` (not recommended).
* Use league/container to do Dependency Injection
* *Breaking* Tasks' loadTasks traits must use `$this->task(TaskClass::class);` instead of `new TaskClass();`
* *Breaking* Tasks that use other tasks must use `$this->collectionBuilder()->taskName();` instead of `new TaskClass();` when creating task objects to call. Implement `Robo\Contract\BuilderAwareInterface` and use `Robo\Contract\BuilderAwareTrait` to add the `collectionBuilder()` method to your task class.
* *Breaking* The `arg()`, `args()` and `option()` methods in CommandArguments now escape the values passed in to them. There is now a `rawArg()` method if you need to add just one argument that has already been escaped.
* *Breaking* taskWrite is now called taskWriteToFile
* [Extract] task added
* [Pack] task added
* [TmpDir], [WorkDir] and [TmpFile] tasks added
* Support Robo scripts that allows scripts starting with `#!/usr/bin/env robo` to define multiple robo commands. Use `#!/usr/bin/env robo run` to define a single robo command implemented by the `run()` method.
* Provide ProgresIndicatorAwareInterface and ProgressIndicatorAwareTrait that make it easy to add progress indicators to tasks
* Add --simulate mode that causes tasks to print what they would have done, but make no changes
* Add `robo generate:task` code-generator to make new stack-based task wrappers around existing classes
* Add `robo sniff` by @dustinleblanc. Runs the PHP code sniffer followed by the code beautifier, if needed.
* Implement ArrayInterface for Result class, so result data may be accessed like an array
* Defer execution of operations in taskWriteToFile until the run() method
* Add Write::textIfMatch() for taskWriteToFile
* ResourceExistenceChecker used for error checking in DeleteDir, CopyDir, CleanDir and Concat tasks by @burzum
* Provide ResultData base class for Result; ResultData may be used in instances where a specific `$task` instance is not available (e.g. in a Robo command)
* ArgvInput now available via $this->getInput() in RoboFile by Thomas Spigel
* Add optional message to git tag task by Tim Tegeler
* Rename 'FileSystem' to 'Filesystem' wherever it occurs.
* Current directory is changed with `chdir` only if specified via the `--load-from` option (RC2)
#### 0.6.0
* Added `--load-from` option to make Robo start RoboFiles from other directories. Use it like `robo --load-from /path/to/where/RobFile/located`.
* Robo will not ask to create RoboFile if it does not exist, `init` command should be used.
* [ImageMinify] task added by @gabor-udvari
* [OpenBrowser] task added by @oscarotero
* [FlattenDir] task added by @gabor-udvari
* Robo Runner can easily extended for custom runner by passing RoboClass and RoboFile parameters to constructor. By @rdeutz See #232
#### 0.5.4
* [WriteToFile] Fixed by @gabor-udvari: always writing to file regardless whether any changes were made or not. This can bring the taskrunner into an inifinite loop if a replaced file is being watched.
* [Scss] task added, requires `leafo/scssphp` library to compile by @gabor-udvari
* [PhpSpec] TAP formatter added by @orls
* [Less] Added ability to set import dir for less compilers by @MAXakaWIZARD
* [Less] fixed passing closure as compiler by @pr0nbaer
* [Sass] task added by *2015-08-31*
#### 0.5.3
* [Rsync] Ability to use remote shell with identity file by @Mihailoff
* [Less] Task added by @burzum
* [PHPUnit] allow to test specific files with `files` parameter by @burzum.
* [GitStack] `tag` added by @SebSept
* [Concat] Fixing concat, it breaks some files if there is no new line. @burzum *2015-03-03-13*
* [Minify] BC fix to support Jsqueeze 1.x and 2.x @burzum *2015-03-12*
* [PHPUnit] Replace log-xml with log-junit @vkunz *2015-03-06*
* [Minify] Making it possible to pass options to the JS minification @burzum *2015-03-05*
* [CopyDir] Create destination recursively @boedah *2015-02-28*
#### 0.5.2
* [Phar] do not compress phar if more than 1000 files included (causes internal PHP error) *2015-02-24*
* _copyDir and _mirrorDir shortcuts fixed by @boedah *2015-02-24*
* [File\Write] methods replace() and regexReplace() added by @asterixcapri *2015-02-24*
* [Codecept] Allow to set custom name of coverage file raw name by @raistlin *2015-02-24*
* [Ssh] Added property `remoteDir` by @boedah *2015-02-24*
* [PhpServer] fixed passing arguments to server *2015-02-24*
#### 0.5.1
* [Exec] fixed execution of background jobs, processes persist till the end of PHP script *2015-01-27*
* [Ssh] Fixed SSH task by @Butochnikov *2015-01-27*
* [CopyDir] fixed shortcut usage by @boedah *2015-01-27*
* Added default value options for Configuration trait by @TamasBarta *2015-01-27*
#### 0.5.0
Refactored core
* All traits moved to `Robo\Common` namespace
* Interfaces moved to `Robo\Contract` namespace
* All task extend `Robo\Task\BaseTask` to use common IO.
* All classes follow PSR-4 standard
* Tasks are loaded into RoboFile with `loadTasks` trait
* One-line tasks are available as shortcuts loaded by `loadShortucts` and used like `$this->_exec('ls')`
* Robo runner is less coupled. Output can be set by `\Robo\Config::setOutput`, `RoboFile` can be changed to any provided class.
* Tasks can be used outside of Robo runner (inside a project)
* Timer for long-running tasks added
* Tasks can be globally configured (WIP) via `Robo\Config` class.
* Updated to Symfony >= 2.5
* IO methods added `askHidden`, `askDefault`, `confirm`
* TaskIO methods added `printTaskError`, `printTaskSuccess` with different formatting.
* [Docker] Tasks added
* [Gulp] Task added by @schorsch3000
#### 0.4.7
* [Minify] Task added by @Rarst. Requires additional dependencies installed *2014-12-26*
* [Help command is populated from annotation](https://github.com/consolidation-org/Robo/pull/71) by @jonsa *2014-12-26*
* Allow empty values as defaults to optional options by @jonsa *2014-12-26*
* `PHP_WINDOWS_VERSION_BUILD` constant is used to check for Windows in tasks by @boedah *2014-12-26*
* [Copy][EmptyDir] Fixed infinite loop by @boedah *2014-12-26*
* [ApiGen] Task added by @drobert *2014-12-26*
* [FileSystem] Equalized `copy` and `chmod` argument to defaults by @Rarst (BC break) *2014-12-26*
* [FileSystem] Added missing umask argument to chmod() method of FileSystemStack by @Rarst
* [SemVer] Fixed file read and exit code
* [Codeception] fixed codeception coverageHtml option by @gunfrank *2014-12-26*
* [phpspec] Task added by @SebSept *2014-12-26*
* Shortcut options: if option name is like foo|f, assign f as shortcut by @jschnare *2014-12-26*
* [Rsync] Shell escape rsync exclude pattern by @boedah. Fixes #77 (BC break) *2014-12-26*
* [Npm] Task added by @AAlakkad *2014-12-26*
#### 0.4.6
* [Exec] Output from buffer is not spoiled by special chars *2014-10-17*
* [PHPUnit] detect PHPUnit on Windows or when is globally installed with Composer *2014-10-17*
* Output: added methods askDefault and confirm by @bkawakami *2014-10-17*
* [Svn] Task added by @anvi *2014-08-13*
* [Stack] added dir and printed options *2014-08-12*
* [ExecTask] now uses Executable trait with printed, dir, arg, option methods added *2014-08-12*
#### 0.4.5
* [Watch] bugfix: Watch only tracks last file if given array of files #46 *2014-08-05*
* All executable tasks can configure working directory with `dir` option
* If no value for an option is provided, assume it's a VALUE_NONE option. #47 by @pfaocle
* [Changelog] changed style *2014-06-27*
* [GenMarkDown] fixed formatting annotations *2014-06-27*
#### 0.4.4 06/05/2014
* Output can be disabled in all executable tasks by ->printed(false)
* disabled timeouts by default in ParallelExec
* better descriptions for Result output
* changed ParallelTask to display failed process in list
* Changed Output to be stored globally in Robo\Runner class
* Added **SshTask** by @boedah
* Added **RsyncTask** by @boedah
* false option added to proceess* callbacks in GenMarkDownTask to skip processing
#### 0.4.3 05/21/2014
* added `SemVer` task by **@jadb**
* `yell` output method added
* task `FileSystemStack` added
* `MirrorDirTask` added by **@devster**
* switched to Symfony Filesystem component
* options can be used to commands
* array arguments can be used in commands
#### 0.4.2 05/09/2014
* ask can now hide answers
* Trait Executable added to provide standard way for passing arguments and options
* added ComposerDumpAutoload task by **@pmcjury**
* added FileSystem task by **@jadb**
* added CommonStack metatsk to have similar interface for all stacked tasks by **@jadb**
* arguments and options can be passed into variable and used in exec task
* passing options into commands
#### 0.4.1 05/05/2014
* [BC] `taskGit` task renamed to `taskGitStack` for compatibility
* unit and functional tests added
* all command tasks now use Symfony\Process to execute them
* enabled Bower and Concat tasks
* added `printed` param to Exec task
* codeception `suite` method now returns `$this`
* timeout options added to Exec task
#### 0.4.0 04/27/2014
* Codeception task added
* PHPUnit task improved
* Bower task added by @jadb
* ParallelExec task added
* Symfony Process component used for execution
* Task descriptions taken from first line of annotations
* `CommandInterface` added to use tasks as parameters
#### 0.3.3 02/25/2014
* PHPUnit basic task
* fixed doc generation
#### 0.3.5 02/21/2014
* changed generated init template
#### 0.3.4 02/21/2014
* [PackPhar] ->executable command will remove hashbang when generated stub file
* [Git][Exec] stopOnFail option for Git and Exec stack
* [ExecStack] shortcut for executing bash commands in stack
#### 0.3.2 02/20/2014
* release process now includes phar
* phar executable method added
* git checkout added
* phar pack created
#### 0.3.0 02/11/2014
* Dynamic configuration via magic methods
* added WriteToFile task
* Result class for managing exit codes and error messages
#### 0.2.0 01/29/2014
* Merged Tasks and Traits to same file
* Added Watcher task
* Added GitHubRelease task
* Added Changelog task
* Added ReplaceInFile task

View File

@@ -0,0 +1,15 @@
# Contributing to Robo
Thank you for your interest in contributing to Robo! Here are some of the guidelines you should follow to make the most of your efforts:
## Code Style Guidelines
Robo adheres to the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/) for PHP code. An `.editorconfig` file is included with the repository to help you get up and running quickly. Most modern editors support this standard, but if yours does not or you would like to configure your editor manually, follow the guidelines in the document linked above.
You can run the PHP Codesniffer on your work using a convenient command built into this project's own `RoboFile.php`:
```
robo sniff src/Foo.php --autofix
```
The above will run the PHP Codesniffer on the `src/Foo.php` file and automatically correct variances from the PSR-2 standard. Please ensure all contributions are compliant _before_ submitting a pull request.

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Codegyre Developers Team, Consolidation Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,174 @@
# RoboTask
**Modern and simple PHP task runner** inspired by Gulp and Rake aimed to automate common tasks:
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/consolidation-org/Robo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Latest Stable Version](https://poser.pugx.org/consolidation/robo/v/stable.png)](https://packagist.org/packages/consolidation/robo)
[![Latest Unstable Version](https://poser.pugx.org/consolidation/robo/v/unstable.png)](https://packagist.org/packages/consolidation/robo)
[![Total Downloads](https://poser.pugx.org/consolidation/robo/downloads.png)](https://packagist.org/packages/consolidation/robo)
[![PHP 7 ready](http://php7ready.timesplinter.ch/consolidation/Robo/badge.svg)](https://travis-ci.org/consolidation/Robo)
[![License](https://poser.pugx.org/consolidation/robo/license.png)](https://www.versioneye.com/user/projects/57c4a6fe968d64004d97620a?child=57c4a6fe968d64004d97620a#tab-licenses)
[![Build Status](https://travis-ci.org/consolidation/Robo.svg?branch=master)](https://travis-ci.org/consolidation/Robo)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/consolidation/Robo/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/consolidation/Robo/?branch=master)
[![Dependency Status](https://www.versioneye.com/user/projects/57c4a6fe968d64004d97620a/badge.svg?style=flat-square)](https://www.versioneye.com/user/projects/57c4a6fe968d64004d97620a)
* writing cross-platform scripts
* processing assets (less, sass, minification)
* running tests
* executing daemons (and workers)
* watching filesystem changes
* deployment with sftp/ssh/docker
## Installing
### Phar
[Download robo.phar >](http://robo.li/robo.phar)
```
wget http://robo.li/robo.phar
```
To install globally put `robo.phar` in `/usr/bin`.
```
chmod +x robo.phar && sudo mv robo.phar /usr/bin/robo
```
Now you can use it just like `robo`.
### Composer
* Run `composer require consolidation/robo:~1`
* Use `vendor/bin/robo` to execute Robo tasks.
## Usage
All tasks are defined as **public methods** in `RoboFile.php`. It can be created by running `robo`.
All protected methods in traits that start with `task` prefix are tasks and can be configured and executed in your tasks.
## Examples
The best way to learn Robo by example is to take a look into [its own RoboFile](https://github.com/consolidation-org/Robo/blob/master/RoboFile.php)
or [RoboFile of Codeception project](https://github.com/Codeception/Codeception/blob/master/RoboFile.php). There are also some basic example commands in examples/RoboFile.php.
Here are some snippets from them:
---
Run acceptance test with local server and selenium server started.
``` php
<?php
class RoboFile extends \Robo\Tasks
{
function testAcceptance($seleniumPath = '~/selenium-server-standalone-2.39.0.jar')
{
// launches PHP server on port 8000 for web dir
// server will be executed in background and stopped in the end
$this->taskServer(8000)
->background()
->dir('web')
->run();
// running Selenium server in background
$this->taskExec('java -jar ' . $seleniumPath)
->background()
->run();
// loading Symfony Command and running with passed argument
$this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
->arg('suite','acceptance')
->run();
}
}
```
If you execute `robo` you will see this task added to list of available task with name: `test:acceptance`.
To execute it you shoud run `robo test:acceptance`. You may change path to selenium server by passing new path as a argument:
```
robo test:acceptance "C:\Downloads\selenium.jar"
```
Using `watch` task so you can use it for running tests or building assets.
``` php
<?php
class RoboFile extends \Robo\Tasks {
function watchComposer()
{
// when composer.json changes `composer update` will be executed
$this->taskWatch()->monitor('composer.json', function() {
$this->taskComposerUpdate()->run();
})->run();
}
}
```
---
Cleaning logs and cache
``` php
<?php
class RoboFile extends \Robo\Tasks
{
public function clean()
{
$this->taskCleanDir([
'app/cache',
'app/logs'
])->run();
$this->taskDeleteDir([
'web/assets/tmp_uploads',
])->run();
}
}
```
This task cleans `app/cache` and `app/logs` dirs (ignoring .gitignore and .gitkeep files)
Can be executed by running:
```
robo clean
```
----
Creating Phar archive
``` php
function buildPhar()
{
$files = Finder::create()->ignoreVCS(true)->files()->name('*.php')->in(__DIR__);
$packer = $this->taskPackPhar('robo.phar');
foreach ($files as $file) {
$packer->addFile($file->getRelativePathname(), $file->getRealPath());
}
$packer->addFile('robo','robo')
->executable('robo')
->run();
}
```
---
## We need more tasks!
Create your own tasks and send them as Pull Requests or create packages prefixed with `robo-` on Packagist.
## Credits
Follow [@robo_php](http://twitter.com/robo_php) for updates.
Brought to you by [Consolidation Team](https://github.com/orgs/consolidation/people) and our [awesome contributors](https://github.com/consolidation/Robo/graphs/contributors).
## License
[MIT](https://github.com/consolidation/Robo/blob/master/LICENSE)

View File

@@ -0,0 +1,377 @@
<?php
use Symfony\Component\Finder\Finder;
use Robo\Result;
use Robo\Collection\CollectionBuilder;
class RoboFile extends \Robo\Tasks
{
/**
* Run the Robo unit tests.
*/
public function test(array $args, $options =
[
'coverage-html' => false,
'coverage' => false
])
{
$taskCodecept = $this->taskCodecept()
->args($args);
if ($options['coverage']) {
$taskCodecept->coverageXml('../../build/logs/clover.xml');
}
if ($options['coverage-html']) {
$taskCodecept->coverageHtml('../../build/logs/coverage');
}
return $taskCodecept->run();
}
/**
* Code sniffer.
*
* Run the PHP Codesniffer on a file or directory.
*
* @param string $file
* A file or directory to analyze.
* @option $autofix Whether to run the automatic fixer or not.
* @option $strict Show warnings as well as errors.
* Default is to show only errors.
*/
public function sniff(
$file = 'src/',
$options = [
'autofix' => false,
'strict' => false,
]
) {
$strict = $options['strict'] ? '' : '-n';
$result = $this->taskExec("./vendor/bin/phpcs --standard=PSR2 {$strict} {$file}")->run();
if (!$result->wasSuccessful()) {
if (!$options['autofix']) {
$options['autofix'] = $this->confirm('Would you like to run phpcbf to fix the reported errors?');
}
if ($options['autofix']) {
$result = $this->taskExec("./vendor/bin/phpcbf --standard=PSR2 {$file}")->run();
}
}
return $result;
}
/**
* Generate a new Robo task that wraps an existing utility class.
*
* @param $className The name of the existing utility class to wrap.
* @param $wrapperClassName The name of the wrapper class to create. Optional.
* @usage generate:task 'Symfony\Component\Filesystem\Filesystem' FilesystemStack
*/
public function generateTask($className, $wrapperClassName = "")
{
return $this->taskGenTask($className, $wrapperClassName)->run();
}
/**
* Release Robo.
*/
public function release($opts = ['beta' => false])
{
$this->yell("Releasing Robo");
$stable = true;
if ($opts['beta']) {
$stable = false;
$this->say('non-stable release');
}
$this->docs();
$this->taskGitStack()
->add('-A')
->commit("auto-update")
->pull()
->push()
->run();
if ($stable) $this->pharPublish();
$this->publish();
$this->taskGitStack()
->tag(\Robo\Robo::VERSION)
->push('origin master --tags')
->run();
if ($stable) $this->versionBump();
}
/**
* Update changelog.
*
* Add an entry to the Robo CHANGELOG.md file.
*
* @param string $addition The text to add to the change log.
*/
public function changed($addition)
{
return $this->taskChangelog()
->version(\Robo\Robo::VERSION)
->change($addition)
->run();
}
/**
* Update the version of Robo.
*
* @param string $version The new verison for Robo.
* Defaults to the next minor (bugfix) version after the current relelase.
*/
public function versionBump($version = '')
{
if (empty($version)) {
$versionParts = explode('.', \Robo\Robo::VERSION);
$versionParts[count($versionParts)-1]++;
$version = implode('.', $versionParts);
}
return $this->taskReplaceInFile(__DIR__.'/src/Robo.php')
->from("VERSION = '".\Robo\Robo::VERSION."'")
->to("VERSION = '".$version."'")
->run();
}
/**
* Generate the Robo documentation files.
*/
public function docs()
{
$collection = $this->collectionBuilder();
$collection->progressMessage('Generate documentation from source code.');
$files = Finder::create()->files()->name('*.php')->in('src/Task');
$docs = [];
foreach ($files as $file) {
if ($file->getFileName() == 'loadTasks.php') {
continue;
}
if ($file->getFileName() == 'loadShortcuts.php') {
continue;
}
$ns = $file->getRelativePath();
if (!$ns) {
continue;
}
$class = basename(substr($file, 0, -4));
class_exists($class = "Robo\\Task\\$ns\\$class");
$docs[$ns][] = $class;
}
ksort($docs);
foreach ($docs as $ns => $tasks) {
$taskGenerator = $collection->taskGenDoc("docs/tasks/$ns.md");
$taskGenerator->filterClasses(function (\ReflectionClass $r) {
return !($r->isAbstract() || $r->isTrait()) && $r->implementsInterface('Robo\Contract\TaskInterface');
})->prepend("# $ns Tasks");
sort($tasks);
foreach ($tasks as $class) {
$taskGenerator->docClass($class);
}
$taskGenerator->filterMethods(
function (\ReflectionMethod $m) {
if ($m->isConstructor() || $m->isDestructor() || $m->isStatic()) {
return false;
}
$undocumentedMethods =
[
'',
'run',
'__call',
'inflect',
'injectDependencies',
'getCommand',
'getPrinted',
'getConfig',
'setConfig',
'logger',
'setLogger',
'setProgressIndicator',
'progressIndicatorSteps',
'setBuilder',
'getBuilder',
'collectionBuilder',
];
return !in_array($m->name, $undocumentedMethods) && $m->isPublic(); // methods are not documented
}
)->processClassSignature(
function ($c) {
return "## " . preg_replace('~Task$~', '', $c->getShortName()) . "\n";
}
)->processClassDocBlock(
function (\ReflectionClass $c, $doc) {
$doc = preg_replace('~@method .*?(.*?)\)~', '* `$1)` ', $doc);
$doc = str_replace('\\'.$c->name, '', $doc);
return $doc;
}
)->processMethodSignature(
function (\ReflectionMethod $m, $text) {
return str_replace('#### *public* ', '* `', $text) . '`';
}
)->processMethodDocBlock(
function (\ReflectionMethod $m, $text) {
return $text ? ' ' . trim(strtok($text, "\n"), "\n") : '';
}
);
}
$collection->progressMessage('Documentation generation complete.');
return $collection->run();
}
/**
* Publish Robo.
*
* Builds a site in gh-pages branch. Uses mkdocs
*/
public function publish()
{
$current_branch = exec('git rev-parse --abbrev-ref HEAD');
return $this->collectionBuilder()
->taskGitStack()
->checkout('site')
->merge('master')
->completion($this->taskGitStack()->checkout($current_branch))
->taskFilesystemStack()
->copy('CHANGELOG.md', 'docs/changelog.md')
->completion($this->taskFilesystemStack()->remove('docs/changelog.md'))
->taskExec('mkdocs gh-deploy')
->run();
}
/**
* Build the Robo phar executable.
*/
public function pharBuild()
{
// Create a collection builder to hold the temporary
// directory until the pack phar task runs.
$collection = $this->collectionBuilder();
$workDir = $collection->tmpDir();
$roboBuildDir = "$workDir/robo";
// Before we run `composer install`, we will remove the dev
// dependencies that we only use in the unit tests. Any dev dependency
// that is in the 'suggested' section is used by a core task;
// we will include all of those in the phar.
$devProjectsToRemove = $this->devDependenciesToRemoveFromPhar();
// We need to create our work dir and run `composer install`
// before we prepare the pack phar task, so create a separate
// collection builder to do this step in.
$prepTasks = $this->collectionBuilder();
$preparationResult = $prepTasks
->taskFilesystemStack()
->mkdir($workDir)
->taskRsync()
->fromPath(
[
__DIR__ . '/composer.json',
__DIR__ . '/scripts',
__DIR__ . '/src',
__DIR__ . '/data'
]
)
->toPath($roboBuildDir)
->recursive()
->progress()
->stats()
->taskComposerRemove()
->dir($roboBuildDir)
->dev()
->noUpdate()
->args($devProjectsToRemove)
->taskComposerInstall()
->dir($roboBuildDir)
->printed(false)
->run();
// Exit if the preparation step failed
if (!$preparationResult->wasSuccessful()) {
return $preparationResult;
}
// Decide which files we're going to pack
$files = Finder::create()->ignoreVCS(true)
->files()
->name('*.php')
->name('*.exe') // for 1symfony/console/Resources/bin/hiddeninput.exe
->name('GeneratedWrapper.tmpl')
->path('src')
->path('vendor')
->notPath('docs')
->notPath('/vendor\/.*\/[Tt]est/')
->in(is_dir($roboBuildDir) ? $roboBuildDir : __DIR__);
// Build the phar
return $collection
->taskPackPhar('robo.phar')
->addFiles($files)
->addFile('robo', 'robo')
->executable('robo')
->taskFilesystemStack()
->chmod('robo.phar', 0777)
->run();
}
/**
* The phar:build command removes the project requirements from the
* 'require-dev' section that are not in the 'suggest' section.
*
* @return array
*/
protected function devDependenciesToRemoveFromPhar()
{
$composerInfo = (array) json_decode(file_get_contents(__DIR__ . '/composer.json'));
$devDependencies = array_keys((array)$composerInfo['require-dev']);
$suggestedProjects = array_keys((array)$composerInfo['suggest']);
return array_diff($devDependencies, $suggestedProjects);
}
/**
* Install Robo phar.
*
* Installs the Robo phar executable in /usr/bin. Uses 'sudo'.
*/
public function pharInstall()
{
return $this->taskExec('sudo cp')
->arg('robo.phar')
->arg('/usr/bin/robo')
->run();
}
/**
* Publish Robo phar.
*
* Commits the phar executable to Robo's GitHub pages site.
*/
public function pharPublish()
{
$this->pharBuild();
$this->collectionBuilder()
->taskFilesystemStack()
->rename('robo.phar', 'robo-release.phar')
->taskGitStack()
->checkout('site')
->pull('origin site')
->taskFilesystemStack()
->remove('robotheme/robo.phar')
->rename('robo-release.phar', 'robotheme/robo.phar')
->taskGitStack()
->add('robotheme/robo.phar')
->commit('Update robo.phar to ' . \Robo\Robo::VERSION)
->push('origin site')
->checkout('master')
->run();
}
}

View File

@@ -0,0 +1,21 @@
actor: Guy
paths:
tests: tests
log: tests/_log
data: tests/_data
helpers: tests/_helpers
settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
modules:
config:
Db:
dsn: ''
user: ''
password: ''
dump: tests/_data/dump.sql
coverage:
enabled: true
include:
- src/*

View File

@@ -0,0 +1,70 @@
{
"name": "consolidation/robo",
"description": "Modern task runner",
"license": "MIT",
"authors": [
{
"name": "Davert",
"email": "davert.php@resend.cc"
}
],
"autoload":{
"classmap": [
"scripts/composer/ScriptHandler.php"
],
"psr-4":{
"Robo\\":"src"
}
},
"autoload-dev":{
"psr-4":{
"Robo\\":"tests/src"
}
},
"bin":["robo"],
"require": {
"php": ">=5.5.0",
"league/container": "^2.2",
"consolidation/log": "~1",
"consolidation/annotated-command": "^2.0.1",
"consolidation/output-formatters": "^2.1.2|~3",
"symfony/finder": "~2.5|~3.0",
"symfony/console": "~2.8|~3.0",
"symfony/process": "~2.5|~3.0",
"symfony/filesystem": "~2.5|~3.0",
"symfony/event-dispatcher": "~2.5|~3.0"
},
"require-dev": {
"patchwork/jsqueeze": "~2",
"henrikbjorn/lurker": "~1",
"natxet/CssMin": "~3",
"pear/archive_tar": "^1.4.2",
"codeception/base": "^2.2.6",
"codeception/verify": "^0.3.2",
"codeception/aspect-mock": "~1",
"satooshi/php-coveralls": "~1",
"squizlabs/php_codesniffer": "~2",
"phpunit/php-code-coverage": "~2|~4"
},
"scripts": {
"pre-install-cmd": [
"Robo\\composer\\ScriptHandler::checkDependencies"
],
"cs": "/robo sniff",
"test": "./robo test"
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"suggest": {
"pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively.",
"henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch",
"patchwork/jsqueeze": "For minifying JS files in taskMinify",
"natxet/CssMin": "For minifying JS files in taskMinify"
},
"replace": {
"codegyre/robo": "< 1.0"
}
}

3518
lib/composer/vendor/consolidation/robo/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
<?php
namespace Robo\Task\{delegateNamespace};
use Robo\Result;
use Robo\Task\StackBasedTask;
use {delegateNamespace}\{delegate};
/**
* Wrapper for {delegate} Component.
* Comands are executed in stack and can be stopped on first fail with `stopOnFail` option.
*
* ``` php
* <?php
* $this->task{wrapperClassName}()
* ...
* ->run();
*
* // one line
* ...
*
* ?>
* ```
*
{methodList}
*/
class {wrapperClassName} extends StackBasedTask
{
protected $delegate;
public function __construct()
{
$this->delegate = new {delegate}();
}
protected function getDelegate()
{
return $this->delegate;
}{immediateMethods}{methodImplementations}
}

View File

@@ -0,0 +1,250 @@
# Collection Builders
Robo provides task collections as a means of making error detection and recovery easier. When Robo tasks are added to a collection, their execution is deferred until the `$collection->run()` method is called. If one of the tasks fail, then the operation will be aborted; rollback tasks may also be defined to restore the system to its original condition.
When using collections, a Robo script will go through three phases:
1. Determine which tasks will need to be run, and create a task builder.
- Assign values to variables.
- Do not alter the state of the system.
2. Create the necessary tasks via the task builder.
- Use variables calculated in the first phase in task parameters.
3. Run the tasks via the `run()` method.
- Check and report errors once after `run()` returns.
Following this pattern will keep your code linear and easy to understand.
## Collections API
Collections are made up of a combination of tasks and/or `callable` functions / method pointers, such as:
- A task (implements TaskInterface)
- A function name (string)
- A closure (inline function)
- A method reference (array with object and method name)
Examples of adding different kinds of tasks to a collection are provided below.
### TaskInterface Objects
```php
<?php
$collection->add(
$this->taskExec('ls')
);
?>
```
### Functions
```php
<?php
$collection->addCode('mytaskfunction');
?>
```
### Closures
```php
<?php
$collection->addCode(
function() use ($work)
{
// do something with $work
});
?>
```
### Methods
```php
<?php
$collection->addCode([$myobject, 'mymethod']);
?>
```
## Using a Collection Builder
To manage a collection of tasks, a collection builder. Collection builders allow tasks to be created via chained methods. All of the tasks created by the same builder are added to a collection; when the `run()` method is called, all of the tasks in the collection run.
The 'publish' command from Robo's own RoboFile is shown below. It uses a collection builder to run some git and filesystem operations. The "completion" tasks are run after all other tasks complete, or during rollback processing when an operation fails.
``` php
<?php
class RoboFile extends \Robo\Tasks
{
public function publish()
{
$current_branch = exec('git rev-parse --abbrev-ref HEAD');
$collection = $this->collectionBuilder();
$collection->taskGitStack()
->checkout('site')
->merge('master')
->completion($this->taskGitStack()->checkout($current_branch))
->taskFilesystemStack()
->copy('CHANGELOG.md', 'docs/changelog.md')
->completion($this->taskFilesystemStack()->remove('docs/changelog.md'))
->taskExec('mkdocs gh-deploy');
return $collection;
}
}
?>
```
The example above also adds a couple of tasks as "completions"; these are run when the collection completes execution, as explained below.
## Rollbacks and Completions
Robo also provides rollbacks and completions, special tasks that are eligible to run only if all of the tasks added to the collection before them succeed. The section below explains the circumstances under which these tasks will run.
### Completion Tasks
Completions run whenever their collection completes or fails, but only if all of the tasks that come before it succeed. An example of this is shown in the first example above. A filesystem stack task copies CHANDELOG.md to docs/changelog.md; after this task is added to the collection, another filesystem stack task is added as a completion to delete docs/changelog.md. This is done because docs/changelog.md is only intended to exist long enough to be used by the `mkdocs` task, which is added later.
### Rollback Tasks
In addition to completions, Robo also supports rollbacks. Rollback tasks can be used to clean up after failures, so the state of the system does not change when execution is interrupted by an error. A rollback task is executed if all of the tasks that come before it succeed, and at least one of the tasks that come after it fails. If all tasks succeed, then no rollback tasks are executed.
### Rollback and Completion Methods
Any task may also implement \Robo\Contract\RollbackInterface; if this is done, then its `rollback()` method will be called if the task is `run()` on a collection that later fails.
Use `addAsCompletion($collection)` in place of `addAsRollback($collection)`, or implement \Robo\Contract\CompletionInterface. Completions otherwise work exactly like rollbacks.
## Temporary Objects
Since the concept of temporary objects that are cleaned up on failure is a common pattern, Robo provides built-in support for them. Temporary directories and files are provided out of the box; other kinds of temporary objects can be easily created using the Temporary global collection.
### Temporary Directories
It is recommended that operations that perform multiple filesystem operations should, whenever possible, do most of their work in a temporary directory. Temporary directories are created by `$this->taskTmpDir()`, and are automatically be removed when the collection completes or rolls back. As an added convenience, the CollectionBuilder class has a `tmpDir()` method that creates a temporary directory via `taskTmpDir()`, and then returns the path to the temporary directory.
``` php
<?php
class RoboFile extends \Robo\Tasks
{
function myOperation()
{
$collection = $this->collectionBuilder();
// Create a temporary directory, and fetch its path.
$work = $collection->tmpDir();
$collection
->taskWriteToFile("$work/README.md")
->line('-----')
->line(date('Y-m-d').' Generated file: do not edit.')
->line('----');
// If all of the preceding tasks succeed, then rename the temporary
// directory to its final name.
$collection->taskFilesystemStack()
->rename($work, 'destination');
return $collection->run();
}
}
?>
```
In the previous example, the path to the temporary directory is stored in the variable `$work`, and is passed as needed to the parameters of the other tasks as they are added to the collection. After the task collection is run, the temporary directory will be automatically deleted. In the example above, the temporary directory is renamed by the last task in the collection. This allows the working directory to persist; the collection will still attempt to remove the working directory, but no errors will be thrown if it no longer exists in its original location. Following this pattern allows Robo scripts to easily and safely do work that cleans up after itself on failure, without introducing a lot of branching or additional error recovery code. This paradigm is common enough to warrant a shortcut method of accomplishing the same thing. The example below is identical to the one above, save for the fact that it uses the `workDir()` method instead of `tmpDir()`. `workDir()` renames the temporary directory to its final name if the collection completes; any directory that exists in the same location will be overwritten at that time, but will persist if the collection roles back.
``` php
<?php
class RoboFile extends \Robo\Tasks
{
function myOperation()
{
$collection = $this->collectionBuilder();
// Create a temporary directory, and fetch its path.
// If all of the tasks succeed, then rename the temporary directory
// to its final name.
$work = $collection->workDir('destination');
$collection
->taskWriteToFile("$work/README.md")
->line('-----')
->line(date('Y-m-d').' Generated file: do not edit.')
->line('----');
return $collection->run();
}
}
?>
```
Temporary directories may also be created via the shortcut `$this->_tmpDir();`. Temporary directories created in this way are deleted when the script terminates.
### Temporary Files
Robo also provides an API for creating temporary files. They may be created via `$this->taskTmpFile()`; they are used exactly like `$this->taskWrite()`, except they are given a random name on creation, and are deleted when their collection completes. If they are not added to a collection, then they are deleted when the script terminates.
### The Temporary Global Collection
Robo maintains a special collection called the Temporary global collection. This collection is used to keep track of temporary objects that are not part of any collection. For example, Robo temporary directories and temporary files are managed by the Temporary global collection. These temporary objects are cleaned up automatically when the script terminates.
It is easy to create your own temporary tasks that behave in the same way as the provided temporary directory and temporary file tasks. There are two steps required:
- Implement \Robo\Contract\CompletionInterface
- Wrap the task via Temporary::wrap()
For example, the implementation of taskTmpFile() looks like this:
``` php
<?php
protected function taskTmpFile($filename = 'tmp', $extension = '', $baseDir = '', $includeRandomPart = true)
{
return Temporary::wrap(new TmpFile($filename, $extension, $baseDir, $includeRandomPart));
}
?>
```
The `complete()` method of the task will be called once the Collection the temporary object is attached to finishes running. If the temporary is not added to a collection, then its `complete()` method will be called when the script terminates.
## Named Tasks
It is also possible to provide names for the tasks added to a collection. This has two primary benefits:
1. Any result data returned from a named task is stored in the Result object under the task name.
2. It is possible for other code to add more tasks before or after any named task.
This feature is useful if you have functions that create task collections, and return them as a function results. The original caller can then use the `$collection->before()` or `$collection->after()` to insert sequenced tasks into the set of operations to be performed. One reason this might be done would be to define a base set of operations to perform (e.g. in a deploy), and then apply modifications for other environments (e.g. dev or stage).
```php
<?php
$collection->addCode(
function() use ($work)
{
// do something with $work
},
"taskname");
?>
```
Given a collection with named tasks, it is possible to insert more tasks before or after a task of a given name.
```php
<?php
$collection->after("taskname",
function() use ($work)
{
// do something with $work after "taskname" executes, if it succeeds.
});
?>
```
```php
<?php
$collection->before("taskname",
function() use ($work)
{
// do something with $work before "taskname" executes.
});
?>
```
It is recommended that named tasks be avoided unless specifically needed.

View File

@@ -0,0 +1,191 @@
# Extending
Robo tasks can be added to your Robo application by using Composer to suppliment the set of built-in tasks that Robo provides by default. To find existing Robo task extensions, search in Packagist for projects of type [robo-tasks](https://packagist.org/search/?type=robo-tasks).
The convention used to add new tasks for use in your RoboFiles is to create a wrapper trait named `loadTasks` that instantiates the implementation class for each task. Each task method in the trait should start with the prefix `task`, and should use **chained method calls** for configuration. Task execution should be triggered by the method `run`.
To include additional tasks in your RoboFile, you must `use` the appropriate `loadTasks` in your RoboFile. See the section [Including Additional Tasks](#including-additional-tasks) below. To create your own Robo extension that provides tasks for use in RoboFiles, then you must write your own class that implements TaskInterface, and create a `loadTasks` trait for it as described in the section [Creating a Robo Extension](#creating-a-robo-extension).
## Including Additional Tasks
Additional tasks may be installed into projects that have included Robo via Composer. For example:
```
$ cd myproject
$ composer require boedah/robo-drush
```
If any of the tasks you include require external Composer projects themselves, then you must `composer require` these as well. See the `suggests` section of Robo's composer.json file for a list of some projects you might need to require.
Once the extension you wish to use has been added to your vendor directory, you may then include it from your RoboFile:
``` php
class RoboFile extends \Robo\Tasks
{
use \Boedah\Robo\Task\Drush\loadTasks;
public function test()
{
// ...
}
}
```
Once you have done this, all of the tasks defined in the extension you selected will be available for use in your commands.
Note that at the moment, it is not possible to extend Robo when using the robo.phar. This capability may be added in the future via [embedded composer](https://github.com/dflydev/dflydev-embedded-composer).
## Creating a Robo Extension
A Robo tasks extension is created by advertising a Composer package of type `robo-tasks` on [Packagist](https://packagist.org/). For an overview on how this is done, see the article [Creating your very own Composer Package](https://knpuniversity.com/screencast/question-answer-day/create-composer-package). Specific instructions for creating Robo task extensions are provided below.
### Create your composer.json File
Your composer.json file should look something like the example below:
```
{
"name": "boedah/robo-drush",
"description": "Drush CommandStack for Robo Task Runner",
"type": "robo-tasks",
"autoload": {
"psr-4": {
"Boedah\\Robo\\Task\\Drush\\": "src"
}
},
"require": {
"php": ">=5.5.0",
"consolidation/robo": "~1"
}
}
```
Customize the name and autoload paths as necessary, and add any additional required projects needed by the tasks that your extensions will provide. The type of your project should always be `robo-tasks`. Robo only supports php >= 5.5.0; you may require a higher version of php if necessary.
### Create the loadTasks.php Trait
It is recommended to place your trait-loading task in a `loadTasks` file in the same namespace as the task implementation.
```
namespace Boedah\Robo\Task\Drush;
use Robo\Container\SimpleServiceProvider;
trait loadTasks
{
/**
* @param string $pathToDrush
* @return DrushStack
*/
protected function taskDrushStack($pathToDrush = 'drush')
{
return $this->task(__FUNCTION__, $pathToDrush);
}
}
```
Note that the name of the service for a given task must start with the word "task", and must have the same name as the function used to call the task. `$this->task()` looks up the service by name; using the PHP built-in constant __FUNCTION__ for this parameter ensures that the names of these items remain in alignment.
### Task implementation
The implementation of each task class should extend \Robo\Task\BaseTask, or some class that extends the same, and should used chained initializer methods and defer all operations that alter the state of the system until its `run()` method. If you follow these patterns, then your task extensions will be usable via Robo collection builders, as explained in the [collections](collections.md) documentation.
There are many examples of task implementations in the Robo\Task namespace. A very basic task example is provided below. The namespace is `MyAssetTasks`, and the example task is `CompileAssets`. To customize to your purposes, choose an appropriate namespace, and then define as many tasks as you need.
``` php
<?php
namespace MyAssetTasks;
trait loadTasks
{
/**
* Example task to compile assets
*
* @param string $pathToCompileAssets
* @return \MyAssetTasks\CompileAssets
*/
protected function taskCompileAssets($path = null)
{
// Always construct your tasks with the `task()` task builder.
return $this->task(CompileAssets::class, $path);
}
}
class CompileAssets implements \Robo\Contract\TaskInterface
{
// configuration params
protected $path;
protected $to;
function __construct($path)
{
$this->path = $path;
}
function to($filename)
{
$this->to = $filename;
// must return $this
return $this;
}
// must implement Run
function run()
{
//....
}
}
?>
```
To use the tasks you define in a RoboFile, use its `loadTasks` trait as explained in the section [Including Additional Tasks](#including-additional-tasks), above.
### TaskIO
To allow tasks access IO, use the `Robo\Common\TaskIO` trait, or inherit your task class from `Robo\Task\BaseTask` (recommended).
Inside tasks you should print process details with `printTaskInfo`, `printTaskSuccess`, and `printTaskError`.
```
$this->printTaskInfo('Processing...');
```
The Task IO methods send all output through a PSR-3 logger. Tasks should use task IO exclusively; methods such as 'say' and 'ask' should reside in the command method. This allows tasks to be usable in any context that has a PSR-3 logger, including background or server processes where it is not possible to directly query the user.
### Tasks That Use Tasks
If one task implementation needs to use other tasks while it is running, it should do so via a `CollectionBuilder` object, as explained in the [Collections](collections.md) documentation.
To obtain access to a `CollectionBuilder`, a task should implement `BuilderAwareInterface` and use `BuilderAwareTrait`. It will then have access to a collection builder via the `$this->collectionBuilder()` method.
### Testing Extensions
If you wish to use the `task()` methods from your `loadTasks` trait in your unit tests, it is necessary to also use the Robo `TaskAccessor` trait, and define a `collectionBuilder()` method to provide a builder. Collection builders are used to initialize all Robo tasks. The easiest way to get a usable collection builder in your tests is to initialize Robo's default dependency injection container, and use it to request a new builder.
An example of how to do this in a PHPUnit test is shown below.
```
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
use Symfony\Component\Console\Output\NullOutput;
use Robo\TaskAccessor;
use Robo\Robo;
class DrushStackTest extends \PHPUnit_Framework_TestCase implements ContainerAwareInterface
{
use \Boedah\Robo\Task\Drush\loadTasks;
use TaskAccessor;
use ContainerAwareTrait;
// Set up the Robo container so that we can create tasks in our tests.
function setup()
{
$container = Robo::createDefaultContainer(null, new NullOutput());
$this->setContainer($container);
}
// Scaffold the collection builder
public function collectionBuilder()
{
$emptyRobofile = new \Robo\Tasks;
return $this->getContainer()->get('collectionBuilder', [$emptyRobofile]);
}
public function testYesIsAssumed()
{
$command = $this->taskDrushStack()
->drush('command')
->getCommand();
$this->assertEquals('drush command -y', $command);
}
}
```
To assert that the output of a command contains some value, use a `Symfony\Component\Console\Output\BufferedOutput` in place of null output when calling Robo::createDefaultContainer().

View File

@@ -0,0 +1,89 @@
# Robo as a Framework
There are multiple ways to use and package Robo scripts; a few of the alternatives are presented below.
## Creating a Standalone Phar with Robo
It is possible to create a standalone phar that is implemented with Robo; doing this does not require the RoboFile to be located in the current working directory, or any particular location within your project. To achieve this, first set up your project as shown in the section [Implementing Composer Scripts with Robo](getting-started.md#implementing-composer-scripts-with-robo). Use of the "scripts" section is optional.
Next, add an "autoload" section to your composer.json to provide a namespace for your Robo commands:
```
{
"name": "myorg/myproject",
"require": {
"consolidation/Robo": "~1"
},
"autoload":{
"psr-4":{
"MyProject\\":"src"
}
}
}
```
Create a new file for your Robo commands, e.g. `class RoboFile` in `namespace MyProject\Commands;` in the file `src\Commands\RoboFile.php`. Optionally, add more task libraries as described in the [extending](extending.md) document.
Create a startup script similar to the one below, and add it to the root of your project, or some other location of your choosing:
``` php
#!/usr/bin/env php
<?php
/**
* If we're running from phar load the phar autoload file.
*/
$pharPath = \Phar::running(true);
if ($pharPath) {
require_once "$pharPath/vendor/autoload.php";
} else {
if (file_exists(__DIR__.'/vendor/autoload.php')) {
require_once __DIR__.'/vendor/autoload.php';
} elseif (file_exists(__DIR__.'/../../autoload.php')) {
require_once __DIR__ . '/../../autoload.php';
}
}
$commandClasses = [ \MyProject\Commands\RoboFile::class ];
$statusCode = \Robo\Robo::run(
$_SERVER['argv'],
$commandClasses,
'MyAppName',
'0.0.0-alpha0'
);
exit($statusCode);
```
When using Robo as a framework, the Robo file should be included in the autoloader, as Robo does not include a `RoboFile.php` file when used in this mode. Instead, specify the class or classes to load as a parameter to the Robo::run() method. By default, all output will be sent to a Symfony ConsoleOutput() that Robo will create for you. If you would like to use some other OutputInterface to capture the output, it may be specified via an optional fifth parameter.
Use [box-project/box2](https://github.com/box-project/box2) to create a phar for your application. Note that if you use Robo's taskPackPhar to create your phar, then `\Phar::running()` will always return an empty string due to a bug in this phar builder. If you encounter any problems with this, then hardcode the path to your autoload file. See the [robo](https://github.com/consolidation-org/Robo/blob/master/robo) script for details.
## Using Multiple RoboFiles in a Standalone Application
It is possible to provide as many command classes as you wish to the Robo `Runner()` constructor. You might wish to separate your Robo command implementations into separate Robo files if you have a lot of commands, or if you wish to group similar commands together in the same source file. If you do this, you can simply add more class references to the `$commandClasses` variable shown above.
```
$commandClasses = [
\MyProject\Commands\BuildCommands::class,
\MyProject\Commands\DeployCommands::class
];
```
If your application has a large number of command files, or if it supports command extensions, then you might wish to use the Command Discovery class to locate your files. The `CommandFileDiscovery` class will use the Symfony Finder class to search for all filenames matching the provided search pattern. It will return a list of class names using the provided base namespace.
``` php
$discovery = new \Consolidation\AnnotatedCommand\CommandFileDiscovery();
$discovery->setSearchPattern('*Command.php');
$commandClasses = $discovery->discover('php/MyProject/Commands', '\MyProject\Commands');
```
Pass the resulting `$commandClasses` to the `Runner()` constructor as shown above. See the annotated-commands project for more information about the different options that the discovery command takes.
## Using Your Own Dependency Injection Container with Robo (Advanced)
It is also possible to completely replace the Robo application with your own. To do this, set up your project as described in the sections above, but replace the Robo runner with your own main event loop.
Create the Robo dependency injection container:
```
use League\Container\Container;
$input = new \Symfony\Component\Console\Input\ArgvInput($argv);
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
$conf = new \Robo\Config(); \\ or use your own subclass
$app = new \My\Application();
$container = \Robo\Robo::createDefaultContainer($input, $output, $app, $conf);
```
If you are using League\Container (recommended), then you may simply add and share your own classes to the same container. If you are using some other DI container, then you should use [delegate lookup](https://github.com/container-interop/fig-standards/blob/master/proposed/container.md#14-additional-feature-delegate-lookup) to combine them.

View File

@@ -0,0 +1,418 @@
# Getting Started
To begin you need to create a RoboFile. Just run `robo init` in your project directory:
```
cd myproject
robo init
```
Your project directory may start out empty; Robo will create a new `RoboFile.php` for you. There will be RoboFile class which extends `\Robo\Tasks`, which includes all bundled tasks of Robo.
``` php
<?php
class RoboFile extends \Robo\Tasks
{
}
?>
```
## Commands
All public methods of the RoboFile class will be treated as **commands**. You can run them from the CLI and pass arguments.
``` php
<?php
class RoboFile extends \Robo\Tasks
{
function hello($world)
{
$this->say("Hello, $world");
}
}
?>
```
When we run:
```
robo hello davert
➜ Hello, davert
```
Method names should be camelCased. In CLI `camelCased` method will be available as `camel:cased` command.
`longCamelCased` method will be transformed to `long:camel-cased` command.
**Note:** This assumes you have installed Robo by downloading the [robo.phar](http://robo.li/robo.phar) file and copied it to a directory in your $PATH. For example, `cp robo.phar ~/bin/robo`.
### Arguments
All method parameters without default values are treated as required arguments. In our example command `hello` requires one argument.
If you pass a default value to parameter the argument becomes optional:
``` php
<?php
function hello($world = 'world')
{
$this->say("Hello, $world");
}
?>
```
```
robo hello
➜ Hello, world
```
To accept multiple, variable arguments, define a parameter as an `array`; Robo will then pass all CLI arguments in this variable:
``` php
<?php
function hello(array $world)
{
$this->say("Hello, " . implode(', ', $world));
}
?>
```
```
robo hello davert jon bill bob
➜ Hello, davert, jon, bill, bob
```
### Options
To define command options you should define the last method parameter as an associative array where the keys define the option names and the values provide each option's default values:
``` php
<?php
function hello($opts = ['silent' => false])
{
if (!$opts['silent']) $this->say("Hello, world");
}
?>
```
```
robo hello
➜ Hello, world
robo hello --silent
```
A one-character shortcut can be specified for option:
``` php
<?php
function hello($opts = ['silent|s' => false])
{
if (!$opts['silent']) $this->say("Hello, world");
}
?>
```
Now command can be executed with '-s' to run in silent mode:
```
robo hello -s
```
### Load From Other Directories
Robo can execute commands from a RoboFile located in different directory.
You can specify the path to another RoboFile by including the `--load-from` option:
```
robo run --load-from /path/to/my/other/project
```
### Pass-Through Arguments
Sometimes you need to pass arguments from your command into a task. A command line after the `--` characters is treated as one argument.
Any special character like `-` will be passed into without change.
``` php
<?php
function ls($args)
{
$this->taskExec('ls')->args($args)->run();
}
?>
```
```
robo ls -- Robo -c --all
[Robo\Task\ExecTask] running ls Robo -c --all
. .. CHANGELOG.md codeception.yml composer.json composer.lock docs .git .gitignore .idea LICENSE README.md robo RoboFile.php robo.phar src tests .travis.yml vendor
```
### Help
The help text for a command in a RoboFile may be provided in Doc-Block comments. An example help Doc-Block comment is shown below:
``` php
<?php
/**
* Calculate the fibonacci sequence between two numbers.
*
* Graphic output will look like
* +----+---+-------------+
* | | | |
* | |-+-| |
* |----+-+-+ |
* | | |
* | | |
* | | |
* +--------+-------------+
*
* @param int $start Number to start from
* @param int $steps Number of steps to perform
* @param array $opts
* @option $graphic Display the sequence graphically using cube
* representation
*/
public function fibonacci($start, $steps, $opts = ['graphic' => false])
{
}
?>
```
The corresponding help text produced is:
```
robo fibonacci --help
Usage:
fibonacci [--graphic] start steps
Arguments:
start Number to start from
steps Number of steps to perform
Options:
--graphic Display the sequence graphically using cube representation
Help:
Graphic output will look like
+----+---+-------------+
| | | |
| |-+-| |
|----+-+-+ |
| | |
| | |
| | |
+--------+-------------+
```
Arguments and options are populated from annotations.
Initially added with [PR by @jonsa](https://github.com/consolidation/Robo/pull/71); now provided by the [consolidation/annotated-command](https://github.com/consolidation/annotated-command) project, which was factored out from Robo.
### Ignored methods
Robo ignores any method of your RoboFile that begins with `get` or `set`. These methods are presumed to be data accessors, not commands. To implement a command whose name contains `get` or `set`, use the `@command` annotation.
``` php
<?php
/**
* @command set-alignment
*/
function setAlignment($value)
{
...
}
?>
```
## Tasks
Robo commands typically divide the work they need to accomplish into **tasks**. The command first determines what needs to be done, inspecting current state if necessary, and then sets up and executes one or more tasks that make the actual changes needed by the command. (See also the documentation on [Collections](collections.md), which allow you to combine groups of tasks which can provide rollback functions to recover from failure situations.)
For details on how to add custom tasks to Robo, see the [extending](extending.md) document.
### Shortcuts
Some tasks may have shortcuts. If a task does not require multi-step configuration, it can be executed with a single line:
```php
<?php
$this->_exec('ps aux');
$this->_copy('config/env.example.yml','config/env.yml');
?>
```
### Result
Each task must return an instance of `Robo\Result`. A Robo Result contains the task instance, exit code, message, and any variable data that the task may wish to return.
The `run` method of `CompileAssets` class may look like this:
```
return new Robo\Result($this, $exitCode, "Assets compiled");
```
or
```
return Robo\Result::success($this, "Assets compiled");
return Robo\Result::error($this, "Failed to compile assets");
```
You can use this results to check if execution was successful, either using the `wasSuccessful()` method, or via the `invoke` shortcut. We will use the `Exec` task in next example to illustrate this:
``` php
<?php
class RoboFile
{
use Robo\Task\Base\loadShortcuts;
function test()
{
$res1 = $this->_exec('phpunit tests/integration');
$res2 = $this->_exec('phpunit tests/unit');
// print message when tests passed
if ($res1->wasSuccessful() and $res2->wasSuccessful()) $this->say("All tests passed");
}
}
?>
```
When making multi-step commands that call one task after another, it is best to use a collection to group the tasks together. The collection will handle error detection and rollback, and will return a single Result object when done. For more information, see the [Collections](collections.md) documentation.
Some tasks may also attach data to the Result object. If this is done, the data may be accessed as an array; for example, `$result['path'];`. This is not common.
Commands should return a Result object obtained from a task; this will ensure that the command exit code is set correctly. If a command does not have a Result object available, then it may use a ResultData object. ResultData objects are just like Result objects, except the do not contain a reference to a task.
return new Robo\ResultData($exitcode, 'Error message.');
If the command returns a TaskInterface instead of a result, then the task will be executed, and the result from that task will be used as the final result of the command. See also `Formatters`, below.
### Stack
Some tasks contain `Stack` in their name. These are called "stack" tasks, and they execute similar tasks one after the other. Each of the primary methods in a stack class executes an operation.
Stack tasks also contain a `stopOnFail` method which can be used to stop task execution if one of its commands was unsuccessful.
### Global StopOnFail
There is a global `stopOnFail` method as well, that can be used to stop a command on first failure of a task.
```
$this->stopOnFail(true);
```
### IO
As you noticed, you can print text via the `say` method, which is taken from the `Robo\Output` trait.
```
$this->say("Hello");
```
Also, you can ask for input from console:
```
$name = $this->ask("What is your name?");
```
There are also `askDefault`, `askHidden`, and `confirm` methods.
In addition, Robo makes all of the methods of Symfony Style available throgh the `io()` method:
$this->io()->title("Build all site assets");
This allows Robo scripts to follow the [Symfony Console Style Guide](http://symfony.com/blog/new-in-symfony-2-8-console-style-guide) if desired.
### Formatters
It is preferable for commands that look up and display information should avoid doing IO directly, and should instead return the data they wish to display as an array. This data can then be converted into different data formats, such as "table" and "json". The user may select which formatter to use via the --format option. For details on formatters, see the [consolidation/output-formatters](https://github.com/consolidation/output-formatters) project.
### Progress
Robo supports progress indicators via the Symfony ProgressBar class. Long-running tasks that wish to display the progress indicator may do so via four simple steps:
- Override the `progressIndicatorSteps()` method and return the number of "steps" in the operation.
- Call `$this->startProgressIndicator()` to begin the progress indicator running.
- Call `$this->advanceProgressIndicator()` a number of times equal to the result returned by `progressIndicatorSteps()`
- Call `$this->stopProgressIndicator()` when the operation is completed.
An example of this is shown below:
``` php
<?php
class MyTask extends BaseTask
{
protected $steps = 10;
public function progressIndicatorSteps()
{
return $this->steps;
}
public function run()
{
$exitCode = 0;
$errorMessage = "";
$this->startProgressIndicator();
for ($i = 0; $i < $this->steps; ++$i) {
$this->advanceProgressIndicator();
}
$this->stopProgressIndicator();
return new Result($this, $exitCode, $errorMessage, ['time' => $this->getExecutionTime()]);
}
}
?>
```
Tasks should not attempt to use a specific progress indicator (e.g. the Symfony ProgressBar class) directly, as the ProgressIndicatorAwareTrait allows for an appropriate progress indicator to be used (or omitted) as best suits the application.
Note that when using [Collections](collections.md), the progress bar will automatically be shown if the collection takes longer than two seconds to run. Each task in the collection will count for one "step"; if the task supports progress indicators as shown above, then it will add an additional number of steps as indicated by its `progressIndicatorSteps()` method.
## Working with Composer
### Adding a RoboFile to your Project
Robo is designed to work well with Composer. To use Robo scripts in your Composer-based project, simply add `robo` to your composer.json file:
```
$ cd myproject
$ composer require consolidation/Robo:~1
$ ./vendor/bin/robo mycommand
```
If you do not want to type the whole path to Robo, you may add `./vendor/bin` to your $PATH (relative paths work), or use `composer exec` to find and run Robo:
```
$ composer exec robo mycommand
```
### Implementing Composer Scripts with Robo
When using Robo in your project, it is convenient to define Composer scripts that call your Robo commands. Simply add the following to your composer.json file:
```
{
"name": "myorg/myproject",
"require": {
"consolidation/Robo": "~1"
},
"scripts": {
"test": "composer robo test",
"phar": "composer robo phar:build",
"robo": "robo --ansi --load-from $(pwd)/scripts/BuildCommands.php"
}
}
```
*Note*: When you include Robo as a library like this, some external projects used by certain core Robo tasks are not automatically included in your project. See the `"suggest":` section of Robo's composer.json for a list of external projects you might also want to require in your project.
Once you have set up your composer.json file (and ran `composer update` if you manually changed the `require` or `require-dev` sections), Composer will ensure that your project-local copy of Robo in the `vendor/bin` dir is in your $PATH when you run the additional Composer scripts that you declared:
```
$ cd myproject
$ composer test
$ composer phar
```
This will call the public methods `test()` and `phar()` in your RoboFile.php when using `composer test` and `composer phar`, respectively.
Advertising your build commands as Composer scripts is a useful way to provide the key commands used for testing, building or packaging your application. Also, if your application should happen to provide a commandline tool to perform the operations of the application itself, then defining your build commands in their own RoboFile provides desirable separation, keeping your build commands out of the help and list commands of your primary script.
If you would like to simplify the output of your script (e.g. when running on a CI service), replace the `--ansi` option in the example above with `--no-ansi`, and colored terminal output and progress bars will be disabled.
## Robo as a Framework
For an overview on how to turn your Robo scripts into standalone tools, see the example [robo.script](https://github.com/consolidation/Robo/blob/master/examples/robo.script), and the section [Robo as a Framework](framework.md).

View File

@@ -0,0 +1,24 @@
# Robo Documentation
* [Getting Started](getting-started.md)
* [Collections](collections.md)
* [Extending](extending.md)
* [Robo as a Framework](framework.md)
## Tasks
* [ApiGen](tasks/ApiGen.md)
* [Archive](tasks/Archive.md)
* [Assets](tasks/Assets.md)
* [Base](tasks/Base.md)
* [Bower](tasks/Bower.md)
* [Composer](tasks/Composer.md)
* [Development](tasks/Development.md)
* [Docker](tasks/Docker.md)
* [File](tasks/File.md)
* [Filesystem](tasks/Filesystem.md)
* [Gulp](tasks/Gulp.md)
* [Npm](tasks/Npm.md)
* [Remote](tasks/Remote.md)
* [Testing](tasks/Testing.md)
* [Vcs](tasks/Vcs.md)

View File

@@ -0,0 +1,56 @@
# ApiGen Tasks
## ApiGen
Executes ApiGen command to generate documentation
``` php
<?php
// ApiGen Command
$this->taskApiGen('./apigen.neon')
->templateConfig('vendor/apigen/apigen/templates/bootstrap/config.neon')
->wipeout(true)
->run();
?>
```
* `config($config)` * `param string` $config
* `source($src)` * `param array|string|Traversable` $src one or more source values
* `destination($dest)` * `param string` $dest
* `extensions($exts)` * `param array|string` $exts one or more extensions
* `exclude($exclude)` * `param array|string` $exclude one or more exclusions
* `skipDocPath($path)` * `param array|string|Traversable` $path one or more skip-doc-path values
* `skipDocPrefix($prefix)` * `param array|string|Traversable` $prefix one or more skip-doc-prefix values
* `charset($charset)` * `param array|string` $charset one or more charsets
* `mainProjectNamePrefix($name)` * `param string` $name
* `title($title)` * `param string` $title
* `baseUrl($baseUrl)` * `param string` $baseUrl
* `googleCseId($id)` * `param string` $id
* `googleAnalytics($trackingCode)` * `param string` $trackingCode
* `templateConfig($templateConfig)` * `param mixed` $templateConfig
* `allowedHtml($tags)` * `param array|string` $tags one or more supported html tags
* `groups($groups)` * `param string` $groups
* `autocomplete($types)` * `param array|string` $types or more supported autocomplete types
* `accessLevels($levels)` * `param array|string` $levels one or more access levels
* `internal($internal)` * `param boolean|string` $internal 'yes' or true if internal, 'no' or false if not
* `php($php)` * `param boolean|string` $php 'yes' or true to generate documentation for internal php classes,
* `tree($tree)` * `param bool|string` $tree 'yes' or true to generate a tree view of classes, 'no' or false otherwise
* `deprecated($dep)` * `param bool|string` $dep 'yes' or true to generate documentation for deprecated classes, 'no' or false otherwise
* `todo($todo)` * `param bool|string` $todo 'yes' or true to document tasks, 'no' or false otherwise
* `sourceCode($src)` * `param bool|string` $src 'yes' or true to generate highlighted source code, 'no' or false otherwise
* `download($zipped)` * `param bool|string` $zipped 'yes' or true to generate downloadable documentation, 'no' or false otherwise
* `report($path)`
* `wipeout($wipeout)` * `param bool|string` $wipeout 'yes' or true to clear out the destination directory, 'no' or false otherwise
* `quiet($quiet)` * `param bool|string` $quiet 'yes' or true for quiet, 'no' or false otherwise
* `progressbar($bar)` * `param bool|string` $bar 'yes' or true to display a progress bar, 'no' or false otherwise
* `colors($colors)` * `param bool|string` $colors 'yes' or true colorize the output, 'no' or false otherwise
* `updateCheck($check)` * `param bool|string` $check 'yes' or true to check for updates, 'no' or false otherwise
* `debug($debug)` * `param bool|string` $debug 'yes' or true to enable debug mode, 'no' or false otherwise
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,52 @@
# Archive Tasks
## Extract
Extracts an archive.
Note that often, distributions are packaged in tar or zip archives
where the topmost folder may contain variable information, such as
the release date, or the version of the package. This information
is very useful when unpacking by hand, but arbitrarily-named directories
are much less useful to scripts. Therefore, by default, Extract will
remove the top-level directory, and instead store all extracted files
into the directory specified by $archivePath.
To keep the top-level directory when extracting, use
`preserveTopDirectory(true)`.
``` php
<?php
$this->taskExtract($archivePath)
->to($destination)
->preserveTopDirectory(false) // the default
->run();
?>
```
* `to(string)` location to store extracted files
* `to($to)` Location to store extracted files.
* `preserveTopDirectory($preserve = null)` * `param bool` $preserve
## Pack
Creates a zip or tar archive.
``` php
<?php
$this->taskPack(
<archiveFile>)
->add('README') // Puts file 'README' in archive at the root
->add('project') // Puts entire contents of directory 'project' in archinve inside 'project'
->addFile('dir/file.txt', 'file.txt') // Takes 'file.txt' from cwd and puts it in archive inside 'dir'.
->run();
?>
```
* `archiveFile($archiveFile)` * `param string` $archiveFile
* `addFile($placementLocation, $filesystemLocation)` Add an item to the archive. Like file_exists(), the parameter
* `addDir($placementLocation, $filesystemLocation)` Alias for addFile, in case anyone has angst about using
* `add($item)` Add a file or directory, or list of same to the archive.

View File

@@ -0,0 +1,163 @@
# Assets Tasks
## ImageMinify
Minifies images. When the required minifier is not installed on the system
the task will try to download it from the [imagemin](https://github.com/imagemin) repository.
When the task is run without any specified minifier it will compress the images
based on the extension.
```php
$this->taskImageMinify('assets/images/*')
->to('dist/images/')
->run();
```
This will use the following minifiers:
- PNG: optipng
- GIF: gifsicle
- JPG, JPEG: jpegtran
- SVG: svgo
When the minifier is specified the task will use that for all the input files. In that case
it is useful to filter the files with the extension:
```php
$this->taskImageMinify('assets/images/*.png')
->to('dist/images/')
->minifier('pngcrush');
->run();
```
The task supports the following minifiers:
- optipng
- pngquant
- advpng
- pngout
- zopflipng
- pngcrush
- gifsicle
- jpegoptim
- jpeg-recompress
- jpegtran
- svgo (only minification, no downloading)
You can also specifiy extra options for the minifiers:
```php
$this->taskImageMinify('assets/images/*.jpg')
->to('dist/images/')
->minifier('jpegtran', ['-progressive' => null, '-copy' => 'none'])
->run();
```
This will execute as:
`jpegtran -copy none -progressive -optimize -outfile "dist/images/test.jpg" "/var/www/test/assets/images/test.jpg"`
* `to($target)` Sets the target directory where the files will be copied to.
* `minifier($minifier, array $options = Array ( ) )` Sets the minifier.
## Less
Compiles less files.
```php
<?php
$this->taskLess([
'less/default.less' => 'css/default.css'
])
->run();
?>
```
Use one of both less compilers in your project:
```
"leafo/lessphp": "~0.5",
"oyejorge/less.php": "~1.5"
```
Specify directory (string or array) for less imports lookup:
```php
<?php
$this->taskLess([
'less/default.less' => 'css/default.css'
])
->importDir('less')
->compiler('lessphp')
->run();
?>
```
You can implement additional compilers by extending this task and adding a
method named after them and overloading the lessCompilers() method to
inject the name there.
* `importDir($dirs)` Sets import directories
* `addImportPath($dir)` Adds import directory
* `setImportPaths($dirs)` Sets import directories
* `setFormatter($formatterName)` * `param string` $formatterName
* `compiler($compiler, array $options = Array ( ) )` Sets the compiler.
## Minify
Minifies asset file (CSS or JS).
``` php
<?php
$this->taskMinify( 'web/assets/theme.css' )
->run()
?>
```
Please install additional dependencies to use:
```
"patchwork/jsqueeze": "~1.0",
"natxet/CssMin": "~3.0"
```
* `to($dst)` Sets destination. Tries to guess type from it.
* `type($type)` Sets type with validation.
* `singleLine($singleLine)` Single line option for the JS minimisation.
* `keepImportantComments($keepImportantComments)` keepImportantComments option for the JS minimisation.
* `specialVarRx($specialVarRx)` specialVarRx option for the JS minimisation.
* `__toString()` @return string
## Scss
Compiles scss files.
```php
<?php
$this->taskScss([
'scss/default.scss' => 'css/default.css'
])
->importDir('assets/styles')
->run();
?>
```
Use the following scss compiler in your project:
```
"leafo/scssphp": "~0.1",
```
You can implement additional compilers by extending this task and adding a
method named after them and overloading the scssCompilers() method to
inject the name there.
* `setFormatter($formatterName)` Sets the formatter for scssphp
* `importDir($dirs)` Sets import directories
* `addImportPath($dir)` Adds import directory
* `setImportPaths($dirs)` Sets import directories
* `compiler($compiler, array $options = Array ( ) )` Sets the compiler.

View File

@@ -0,0 +1,126 @@
# Base Tasks
## Exec
Executes shell script. Closes it when running in background mode.
``` php
<?php
$this->taskExec('compass')->arg('watch')->run();
// or use shortcut
$this->_exec('compass watch');
$this->taskExec('compass watch')->background()->run();
if ($this->taskExec('phpunit .')->run()->wasSuccessful()) {
$this->say('tests passed');
}
?>
```
* `background()` Executes command in background mode (asynchronously)
* `timeout($timeout)` Stop command if it runs longer then $timeout in seconds
* `idleTimeout($timeout)` Stops command if it does not output something for a while
* `env(array $env)` Sets the environment variables for the command
* `simulate($context)` {@inheritdoc}
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## ExecStack
Execute commands one by one in stack.
Stack can be stopped on first fail if you call `stopOnFail()`.
```php
<?php
$this->taskExecStack()
->stopOnFail()
->exec('mkdir site')
->exec('cd site')
->run();
?>
```
* `$this stopOnFail()`
* `executable($executable)` * `param string` $executable
* `exec($command)` * `param string|string[]` $command
* `stopOnFail($stopOnFail = null)` * `param bool` $stopOnFail
* `result($result)`
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
## ParallelExec
Class ParallelExecTask
``` php
<?php
$this->taskParallelExec()
->process('php ~/demos/script.php hey')
->process('php ~/demos/script.php hoy')
->process('php ~/demos/script.php gou')
->run();
?>
```
* ` timeout(int $timeout)` stops process if it runs longer then `$timeout` (seconds)
* ` idleTimeout(int $timeout)` stops process if it does not output for time longer then `$timeout` (seconds)
* `printed($isPrinted = null)` * `param bool` $isPrinted
* `process($command)` * `param string|\Robo\Contract\CommandInterface` $command
* `timeout($timeout)` * `param int` $timeout
* `idleTimeout($idleTimeout)` * `param int` $idleTimeout
## SymfonyCommand
Executes Symfony Command
``` php
<?php
// Symfony Command
$this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
->arg('suite','acceptance')
->opt('debug')
->run();
// Artisan Command
$this->taskSymfonyCommand(new ModelGeneratorCommand())
->arg('name', 'User')
->run();
?>
```
* `arg($arg, $value)` * `param string` $arg
* `opt($option, $value = null)`
## Watch
Runs task when specified file or dir was changed.
Uses Lurker library.
``` php
<?php
$this->taskWatch()
->monitor('composer.json', function() {
$this->taskComposerUpdate()->run();
})->monitor('src', function() {
$this->taskExec('phpunit')->run();
})->run();
?>
```
* `monitor($paths, $callable)` * `param string|string[]` $paths

View File

@@ -0,0 +1,60 @@
# Bower Tasks
## Install
Bower Install
``` php
<?php
// simple execution
$this->taskBowerInstall()->run();
// prefer dist with custom path
$this->taskBowerInstall('path/to/my/bower')
->noDev()
->run();
?>
```
* `allowRoot()` adds `allow-root` option to bower
* `forceLatest()` adds `force-latest` option to bower
* `noDev()` adds `production` option to bower
* `offline()` adds `offline` option to bower
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Update
Bower Update
``` php
<?php
// simple execution
$this->taskBowerUpdate->run();
// prefer dist with custom path
$this->taskBowerUpdate('path/to/my/bower')
->noDev()
->run();
?>
```
* `allowRoot()` adds `allow-root` option to bower
* `forceLatest()` adds `force-latest` option to bower
* `noDev()` adds `production` option to bower
* `offline()` adds `offline` option to bower
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,179 @@
# Composer Tasks
## DumpAutoload
Composer Dump Autoload
``` php
<?php
// simple execution
$this->taskComposerDumpAutoload()->run();
// dump auto loader with custom path
$this->taskComposerDumpAutoload('path/to/my/composer.phar')
->preferDist()
->run();
// optimize autoloader dump with custom path
$this->taskComposerDumpAutoload('path/to/my/composer.phar')
->optimize()
->run();
// optimize autoloader dump with custom path and no dev
$this->taskComposerDumpAutoload('path/to/my/composer.phar')
->optimize()
->noDev()
->run();
?>
```
* `optimize()` * `return` $this
* `preferDist()` adds `prefer-dist` option to composer
* `preferSource()` adds `prefer-source` option to composer
* `noDev()` adds `no-dev` option to composer
* `noAnsi()` adds `no-ansi` option to composer
* `ansi()` adds `ansi` option to composer
* `optimizeAutoloader()` adds `optimize-autoloader` option to composer
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Install
Composer Install
``` php
<?php
// simple execution
$this->taskComposerInstall()->run();
// prefer dist with custom path
$this->taskComposerInstall('path/to/my/composer.phar')
->preferDist()
->run();
// optimize autoloader with custom path
$this->taskComposerInstall('path/to/my/composer.phar')
->optimizeAutoloader()
->run();
?>
```
* `preferDist()` adds `prefer-dist` option to composer
* `preferSource()` adds `prefer-source` option to composer
* `noDev()` adds `no-dev` option to composer
* `noAnsi()` adds `no-ansi` option to composer
* `ansi()` adds `ansi` option to composer
* `optimizeAutoloader()` adds `optimize-autoloader` option to composer
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Remove
Composer Validate
``` php
<?php
// simple execution
$this->taskComposerValidate()->run();
?>
```
* `dev()` * `return` $this
* `noProgress()` * `return` $this
* `noUpdate()` * `return` $this
* `updateNoDev()` * `return` $this
* `noUpdateWithDependencies()` * `return` $this
* `preferDist()` adds `prefer-dist` option to composer
* `preferSource()` adds `prefer-source` option to composer
* `noDev()` adds `no-dev` option to composer
* `noAnsi()` adds `no-ansi` option to composer
* `ansi()` adds `ansi` option to composer
* `optimizeAutoloader()` adds `optimize-autoloader` option to composer
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Update
Composer Update
``` php
<?php
// simple execution
$this->taskComposerUpdate()->run();
// prefer dist with custom path
$this->taskComposerUpdate('path/to/my/composer.phar')
->preferDist()
->run();
// optimize autoloader with custom path
$this->taskComposerUpdate('path/to/my/composer.phar')
->optimizeAutoloader()
->run();
?>
```
* `preferDist()` adds `prefer-dist` option to composer
* `preferSource()` adds `prefer-source` option to composer
* `noDev()` adds `no-dev` option to composer
* `noAnsi()` adds `no-ansi` option to composer
* `ansi()` adds `ansi` option to composer
* `optimizeAutoloader()` adds `optimize-autoloader` option to composer
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Validate
Composer Validate
``` php
<?php
// simple execution
$this->taskComposerValidate()->run();
?>
```
* `noCheckAll()` * `return` $this
* `noCheckLock()` * `return` $this
* `noCheckPublish()` * `return` $this
* `withDependencies()` * `return` $this
* `strict()` * `return` $this
* `preferDist()` adds `prefer-dist` option to composer
* `preferSource()` adds `prefer-source` option to composer
* `noDev()` adds `no-dev` option to composer
* `noAnsi()` adds `no-ansi` option to composer
* `ansi()` adds `ansi` option to composer
* `optimizeAutoloader()` adds `optimize-autoloader` option to composer
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,282 @@
# Development Tasks
## Changelog
Helps to manage changelog file.
Creates or updates `changelog.md` file with recent changes in current version.
``` php
<?php
$version = "0.1.0";
$this->taskChangelog()
->version($version)
->change("released to github")
->run();
?>
```
Changes can be asked from Console
``` php
<?php
$this->taskChangelog()
->version($version)
->askForChanges()
->run();
?>
```
* `Development\Changelog filename(string $filename)`
* `Development\Changelog anchor(string $anchor)`
* `Development\Changelog version(string $version)`
* `filename($filename)` * `param string` $filename
* `log($item)` * `param string` $item
* `anchor($anchor)` * `param string` $anchor
* `version($version)` * `param string` $version
* `changes(array $data)` * `param array` $data
* `change($change)` * `param string` $change
* `getChanges()` @return array
## GenerateMarkdownDoc
Simple documentation generator from source files.
Takes classes, properties and methods with their docblocks and writes down a markdown file.
``` php
<?php
$this->taskGenDoc('models.md')
->docClass('Model\User') // take class Model\User
->docClass('Model\Post') // take class Model\Post
->filterMethods(function(\ReflectionMethod $r) {
return $r->isPublic() or $r->isProtected(); // process public and protected methods
})->processClass(function(\ReflectionClass $r, $text) {
return "Class ".$r->getName()."\n\n$text\n\n###Methods\n";
})->run();
```
By default this task generates a documentation for each public method of a class.
It combines method signature with a docblock. Both can be post-processed.
``` php
<?php
$this->taskGenDoc('models.md')
->docClass('Model\User')
->processClassSignature(false) // false can be passed to not include class signature
->processClassDocBlock(function(\ReflectionClass $r, $text) {
return "[This is part of application model]\n" . $text;
})->processMethodSignature(function(\ReflectionMethod $r, $text) {
return "#### {$r->name}()";
})->processMethodDocBlock(function(\ReflectionMethod $r, $text) {
return strpos($r->name, 'save')===0 ? "[Saves to the database]\n" . $text : $text;
})->run();
```
* ` docClass(string $classname)` put a class you want to be documented
* ` filterMethods(\Closure $func)` using callback function filter out methods that won't be documented
* ` filterClasses(\Closure $func)` using callback function filter out classes that won't be documented
* ` filterProperties(\Closure $func)` using callback function filter out properties that won't be documented
* ` processClass(\Closure $func)` post-process class documentation
* ` processClassSignature(\Closure $func)` post-process class signature. Provide *false* to skip.
* ` processClassDocBlock(\Closure $func)` post-process class docblock contents. Provide *false* to skip.
* ` processMethod(\Closure $func)` post-process method documentation. Provide *false* to skip.
* ` processMethodSignature(\Closure $func)` post-process method signature. Provide *false* to skip.
* ` processMethodDocBlock(\Closure $func)` post-process method docblock contents. Provide *false* to skip.
* ` processProperty(\Closure $func)` post-process property documentation. Provide *false* to skip.
* ` processPropertySignature(\Closure $func)` post-process property signature. Provide *false* to skip.
* ` processPropertyDocBlock(\Closure $func)` post-process property docblock contents. Provide *false* to skip.
* ` reorder(\Closure $func)` use a function to reorder classes
* ` reorderMethods(\Closure $func)` use a function to reorder methods in class
* ` prepend($text)` inserts text into beginning of markdown file
* ` append($text)` inserts text in the end of markdown file
* `docClass($item)` * `param string` $item
* `filterMethods($filterMethods)` * `param callable` $filterMethods
* `filterClasses($filterClasses)` * `param callable` $filterClasses
* `filterProperties($filterProperties)` * `param callable` $filterProperties
* `processClass($processClass)` * `param callable` $processClass
* `processClassSignature($processClassSignature)` * `param callable|false` $processClassSignature
* `processClassDocBlock($processClassDocBlock)` * `param callable|false` $processClassDocBlock
* `processMethod($processMethod)` * `param callable|false` $processMethod
* `processMethodSignature($processMethodSignature)` * `param callable|false` $processMethodSignature
* `processMethodDocBlock($processMethodDocBlock)` * `param callable|false` $processMethodDocBlock
* `processProperty($processProperty)` * `param callable|false` $processProperty
* `processPropertySignature($processPropertySignature)` * `param callable|false` $processPropertySignature
* `processPropertyDocBlock($processPropertyDocBlock)` * `param callable|false` $processPropertyDocBlock
* `reorder($reorder)` * `param callable` $reorder
* `reorderMethods($reorderMethods)` * `param callable` $reorderMethods
* `reorderProperties($reorderProperties)` * `param callable` $reorderProperties
* `filename($filename)` * `param string` $filename
* `prepend($prepend)` * `param string` $prepend
* `append($append)` * `param string` $append
* `text($text)` * `param string` $text
* `textForClass($item)` * `param string` $item
## Generate
Generate a Robo Task that is a wrapper around an existing class.
``` php
<?php
$this->taskGenerateTask('Symfony\Component\Filesystem\Filesystem', 'FilesystemStack')
->run();
```
## GitHubRelease
Publishes new GitHub release.
``` php
<?php
$this->taskGitHubRelease('0.1.0')
->uri('consolidation-org/Robo')
->description('Add stuff people need.')
->change('Fix #123')
->change('Add frobulation method to all widgets')
->run();
?>
```
* `tag($tag)` * `param string` $tag
* `draft($draft)` * `param bool` $draft
* `name($name)` * `param string` $name
* `description($description)` * `param string` $description
* `prerelease($prerelease)` * `param bool` $prerelease
* `comittish($comittish)` * `param string` $comittish
* `appendDescription($description)` * `param string` $description
* `changes(array $changes)`
* `change($change)` * `param string` $change
* `repo($repo)` * `param string` $repo
* `owner($owner)` * `param string` $owner
* `uri($uri)` * `param string` $uri
* `user($user)` * `param string` $user
* `password($password)` * `param` $password
## OpenBrowser
Opens the default's user browser
code inspired from openBrowser() function in https://github.com/composer/composer/blob/master/src/Composer/Command/HomeCommand.php
``` php
<?php
// open one browser window
$this->taskOpenBrowser('http://localhost')
->run();
// open two browser windows
$this->taskOpenBrowser([
'http://localhost/mysite',
'http://localhost/mysite2'
])
->run();
```
## PackPhar
Creates Phar.
``` php
<?php
$pharTask = $this->taskPackPhar('package/codecept.phar')
->compress()
->stub('package/stub.php');
$finder = Finder::create()
->name('*.php')
->in('src');
foreach ($finder as $file) {
$pharTask->addFile('src/'.$file->getRelativePathname(), $file->getRealPath());
}
$finder = Finder::create()->files()
->name('*.php')
->in('vendor');
foreach ($finder as $file) {
$pharTask->addStripped('vendor/'.$file->getRelativePathname(), $file->getRealPath());
}
$pharTask->run();
// verify Phar is packed correctly
$code = $this->_exec('php package/codecept.phar');
?>
```
* `compress($compress = null)` * `param bool` $compress
* `stub($stub)` * `param string` $stub
* `addStripped($path, $file)` * `param string` $path
* `addFile($path, $file)` * `param string` $path
* `addFiles($files)` * `param \Symfony\Component\Finder\SplFileInfo[]` $files
* `executable($file)` * `param string` $file
## PhpServer
Runs PHP server and stops it when task finishes.
``` php
<?php
// run server in /public directory
$this->taskServer(8000)
->dir('public')
->run();
// run with IP 0.0.0.0
$this->taskServer(8000)
->host('0.0.0.0')
->run();
// execute server in background
$this->taskServer(8000)
->background()
->run();
?>
```
* `host($host)` * `param string` $host
* `dir($path)` * `param string` $path
* `background()` Executes command in background mode (asynchronously)
* `timeout($timeout)` Stop command if it runs longer then $timeout in seconds
* `idleTimeout($timeout)` Stops command if it does not output something for a while
* `env(array $env)` Sets the environment variables for the command
* `simulate($context)` {@inheritdoc}
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## SemVer
Helps to maintain `.semver` file.
```php
<?php
$this->taskSemVer('.semver')
->increment()
->run();
?>
```
* `__toString()` @return string
* `setFormat($format)` * `param string` $format
* `setMetadataSeparator($separator)` * `param string` $separator
* `setPrereleaseSeparator($separator)` * `param string` $separator
* `increment($what = null)` * `param string` $what
* `prerelease($tag = null)` * `param string` $tag
* `metadata($data)` * `param array|string` $data

View File

@@ -0,0 +1,249 @@
# Docker Tasks
## Build
Builds Docker image
```php
<?php
$this->taskDockerBuild()->run();
$this->taskDockerBuild('path/to/dir')
->tag('database')
->run();
?>
```
Class Build
@package Robo\Task\Docker
* `tag($tag)` * `param string` $tag
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Commit
Commits docker container to an image
```
$this->taskDockerCommit($containerId)
->name('my/database')
->run();
// alternatively you can take the result from DockerRun task:
$result = $this->taskDockerRun('db')
->exec('./prepare_database.sh')
->run();
$task->dockerCommit($result)
->name('my/database')
->run();
```
* `name($name)` * `param` $name
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Exec
Executes command inside running Docker container
```php
<?php
$test = $this->taskDockerRun('test_env')
->detached()
->run();
$this->taskDockerExec($test)
->interactive()
->exec('./runtests')
->run();
// alternatively use commands from other tasks
$this->taskDockerExec($test)
->interactive()
->exec($this->taskCodecept()->suite('acceptance'))
->run();
?>
```
* `detached()` * `return` $this
* `interactive()` * `return` $this
* `exec($command)` * `param string|\Robo\Contract\CommandInterface` $command
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Pull
Pulls an image from DockerHub
```php
<?php
$this->taskDockerPull('wordpress')
->run();
?>
```
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Remove
Remove docker container
```php
<?php
$this->taskDockerRemove($container)
->run();
?>
```
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Run
Performs `docker run` on a container.
```php
<?php
$this->taskDockerRun('mysql')->run();
$result = $this->taskDockerRun('my_db_image')
->env('DB', 'database_name')
->volume('/path/to/data', '/data')
->detached()
->publish(3306)
->name('my_mysql')
->run();
// retrieve container's cid:
$this->say("Running container ".$result->getCid());
// execute script inside container
$result = $this->taskDockerRun('db')
->exec('prepare_test_data.sh')
->run();
$this->taskDockerCommit($result)
->name('test_db')
->run();
// link containers
$mysql = $this->taskDockerRun('mysql')
->name('wp_db') // important to set name for linked container
->env('MYSQL_ROOT_PASSWORD', '123456')
->run();
$this->taskDockerRun('wordpress')
->link($mysql)
->publish(80, 8080)
->detached()
->run();
?>
```
* `detached()` * `return` $this
* `interactive()` * `return` $this
* `exec($run)` * `param string|\Robo\Contract\CommandInterface` $run
* `volume($from, $to = null)` * `param string` $from
* `env($variable, $value = null)` * `param string` $variable
* `publish($port = null, $portTo = null)` * `param null|int` $port
* `containerWorkdir($dir)` * `param string` $dir
* `user($user)` * `param string` $user
* `privileged()` * `return` $this
* `name($name)` * `param string` $name
* `link($name, $alias)` * `param string|\Robo\Task\Docker\Result` $name
* `tmpDir($dir)` * `param string` $dir
* `getTmpDir()` @return string
* `getUniqId()` @return string
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Start
Starts Docker container
```php
<?php
$this->taskDockerStart($cidOrResult)
->run();
?>
```
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Stop
Stops Docker container
```php
<?php
$this->taskDockerStop($cidOrResult)
->run();
?>
```
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,129 @@
# File Tasks
## Concat
Merges files into one. Used for preparing assets.
``` php
<?php
$this->taskConcat([
'web/assets/screen.css',
'web/assets/print.css',
'web/assets/theme.css'
])
->to('web/assets/style.css')
->run()
?>
```
* `to($dst)` set the destination file
## Replace
Performs search and replace inside a files.
``` php
<?php
$this->taskReplaceInFile('VERSION')
->from('0.2.0')
->to('0.3.0')
->run();
$this->taskReplaceInFile('README.md')
->from(date('Y')-1)
->to(date('Y'))
->run();
$this->taskReplaceInFile('config.yml')
->regex('~^service:~')
->to('services:')
->run();
$this->taskReplaceInFile('box/robo.txt')
->from(array('##dbname##', '##dbhost##'))
->to(array('robo', 'localhost'))
->run();
?>
```
* `regex(string)` regex to match string to be replaced
* `from(string|array)` string(s) to be replaced
* `to(string|array)` value(s) to be set as a replacement
* `filename($filename)` * `param string` $filename
* `from($from)` * `param string` $from
* `to($to)` * `param string` $to
* `regex($regex)` * `param string` $regex
## TmpFile
Create a temporary file that is automatically cleaned up
once the task collection is is part of completes. When created,
it is given a random filename.
This temporary file may be manipulated exacatly like taskWrite().
It is deleted as soon as the collection it is a part of completes
or rolls back.
``` php
<?php
$collection = $this->collectionBuilder();
$tmpFilePath = $collection->taskTmpFile()
->line('-----')
->line(date('Y-m-d').' '.$title)
->line('----')
->getPath();
$collection->run();
?>
```
* `complete()` Delete this file when our collection completes.
* `filename($filename)` * `param string` $filename
* `append($append = null)` * `param bool` $append
* `line($line)` add a line.
* `lines(array $lines)` add more lines.
* `text($text)` add a text.
* `textFromFile($filename)` add a text from a file.
* `place($name, $val)` substitute a placeholder with value, placeholder must be enclosed by `{}`.
* `replace($string, $replacement)` replace any string with value.
* `regexReplace($pattern, $replacement)` replace any string with value using regular expression.
* `appendIfMatches($pattern, $text)` Append the provided text to the end of the buffer if the provided
* `appendUnlessMatches($pattern, $text)` Append the provided text to the end of the buffer unless the provided
* `originalContents()` @return string
* `wouldChange()` @return bool
* `getPath()` @return string
## Write
Writes to file.
``` php
<?php
$this->taskWriteToFile('blogpost.md')
->line('-----')
->line(date('Y-m-d').' '.$title)
->line('----')
->run();
?>
```
* `append()`
* `filename($filename)` * `param string` $filename
* `append($append = null)` * `param bool` $append
* `line($line)` add a line.
* `lines(array $lines)` add more lines.
* `text($text)` add a text.
* `textFromFile($filename)` add a text from a file.
* `place($name, $val)` substitute a placeholder with value, placeholder must be enclosed by `{}`.
* `replace($string, $replacement)` replace any string with value.
* `regexReplace($pattern, $replacement)` replace any string with value using regular expression.
* `appendIfMatches($pattern, $text)` Append the provided text to the end of the buffer if the provided
* `appendUnlessMatches($pattern, $text)` Append the provided text to the end of the buffer unless the provided
* `originalContents()` @return string
* `wouldChange()` @return bool
* `getPath()` @return string

View File

@@ -0,0 +1,217 @@
# Filesystem Tasks
## CleanDir
Deletes all files from specified dir, ignoring git files.
``` php
<?php
$this->taskCleanDir(['tmp','logs'])->run();
// as shortcut
$this->_cleanDir('app/cache');
?>
```
## CopyDir
Copies one dir into another
``` php
<?php
$this->taskCopyDir(['dist/config' => 'config'])->run();
// as shortcut
$this->_copyDir('dist/config', 'config');
?>
```
* `dirPermissions($value)` Sets the default folder permissions for the destination if it doesn't exist
* `exclude($exclude = null)` List files to exclude.
## DeleteDir
Deletes dir
``` php
<?php
$this->taskDeleteDir('tmp')->run();
// as shortcut
$this->_deleteDir(['tmp', 'log']);
?>
```
## FilesystemStack
Wrapper for [Symfony Filesystem](http://symfony.com/doc/current/components/filesystem.html) Component.
Comands are executed in stack and can be stopped on first fail with `stopOnFail` option.
``` php
<?php
$this->taskFilesystemStack()
->mkdir('logs')
->touch('logs/.gitignore')
->chgrp('www', 'www-data')
->symlink('/var/log/nginx/error.log', 'logs/error.log')
->run();
// one line
$this->_touch('.gitignore');
$this->_mkdir('logs');
?>
```
* `$this mkdir($dir)`
* `$this touch($file)`
* `$this copy($from, $to, $force = null)`
* `$this chmod($file, $permissions, $umask = null, $recursive = null)`
* `$this chgrp($file, $group, $recursive = null)`
* `$this chown($file, $user, $recursive = null)`
* `$this remove($file)`
* `$this rename($from, $to)`
* `$this symlink($from, $to)`
* `$this mirror($from, $to)`
* `stopOnFail($stop = null)` * `param bool` $stop
## FlattenDir
Searches for files in a nested directory structure and copies them to
a target directory with or without the parent directories. The task was
inspired by [gulp-flatten](https://www.npmjs.com/package/gulp-flatten).
Example directory structure:
```
└── assets
├── asset-library1
│ ├── README.md
│ └── asset-library1.min.js
└── asset-library2
├── README.md
└── asset-library2.min.js
```
The following code will search the `*.min.js` files and copy them
inside a new `dist` folder:
``` php
<?php
$this->taskFlattenDir(['assets/*.min.js' => 'dist'])->run();
// or use shortcut
$this->_flattenDir('assets/*.min.js', 'dist');
?>
```
You can also define the target directory with an additional method, instead of
key/value pairs. More similar to the gulp-flatten syntax:
``` php
<?php
$this->taskFlattenDir(['assets/*.min.js'])
->to('dist')
->run();
?>
```
You can also append parts of the parent directories to the target path. If you give
the value `1` to the `includeParents()` method, then the top parent will be appended
to the target directory resulting in a path such as `dist/assets/asset-library1.min.js`.
If you give a negative number, such as `-1` (the same as specifying `array(0, 1)` then
the bottom parent will be appended, resulting in a path such as
`dist/asset-library1/asset-library1.min.js`.
The top parent directory will always be starting from the relative path to the current
directory. You can override that with the `parentDir()` method. If in the above example
you would specify `assets`, then the top parent directory would be `asset-library1`.
``` php
<?php
$this->taskFlattenDir(['assets/*.min.js' => 'dist'])
->parentDir('assets')
->includeParents(1)
->run();
?>
```
* `dirPermissions($permission)` Sets the default folder permissions for the destination if it does not exist.
* `includeParents($parents)` Sets the value from which direction and how much parent dirs should be included.
* `parentDir($dir)` Sets the parent directory from which the relative parent directories will be calculated.
* `to($target)` Sets the target directory where the files will be copied to.
## MirrorDir
Mirrors a directory to another
``` php
<?php
$this->taskMirrorDir(['dist/config/' => 'config/'])->run();
// or use shortcut
$this->_mirrorDir('dist/config/', 'config/');
?>
```
## TmpDir
Create a temporary directory that is automatically cleaned up
once the task collection is is part of completes.
Use WorkDir if you do not want the directory to be deleted.
``` php
<?php
// Delete on rollback or on successful completion.
// Note that in this example, everything is deleted at
// the end of $collection->run().
$collection = $this->collectionBuilder();
$tmpPath = $collection->tmpDir()->getPath();
$collection->taskFilesystemStack()
->mkdir("$tmpPath/log")
->touch("$tmpPath/log/error.txt");
$collection->run();
// as shortcut (deleted when program exits)
$tmpPath = $this->_tmpDir();
?>
```
* `cwd($shouldChangeWorkingDirectory = null)` Flag that we should cwd to the temporary directory when it is
* `complete()` Delete this directory when our collection completes.
* `getPath()` Get a reference to the path to the temporary directory, so that
## WorkDir
Create a temporary working directory that is automatically renamed to its
final desired location if all of the tasks in the collection succeed. If
there is a rollback, then the working directory is deleted.
``` php
<?php
$collection = $this->collectionBuilder();
$workingPath = $collection->workDir("build")->getPath();
$collection->taskFilesystemStack()
->mkdir("$workingPath/log")
->touch("$workingPath/log/error.txt");
$collection->run();
?>
```
* `complete()` Move our working directory into its final destination once the
* `rollback()` Delete our working directory
* `getPath()` Get a reference to the path to the temporary directory, so that
* `cwd($shouldChangeWorkingDirectory = null)` Flag that we should cwd to the temporary directory when it is

View File

@@ -0,0 +1,31 @@
# Gulp Tasks
## Run
Gulp Run
``` php
<?php
// simple execution
$this->taskGulpRun()->run();
// run task 'clean' with --silent option
$this->taskGulpRun('clean')
->silent()
->run();
?>
```
* `silent()` adds `silent` option to gulp
* `noColor()` adds `--no-color` option to gulp
* `color()` adds `--color` option to gulp
* `simple()` adds `--tasks-simple` option to gulp
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,54 @@
# Npm Tasks
## Install
Npm Install
``` php
<?php
// simple execution
$this->taskNpmInstall()->run();
// prefer dist with custom path
$this->taskNpmInstall('path/to/my/npm')
->noDev()
->run();
?>
```
* `noDev()` adds `production` option to npm
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Update
Npm Update
```php
<?php
// simple execution
$this->taskNpmUpdate()->run();
// prefer dist with custom path
$this->taskNpmUpdate('path/to/my/npm')
->noDev()
->run();
?>
```
* `noDev()` adds `production` option to npm
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,143 @@
# Remote Tasks
## Rsync
Executes rsync in a flexible manner.
``` php
$this->taskRsync()
->fromPath('src/')
->toHost('localhost')
->toUser('dev')
->toPath('/var/www/html/app/')
->remoteShell('ssh -i public_key')
->recursive()
->excludeVcs()
->checksum()
->wholeFile()
->verbose()
->progress()
->humanReadable()
->stats()
->run();
```
You could also clone the task and do a dry-run first:
``` php
$rsync = $this->taskRsync()
->fromPath('src/')
->toPath('example.com:/var/www/html/app/')
->archive()
->excludeVcs()
->progress()
->stats();
$dryRun = clone $rsync;
$dryRun->dryRun()->run();
if ('y' === $this->ask('Do you want to run (y/n)')) {
$rsync->run();
}
```
* ` fromUser(string $user)`
* ` fromHost(string $hostname)`
* ` toUser(string $user)`
* ` toHost(string $hostname)`
* `fromPath($path)` This can either be a full rsync path spec (user@host:path) or just a path.
* `toPath($path)` This can either be a full rsync path spec (user@host:path) or just a path.
* `fromUser($fromUser)` * `param string` $fromUser
* `fromHost($fromHost)` * `param string` $fromHost
* `toUser($toUser)` * `param string` $toUser
* `toHost($toHost)` * `param string` $toHost
* `progress()` * `return` $this
* `stats()` * `return` $this
* `recursive()` * `return` $this
* `verbose()` * `return` $this
* `checksum()` * `return` $this
* `archive()` * `return` $this
* `compress()` * `return` $this
* `owner()` * `return` $this
* `group()` * `return` $this
* `times()` * `return` $this
* `delete()` * `return` $this
* `timeout($seconds)` * `param int` $seconds
* `humanReadable()` * `return` $this
* `wholeFile()` * `return` $this
* `dryRun()` * `return` $this
* `itemizeChanges()` * `return` $this
* `excludeVcs()` Excludes .git, .svn and .hg items at any depth.
* `exclude($pattern)` * `param array|string` $pattern
* `excludeFrom($file)` * `param string` $file
* `includeFilter($pattern)` * `param array|string` $pattern
* `filter($pattern)` * `param array|string` $pattern
* `filesFrom($file)` * `param string` $file
* `remoteShell($command)` * `param string` $command
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Ssh
Runs multiple commands on a remote server.
Per default, commands are combined with &&, unless stopOnFail is false.
```php
<?php
$this->taskSshExec('remote.example.com', 'user')
->remoteDir('/var/www/html')
->exec('ls -la')
->exec('chmod g+x logs')
->run();
```
You can even exec other tasks (which implement CommandInterface):
```php
$gitTask = $this->taskGitStack()
->checkout('master')
->pull();
$this->taskSshExec('remote.example.com')
->remoteDir('/var/www/html/site')
->exec($gitTask)
->run();
```
You can configure the remote directory for all future calls:
```php
::configure('remoteDir', '/some-dir');
```
* `$this stopOnFail(bool $stopOnFail)` Whether or not to chain commands together with &&
and stop the chain if one command fails
* `$this remoteDir(string $remoteWorkingDirectory)` Changes to the given directory before running commands
* `hostname($hostname)` * `param string` $hostname
* `user($user)` * `param string` $user
* `stopOnFail($stopOnFail = null)` * `param bool` $stopOnFail
* `remoteDir($remoteDir)` * `param string` $remoteDir
* `identityFile($filename)` * `param string` $filename
* `port($port)` * `param int` $port
* `forcePseudoTty()` * `return` $this
* `quiet()` * `return` $this
* `verbose()` * `return` $this
* `exec($command)` * `param string|string[]|CommandInterface` $command
* `simulate($context)` {@inheritdoc}
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,171 @@
# Testing Tasks
## Atoum
Runs [atoum](http://atoum.org/) tests
``` php
<?php
$this->taskAtoum()
->files('path/to/test.php')
->configFile('config/dev.php')
->run()
?>
```
* `tags($tags)` Tag or Tags to filter.
* `lightReport()` Display result using the light reporter.
* `tap()` Display result using the tap reporter.
* `bootstrap($file)` Path to the bootstrap file.
* `configFile($file)` Path to the config file.
* `debug()` Use atoum's debug mode.
* `files($files)` Test file or test files to run.
* `directories($directories)` Test directory or directories to run.
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Behat
Executes Behat tests
``` php
<?php
$this->taskBehat()
->format('pretty')
->noInteraction()
->run();
?>
```
* `stopOnFail()` * `return` $this
* `noInteraction()` * `return` $this
* `config($config_file)` * `param` $config_file
* `colors()` * `return` $this
* `noColors()` * `return` $this
* `suite($suite)` * `param string` $suite
* `verbose($level = null)` * `param string` $level
* `format($formater)` * `param string` $formater
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Codecept
Executes Codeception tests
``` php
<?php
// config
$this->taskCodecept()
->suite('acceptance')
->env('chrome')
->group('admin')
->xml()
->html()
->run();
?>
```
* `suite($suite)` * `param string` $suite
* `test($testName)` * `param string` $testName
* `group($group)` set group option. Can be called multiple times
* `excludeGroup($group)` * `param string` $group
* `json($file = null)` generate json report
* `xml($file = null)` generate xml JUnit report
* `html($dir = null)` Generate html report
* `tap($file = null)` generate tap report
* `configFile($file)` provides config file other then default `codeception.yml` with `-c` option
* `coverage($cov = null)` collect codecoverage in raw format. You may pass name of cov file to save results
* `silent()` execute in silent mode
* `coverageXml($xml = null)` collect code coverage in xml format. You may pass name of xml file to save results
* `coverageHtml($html = null)` collect code coverage and generate html report. You may pass
* `env($env)` * `param string` $env
* `debug()` * `return` $this
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## PHPUnit
Runs PHPUnit tests
``` php
<?php
$this->taskPHPUnit()
->group('core')
->bootstrap('test/bootstrap.php')
->run()
?>
```
* `filter($filter)` * `param string` $filter
* `group($group)` * `param string` $group
* `excludeGroup($group)` * `param string` $group
* `json($file = null)` adds `log-json` option to runner
* `xml($file = null)` adds `log-junit` option
* `tap($file = null)` * `param string` $file
* `bootstrap($file)` * `param string` $file
* `configFile($file)` * `param string` $file
* `debug()` * `return` $this
* `files($files)` Directory of test files or single test file to run.
* `file($file)` Test the provided file.
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.
## Phpspec
Executes Phpspec tests
``` php
<?php
$this->taskPhpspec()
->format('pretty')
->noInteraction()
->run();
?>
```
* `stopOnFail()`
* `noCodeGeneration()`
* `quiet()`
* `verbose($level = null)`
* `noAnsi()`
* `noInteraction()`
* `config($config_file)`
* `format($formater)`
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
* `arg($arg)` Pass argument to executable. Its value will be automatically escaped.
* `args($args)` Pass methods parameters as arguments to executable. Argument values
* `rawArg($arg)` Pass the provided string in its raw (as provided) form as an argument to executable.
* `option($option, $value = null)` Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* `optionList($option, $value = null)` Pass multiple options to executable. Value can be a string or array.

View File

@@ -0,0 +1,108 @@
# Vcs Tasks
## GitStack
Runs Git commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
``` php
<?php
$this->taskGitStack()
->stopOnFail()
->add('-A')
->commit('adding everything')
->push('origin','master')
->tag('0.6.0')
->push('origin','0.6.0')
->run()
$this->taskGitStack()
->stopOnFail()
->add('doc/*')
->commit('doc updated')
->push()
->run();
?>
```
* `cloneRepo($repo, $to = null)` Executes `git clone`
* `add($pattern)` Executes `git add` command with files to add pattern
* `commit($message, $options = null)` Executes `git commit` command with a message
* `pull($origin = null, $branch = null)` Executes `git pull` command.
* `push($origin = null, $branch = null)` Executes `git push` command
* `merge($branch)` Performs git merge
* `checkout($branch)` Executes `git checkout` command
* `tag($tag_name, $message = null)` Executes `git tag` command
* `executable($executable)` * `param string` $executable
* `exec($command)` * `param string|string[]` $command
* `stopOnFail($stopOnFail = null)` * `param bool` $stopOnFail
* `result($result)`
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
## HgStack
Runs hg commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
``` php
<?php
$this->hgStack
->cloneRepo('https://bitbucket.org/durin42/hgsubversion')
->pull()
->add()
->commit('changed')
->push()
->tag('0.6.0')
->push('0.6.0')
->run();
?>
```
* `cloneRepo($repo, $to = null)` Executes `hg clone`
* `add($include = null, $exclude = null)` Executes `hg add` command with files to add by pattern
* `commit($message, $options = null)` Executes `hg commit` command with a message
* `pull($branch = null)` Executes `hg pull` command.
* `push($branch = null)` Executes `hg push` command
* `merge($revision = null)` Performs hg merge
* `tag($tag_name, $message = null)` Executes `hg tag` command
* `executable($executable)` * `param string` $executable
* `exec($command)` * `param string|string[]` $command
* `stopOnFail($stopOnFail = null)` * `param bool` $stopOnFail
* `result($result)`
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed
## SvnStack
Runs Svn commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
``` php
<?php
$this->taskSvnStack()
->checkout('http://svn.collab.net/repos/svn/trunk')
->run()
// alternatively
$this->_svnCheckout('http://svn.collab.net/repos/svn/trunk');
$this->taskSvnStack('username', 'password')
->stopOnFail()
->update()
->add('doc/*')
->commit('doc updated')
->run();
?>
```
* `update($path = null)` Updates `svn update` command
* `add($pattern = null)` Executes `svn add` command with files to add pattern
* `commit($message, $options = null)` Executes `svn commit` command with a message
* `checkout($branch)` Executes `svn checkout` command
* `executable($executable)` * `param string` $executable
* `exec($command)` * `param string|string[]` $command
* `stopOnFail($stopOnFail = null)` * `param bool` $stopOnFail
* `result($result)`
* `dir($dir)` Changes working directory of command
* `printed($arg)` Should command output be printed

View File

@@ -0,0 +1,425 @@
<?php
use Robo\Result;
use Robo\ResultData;
use Robo\Collection\CollectionBuilder;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Consolidation\OutputFormatters\StructuredData\PropertyList;
/**
* Example RoboFile.
*
* To test:
*
* $ cd ROBO_PROJECT/examples
* $ ../robo try:success
*
* - or -
*
* $ cd ROBO_PROJECT
* $ ./robo -f examples try:formatters
*/
class RoboFile extends \Robo\Tasks
{
/**
* Watch a file.
*
* Demonstrates the 'watch' command. Runs 'composer update' any time
* composer.json changes.
*/
public function tryWatch()
{
$this->taskWatch()->monitor(['composer.json', 'composer.lock'], function () {
$this->taskComposerUpdate()->run();
})->run();
}
/**
* Demonstrates Robo input APIs.
*/
public function tryInput()
{
$answer = $this->ask('how are you?');
$this->say('You are '.$answer);
$yes = $this->confirm('Do you want one more question?');
if (!$yes) {
return Result::cancelled();
}
$lang = $this->askDefault('what is your favorite scripting language?', 'PHP');
$this->say($lang);
$pin = $this->askHidden('Ok, now tell your PIN code (it is hidden)');
$this->yell('Ha-ha, your pin code is: '.$pin);
$this->say('Bye!');
}
/**
* Demonstrates parallel execution.
*
* @option $printed Print the output of each process.
* @option $error Include an extra process that fails.
*/
public function tryPara($options = ['printed' => false, 'error' => false])
{
$dir = __DIR__;
$para = $this->taskParallelExec()
->printed($options['printed'])
->process("php $dir/tests/_data/parascript.php hey 4")
->process("php $dir/tests/_data/parascript.php hoy 3")
->process("php $dir/tests/_data/parascript.php gou 2")
->process("php $dir/tests/_data/parascript.php die 1");
if ($options['error']) {
$para->process("ls $dir/tests/_data/filenotfound");
}
return $para->run();
}
/**
* Demonstrates Robo argument passing.
*
* @param string $a The first parameter. Required.
* @param string $b The second parameter. Optional.
*/
public function tryArgs($a, $b = 'default')
{
$this->say("The parameter a is $a and b is $b");
}
/**
* Demonstrate Robo variable argument passing.
*
* @param $a A list of commandline parameters.
*/
public function tryArrayArgs(array $a)
{
$this->say("The parameters passed are:\n" . var_export($a, true));
}
/**
* Demonstrate Robo boolean options.
*
* @param $opts The options.
* @option boolean $silent Supress output.
*/
public function tryOptbool($opts = ['silent|s' => false])
{
if (!$opts['silent']) {
$this->say("Hello, world");
}
}
/**
* Demonstrate the use of the PHP built-in webserver.
*/
public function tryServer()
{
return $this->taskServer(8000)
->dir('site')
->arg('site/index.php')
->run();
}
/**
* Demonstrate the use of the Robo open-browser task.
*/
public function tryOpenBrowser()
{
return $this->taskOpenBrowser([
'http://robo.li',
'https://github.com/consolidation-org/Robo'
])->run();
}
/**
* Demonstrate Robo error output and command failure.
*/
public function tryError()
{
return $this->taskExec('ls xyzzy' . date('U'))->dir('/tmp')->run();
}
/**
* Demonstrate Robo standard output and command success.
*/
public function trySuccess()
{
return $this->_exec('pwd');
}
/**
* @field-labels
* name: Name
* species: Species
* legs: Legs
* food: Favorite Food
* id: Id
* @return PropertyList
*/
public function tryInfo()
{
$outputData = [
'name' => 'fluffy',
'species' => 'cat',
'legs' => 4,
'food' => 'salmon',
'id' => 389245032,
];
$data = new PropertyList($outputData);
// Add a render function to transform cell data when the output
// format is a table, or similar. This allows us to add color
// information to the output without modifying the data cells when
// using yaml or json output formats.
$data->addRendererFunction(
// n.b. There is a fourth parameter $rowData that may be added here.
function ($key, $cellData, FormatterOptions $options) {
if ($key == 'name') {
return "<info>$cellData</>";
}
return $cellData;
}
);
return $data;
}
/**
* Demonstrate Robo formatters. Default format is 'table'.
*
* @field-labels
* first: I
* second: II
* third: III
* @default-string-field second
* @usage try:formatters --format=yaml
* @usage try:formatters --format=csv
* @usage try:formatters --fields=first,third
* @usage try:formatters --fields=III,II
* @aliases tf
*
* @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
*/
public function tryFormatters($somthing = 'default', $options = ['format' => 'table', 'fields' => ''])
{
$outputData = [
'en' => [ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ],
'de' => [ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ],
'jp' => [ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ],
'es' => [ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ],
];
return new RowsOfFields($outputData);
}
/**
* Demonstrate an alter hook with an option
*
* @hook alter try:formatters
* @option $french Add a row with French numbers.
* @usage try:formatters --french
*/
public function alterFormatters($result, CommandData $commandData)
{
if ($commandData->input()->getOption('french')) {
$result['fr'] = [ 'first' => 'Un', 'second' => 'Deux', 'third' => 'Trois' ];
}
return $result;
}
/**
* Demonstrate what happens when a command or a task
* throws an exception. Note that typically, Robo commands
* should return Result objects rather than throw exceptions.
*/
public function tryException($options = ['task' => false])
{
if (!$options['task']) {
throw new RuntimeException('Command failed with an exception.');
}
return new ExceptionTask('Task failed with an exception.');
}
/**
* Demonstrate deprecated task behavior.
*
* Demonstrate what happens when using a task that is created via
* direct instantiation, which omits initialization done by the
* container. Emits a warning message.
*/
public function tryDeprecated()
{
// Calling 'new' directly without manually setting
// up dependencies will result in a deprecation warning.
// @see RoboFile::trySuccess()
return (new \Robo\Task\Base\Exec('pwd'))->run();
}
/**
* Demonstrate the use of a collection builder to chain multiple tasks
* together into a collection, which is executed once constructed.
*
* For demonstration purposes only; this could, of course, be done
* with a single FilesystemStack.
*/
public function tryBuilder()
{
return $this->collectionBuilder()
->taskFilesystemStack()
->mkdir('a')
->touch('a/a.txt')
->taskFilesystemStack()
->mkdir('a/b')
->touch('a/b/b.txt')
->taskFilesystemStack()
->mkdir('a/b/c')
->touch('a/b/c/c.txt')
->run();
}
public function tryBuilderRollback()
{
// This example will create two builders, and add
// the first one as a child of the second in order
// to demonstrate nested rollbacks.
$collection = $this->collectionBuilder()
->taskFilesystemStack()
->mkdir('g')
->touch('g/g.txt')
->rollback(
$this->taskDeleteDir('g')
)
->taskFilesystemStack()
->mkdir('g/h')
->touch('g/h/h.txt')
->taskFilesystemStack()
->mkdir('g/h/i/c')
->touch('g/h/i/i.txt');
return $this->collectionBuilder()
->progressMessage('Start recursive collection')
->addTask($collection)
->progressMessage('Done with recursive collection')
->taskExec('ls xyzzy' . date('U'))
->dir('/tmp')
->run();
}
public function tryWorkdir()
{
// This example works like tryBuilderRollback,
// but does equivalent operations using a working
// directory. The working directory is deleted on rollback
$collection = $this->collectionBuilder();
$workdir = $collection->workDir('w');
$collection
->taskFilesystemStack()
->touch("$workdir/g.txt")
->taskFilesystemStack()
->mkdir("$workdir/h")
->touch("$workdir/h/h.txt")
->taskFilesystemStack()
->mkdir("$workdir/h/i/c")
->touch("$workdir/h/i/i.txt");
return $this->collectionBuilder()
->progressMessage('Start recursive collection')
->addTask($collection)
->progressMessage('Done with recursive collection')
->taskExec('ls xyzzy' . date('U'))
->dir('/tmp')
->run();
}
/**
* Demonstrates Robo temporary directory usage.
*/
public function tryTmpDir()
{
// Set up a collection to add tasks to
$collection = $this->collectionBuilder();
// Get a temporary directory to work in. Note that we get a path
// back, but the directory is not created until the task runs.
$tmpPath = $collection->tmpDir();
$result = $collection
->taskWriteToFile("$tmpPath/file.txt")
->line('Example file')
->run();
if (is_dir($tmpPath)) {
$this->say("The temporary directory at $tmpPath was not cleaned up after the collection completed.");
} else {
$this->say("The temporary directory at $tmpPath was automatically deleted.");
}
return $result;
}
/**
* Description
* @param $options
* @option delay Miliseconds delay
* @return type
*/
public function tryProgress($options = ['delay' => 500])
{
$delay = $options['delay'];
$delayUntilProgressStart = \Robo\Robo::config()->get(\Robo\Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL);
$this->say("Progress bar will display after $delayUntilProgressStart seconds of activity.");
$processList = range(1, 10);
return $this->collectionBuilder()
->taskForEach($processList)
->iterationMessage('Processing {value}')
->call(
function ($value) use($delay) {
// TaskForEach::call should only be used to do
// non-Robo operations. To use Robo tasks in an
// iterator, @see TaskForEach::withBuilder.
usleep($delay * 1000); // delay units: msec, usleep units: usec
}
)
->run();
}
public function tryIter()
{
$workdir = 'build/iter-example';
$this->say("Creating sample direcories in $workdir.");
$processList = ['cats', 'dogs', 'sheep', 'fish', 'horses', 'cows'];
return $this->collectionBuilder()
->taskFilesystemStack()
->mkdir($workdir)
->taskCleanDir($workdir)
->taskForEach($processList)
->withBuilder(
function ($builder, $key, $value) use ($workdir) {
return $builder
->taskFilesystemStack()
->mkdir("$workdir/$value");
}
)
->run();
}
}
class ExceptionTask extends \Robo\Task\BaseTask
{
protected $message;
public function __construct($message)
{
$this->message = $message;
}
public function run()
{
throw new RuntimeException($this->message);
}
}

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env robo
<?php
/**
* Robo script.
*
* This file may be executed from the shell as if it were a bash script.
*
* Example:
*
* $ ./robo.script foo bar
* ➜ This is a Robo script, bar
*
* If the script has only one command, then you may remove the need to
* specify it on the commandline by naming it on the `#!` line.
*
* e.g. if the first line is `#!/usr/bin/env robo foo`:
*
* $ ./robo.script bar
* ➜ This is a Robo script, bar
*
* Note that in order for Robo scripts to work, the 'robo' application
* must be in your $PATH. Usually, this is done by installing
* Robo via `composer global require`, and placing ~/.composer/vendor/bin
* on your $PATH. Robo libraries that are also installed via `composer global
* require` will be available for use in your Robo scripts, provided that
* they are loaded via the `getServiceProviders` method of the script.
*
* See also: https://github.com/consolidation-org/cgr
*/
class MyRoboScript extends \Robo\Tasks
{
/**
* Foo
*
* A demonstration of a command in a Robo script.
*
* @param string $name a name that is printed.
*/
public function foo($name)
{
$this->say("This is a Robo script, $name");
}
}

21
lib/composer/vendor/consolidation/robo/robo vendored Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
/**
* if we're running from phar load the phar autoload,
* else let the script 'robo' search for the autoloader
*/
if (strpos(basename(__FILE__), 'phar')) {
require_once 'phar://robo.phar/vendor/autoload.php';
} else {
if (file_exists(__DIR__.'/vendor/autoload.php')) {
require_once __DIR__.'/vendor/autoload.php';
} elseif (file_exists(__DIR__.'/../../autoload.php')) {
require_once __DIR__ . '/../../autoload.php';
} else {
require_once 'phar://robo.phar/vendor/autoload.php';
}
}
$runner = new \Robo\Runner();
$statusCode = $runner->execute($_SERVER['argv']);
exit($statusCode);

View File

@@ -0,0 +1,58 @@
<?php
/**
* @file
* Contains \Robo\composer\ScriptHandler.
*/
namespace Robo\composer;
use Composer\Script\Event;
use Symfony\Component\Filesystem\Filesystem;
class ScriptHandler
{
/**
* Run prior to `composer installl` when a composer.lock is present.
* @param Event $event
*/
public static function checkDependencies(Event $event)
{
if (version_compare(PHP_VERSION, '5.6.0') < 0) {
static::checkDependenciesFor55();
}
}
/**
* Check to see if the dependencies in composer.lock are compatible
* with php 5.5.
*/
protected static function checkDependenciesFor55()
{
$fs = new Filesystem();
if (!$fs->exists('composer.lock')) {
return;
}
$composerLockContents = file_get_contents('composer.lock');
if (preg_match('#"php":.*(5\.6)#', $composerLockContents)) {
static::fixDependenciesFor55();
}
}
protected static function fixDependenciesFor55()
{
$fs = new Filesystem();
$status = 0;
$fs->remove('composer.lock');
// Composer has already read our composer.json file, so we will
// need to run in a new process to fix things up.
passthru('composer install --ansi', $status);
// Don't continue with the initial 'composer install' command
exit($status);
}
}

View File

@@ -0,0 +1,56 @@
<?php
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
{
/**
* @param string $name
* @param string $version
*/
public function __construct($name, $version)
{
parent::__construct($name, $version);
$this->getDefinition()
->addOption(
new InputOption('--simulate', null, InputOption::VALUE_NONE, 'Run in simulated mode (show what would have happened).')
);
$this->getDefinition()
->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)
);
}
/**
* @param string $roboFile
* @param string $roboClass
*/
public function addInitRoboFileCommand($roboFile, $roboClass)
{
$createRoboFile = new Command('init');
$createRoboFile->setDescription("Intitalizes basic RoboFile in current dir");
$createRoboFile->setCode(function () use ($roboClass, $roboFile) {
$output = Robo::output();
$output->writeln("<comment> ~~~ Welcome to Robo! ~~~~ </comment>");
$output->writeln("<comment> ". basename($roboFile) ." will be created in the current directory </comment>");
file_put_contents(
$roboFile,
'<?php'
. "\n/**"
. "\n * This is project's console commands configuration for Robo task runner."
. "\n *"
. "\n * @see http://robo.li/"
. "\n */"
. "\nclass " . $roboClass . " extends \\Robo\\Tasks\n{\n // define public methods as commands\n}"
);
$output->writeln("<comment> Edit this file to add your commands! </comment>");
});
$this->add($createRoboFile);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Robo\Collection;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Collection\Collection;
/**
* Creates a task wrapper that converts any Callable into an
* object that can be used directly with a task collection.
*
* It is not necessary to use this class directly; Collection will
* automatically wrap Callables when they are added.
*/
class CallableTask implements TaskInterface
{
/**
* @var callable
*/
protected $fn;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $reference;
public function __construct(callable $fn, TaskInterface $reference)
{
$this->fn = $fn;
$this->reference = $reference;
}
/**
* @return \Robo\Result
*/
public function run()
{
$result = call_user_func($this->fn);
// If the function returns no result, then count it
// as a success.
if (!isset($result)) {
$result = Result::success($this->reference);
}
// If the function returns a result, it must either return
// a \Robo\Result or an exit code. In the later case, we
// convert it to a \Robo\Result.
if (!$result instanceof Result) {
$result = new Result($this->reference, $result);
}
return $result;
}
}

View File

@@ -0,0 +1,683 @@
<?php
namespace Robo\Collection;
use Robo\Result;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
use Robo\Task\StackBasedTask;
use Robo\Task\BaseTask;
use Robo\TaskInfo;
use Robo\Contract\WrappedTaskInterface;
use Robo\Exception\TaskException;
use Robo\Exception\TaskExitException;
use Robo\Contract\CommandInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\InflectionInterface;
/**
* Group tasks into a collection that run together. Supports
* rollback operations for handling error conditions.
*
* This is an internal class. Clients should use a CollectionBuilder
* rather than direct use of the Collection class. @see CollectionBuilder.
*
* Below, the example FilesystemStack task is added to a collection,
* and associated with a rollback task. If any of the operations in
* the FilesystemStack, or if any of the other tasks also added to
* the task collection should fail, then the rollback function is
* called. Here, taskDeleteDir is used to remove partial results
* of an unfinished task.
*/
class Collection extends BaseTask implements CollectionInterface, CommandInterface
{
/**
* @var \Robo\Collection\Element[]
*/
protected $taskList = [];
/**
* @var TaskInterface[]
*/
protected $rollbackStack = [];
/**
* @var TaskInterface[]
*/
protected $completionStack = [];
/**
* @var CollectionInterface
*/
protected $parentCollection;
/**
* Constructor.
*/
public function __construct()
{
}
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) {
$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);
}
} 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);
}
$taskResult = $task->run();
return $taskResult;
}
/**
* @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();
}
}

View File

@@ -0,0 +1,475 @@
<?php
namespace Robo\Collection;
use Guzzle\Inflection\InflectorInterface;
use Robo\Config;
use Robo\Common\Timer;
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;
/**
* Creates a collection, and adds tasks to it. The collection builder
* offers a streamlined chained-initialization mechanism for easily
* creating task groups. Facilities for creating working and temporary
* directories are also provided.
*
* ``` php
* <?php
* $result = $this->collectionBuilder()
* ->taskFilesystemStack()
* ->mkdir('g')
* ->touch('g/g.txt')
* ->rollback(
* $this->taskDeleteDir('g')
* )
* ->taskFilesystemStack()
* ->mkdir('g/h')
* ->touch('g/h/h.txt')
* ->taskFilesystemStack()
* ->mkdir('g/h/i/c')
* ->touch('g/h/i/i.txt')
* ->run()
* ?>
*
* In the example above, the `taskDeleteDir` will be called if
* ```
*/
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface
{
/**
* @var \Robo\Tasks
*/
protected $commandFile;
/**
* @var CollectionInterface
*/
protected $collection;
/**
* @var TaskInterface
*/
protected $currentTask;
/**
* @var bool
*/
protected $simulated;
/**
* @param \Robo\Tasks $commandFile
*/
public function __construct($commandFile)
{
$this->commandFile = $commandFile;
}
/**
* @param bool $simulated
*
* @return $this
*/
public function simulated($simulated = true)
{
$this->simulated = $simulated;
return $this;
}
/**
* @return bool
*/
public function isSimulated()
{
if (!isset($this->simulated)) {
$this->simulated = $this->getConfig()->get(Config::SIMULATE);
}
return $this->simulated;
}
/**
* Create a temporary directory to work in. When the collection
* completes or rolls back, the temporary directory will be deleted.
* Returns the path to the location where the directory will be
* created.
*
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return string
*/
public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
// n.b. Any task that the builder is asked to create is
// automatically added to the builder's collection, and
// wrapped in the builder object. Therefore, the result
// of any call to `taskFoo()` from within the builder will
// always be `$this`.
return $this->taskTmpDir($prefix, $base, $includeRandomPart)->getPath();
}
/**
* Create a working directory to hold results. A temporary directory
* is first created to hold the intermediate results. After the
* builder finishes, the work directory is moved into its final location;
* any results already in place will be moved out of the way and
* then deleted.
*
* @param string $finalDestination The path where the working directory
* will be moved once the task collection completes.
*
* @return string
*/
public function workDir($finalDestination)
{
// Creating the work dir task in this context adds it to our task collection.
return $this->taskWorkDir($finalDestination)->getPath();
}
public function addTask(TaskInterface $task)
{
$this->getCollection()->add($task);
return $this;
}
public function addCode(callable $code)
{
$this->getCollection()->addCode($code);
return $this;
}
/**
* 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)
{
$this->getCollection()->addTaskList($tasks);
return $this;
}
public function rollback(TaskInterface $task)
{
// Ensure that we have a collection if we are going to add
// a rollback function.
$this->getCollection()->rollback($task);
return $this;
}
public function rollbackCode(callable $rollbackCode)
{
$this->getCollection()->rollbackCode($rollbackCode);
return $this;
}
public function completion(TaskInterface $task)
{
$this->getCollection()->completion($task);
return $this;
}
public function completionCode(callable $completionCode)
{
$this->getCollection()->completionCode($completionCode);
return $this;
}
/**
* @param string $text
* @param array $context
* @param string $level
*
* @return $this
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
{
$this->getCollection()->progressMessage($text, $context, $level);
return $this;
}
/**
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->getCollection()->setParentCollection($parentCollection);
return $this;
}
/**
* Called by the factory method of each task; adds the current
* task to the task builder.
*
* TODO: protected
*
* @param TaskInterface $task
*
* @return $this
*/
public function addTaskToCollection($task)
{
// Postpone creation of the collection until the second time
// we are called. At that time, $this->currentTask will already
// be populated. We call 'getCollection()' so that it will
// create the collection and add the current task to it.
// Note, however, that if our only tasks implements NestedCollectionInterface,
// then we should force this builder to use a collection.
if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
$this->getCollection();
}
$this->currentTask = $task;
if ($this->collection) {
$this->collection->add($task);
}
return $this;
}
/**
* Return the current task for this collection builder.
* TODO: Not needed?
*
* @return \Robo\Contract\TaskInterface
*/
public function getCollectionBuilderCurrentTask()
{
return $this->currentTask;
}
/**
* Create a new builder with its own task collection
*
* @return CollectionBuilder
*/
public function newBuilder()
{
$collectionBuilder = new self($this->commandFile);
$collectionBuilder->inflect($this);
$collectionBuilder->simulated($this->isSimulated());
return $collectionBuilder;
}
/**
* Calling the task builder with methods of the current
* task calls through to that method of the task.
*
* There is extra complexity in this function that could be
* simplified if we attached the 'LoadAllTasks' and custom tasks
* to the collection builder instead of the RoboFile. While that
* change would be a better design overall, it would require that
* the user do a lot more work to set up and use custom tasks.
* We therefore take on some additional complexity here in order
* to allow users to maintain their tasks in their RoboFile, which
* is much more convenient.
*
* Calls to $this->collectionBuilder()->taskFoo() cannot be made
* directly because all of the task methods are protected. These
* calls will therefore end up here. If the method name begins
* with 'task', then it is eligible to be used with the builder.
*
* When we call getBuiltTask, below, it will use the builder attached
* to the commandfile to build the task. However, this is not what we
* want: the task needs to be built from THIS collection builder, so that
* it will be affected by whatever state is active in this builder.
* To do this, we have two choices: 1) save and restore the builder
* in the commandfile, or 2) clone the commandfile and set this builder
* on the copy. 1) is vulnerable to failure in multithreaded environments
* (currently not supported), while 2) might cause confusion if there
* is shared state maintained in the commandfile, which is in the
* domain of the user.
*
* Note that even though we are setting up the commandFile to
* use this builder, getBuiltTask always creates a new builder
* (which is constructed using all of the settings from the
* commandFile's builder), and the new task is added to that.
* We therefore need to transfer the newly built task into this
* builder. The temporary builder is discarded.
*
* @param string $fn
* @param array $args
*
* @return $this|mixed
*/
public function __call($fn, $args)
{
if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
$saveBuilder = $this->commandFile->getBuilder();
$this->commandFile->setBuilder($this);
$temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
$this->commandFile->setBuilder($saveBuilder);
if (!$temporaryBuilder) {
throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
}
$temporaryBuilder->getCollection()->transferTasks($this);
return $this;
}
if (!isset($this->currentTask)) {
throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
}
// If the method called is a method of the current task,
// then call through to the current task's setter method.
$result = call_user_func_array([$this->currentTask, $fn], $args);
// If something other than a setter method is called, then return its result.
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
if (isset($result) && ($result !== $currentTask)) {
return $result;
}
return $this;
}
/**
* Construct the desired task and add it to this builder.
*
* @param string|object $name
* @param array $args
*
* @return \Robo\Collection\CollectionBuilder
*/
public function build($name, $args)
{
$reflection = new ReflectionClass($name);
$task = $reflection->newInstanceArgs($args);
if (!$task) {
throw new RuntimeException("Can not construct task $name");
}
$task = $this->fixTask($task, $args);
return $this->addTaskToCollection($task);
}
/**
* @param InflectionInterface $task
* @param array $args
*
* @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
*/
protected function fixTask($task, $args)
{
$task->inflect($this);
if ($task instanceof BuilderAwareInterface) {
$task->setBuilder($this);
}
// Do not wrap our wrappers.
if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
return $task;
}
// Remember whether or not this is a task before
// it gets wrapped in any decorator.
$isTask = $task instanceof TaskInterface;
$isCollection = $task instanceof NestedCollectionInterface;
// If the task implements CompletionInterface, ensure
// that its 'complete' method is called when the application
// terminates -- but only if its 'run' method is called
// first. If the task is added to a collection, then the
// task will be unwrapped via its `original` method, and
// it will be re-wrapped with a new completion wrapper for
// its new collection.
if ($task instanceof CompletionInterface) {
$task = new CompletionWrapper(Temporary::getCollection(), $task);
}
// If we are in simulated mode, then wrap any task in
// a TaskSimulator.
if ($isTask && !$isCollection && ($this->isSimulated())) {
$task = new \Robo\Task\Simulator($task, $args);
$task->inflect($this);
}
return $task;
}
/**
* When we run the collection builder, run everything in the collection.
*
* @return \Robo\Result
*/
public function run()
{
$this->startTimer();
$result = $this->runTasks();
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* If there is a single task, run it; if there is a collection, run
* all of its tasks.
*
* @return \Robo\Result
*/
protected function runTasks()
{
if (!$this->collection && $this->currentTask) {
return $this->currentTask->run();
}
return $this->getCollection()->run();
}
/**
* @return string
*/
public function getCommand()
{
if (!$this->collection && $this->currentTask) {
$task = $this->currentTask;
$task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
if ($task instanceof CommandInterface) {
return $task->getCommand();
}
}
return $this->getCollection()->getCommand();
}
/**
* @return \Robo\Collection\Collection
*/
public function original()
{
return $this->getCollection();
}
/**
* Return the collection of tasks associated with this builder.
*
* @return CollectionInterface
*/
public function getCollection()
{
if (!isset($this->collection)) {
$this->collection = new Collection();
$this->collection->inflect($this);
$this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
if (isset($this->currentTask)) {
$this->collection->add($this->currentTask);
}
}
return $this->collection;
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Robo\Collection;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
interface CollectionInterface extends NestedCollectionInterface
{
/**
* Unnamed tasks are assigned an arbitrary numeric index
* in the task list. Any numeric value may be used, but the
* UNNAMEDTASK constant is recommended for clarity.
*
* @var int
*/
const UNNAMEDTASK = 0;
/**
* Add a task or a list of tasks to our task collection. Each task
* will run via its 'run()' method once (and if) all of the tasks
* added before it complete successfully. If the task also implements
* RollbackInterface, then it will be rolled back via its 'rollback()'
* method ONLY if its 'run()' method completes successfully, and some
* task added after it fails.
*
* @param TaskInterface $task
* The task to add to our collection.
* @param int|string $name
* An optional name for the task -- missing or UNNAMEDTASK for unnamed tasks.
* Names are used for positioning before and after tasks.
*
* @return CollectionInterface
*/
public function add(TaskInterface $task, $name = self::UNNAMEDTASK);
/**
* Add arbitrary code to execute as a task.
*
* @param callable $code Code to execute as a task
* @param int|string $name
* An optional name for the task -- missing or UNNAMEDTASK for unnamed tasks.
* Names are used for positioning before and after tasks.
*
* @return $this
*/
public function addCode(callable $code, $name = self::UNNAMEDTASK);
/**
* Add arbitrary code that will be called once for every item in the
* provided array or iterable object. If the function result of the
* provided callback is a TaskInterface or Collection, then it will be
* executed.
*
* @param CollectionInterface|array $iterable A collection of things to iterate
* @param $code $code A callback function to call for each item in the collection.
*
* @return $this
*/
public function addIterable($iterable, callable $code);
/**
* Add a rollback task to our task collection. A rollback task
* will execute ONLY if all of the tasks added before it complete
* successfully, AND some task added after it fails.
*
* @param TaskInterface $rollbackTask
* The rollback task to add. Note that the 'run()' method of the
* task executes, not its 'rollback()' method. To use the 'rollback()'
* method, add the task via 'Collection::add()' instead.
*
* @return $this
*/
public function rollback(TaskInterface $rollbackTask);
/**
* Add arbitrary code to execute as a rollback.
*
* @param callable $rollbackTask Code to execute during rollback processing
*
* @return $this
*/
public function rollbackCode(callable $rollbackTask);
/**
* Add a completion task to our task collection. A completion task
* will execute EITHER after all tasks succeed, OR immediatley after
* any task fails. Completion tasks never cause errors to be returned
* from Collection::run(), even if they fail.
*
* @param TaskInterface $completionTask
* The completion task to add. Note that the 'run()' method of the
* task executes, just as if the task was added normally.
*
* @return $this
*/
public function completion(TaskInterface $completionTask);
/**
* Add arbitrary code to execute as a completion.
*
* @param callable $completionTask Code to execute after collection completes
*
* @return $this
*/
public function completionCode(callable $completionTask);
/**
* Add a task before an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
* @param callable|TaskInterface $task
* The task to add.
* @param int|string $nameOfTaskToAdd
* The name of the task to add. If not provided, will be associated
* with the named task it was added before.
*
* @return $this
*/
public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK);
/**
* Add a task after an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
* @param callable|TaskInterface $task
* The task to add.
* @param int|string $nameOfTaskToAdd
* The name of the task to add. If not provided, will be associated
* with the named task it was added after.
*
* @return $this
*/
public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK);
/**
* Print a progress message after Collection::run() has executed
* all of the tasks that were added prior to the point when this
* method was called. If one of the previous tasks fail, then this
* message will not be printed.
*
* @param string $text Message to print.
* @param array $context Extra context data for use by the logger.
* @param \Psr\Log\LogLevel|string $level The log level to print the information at. Default is NOTICE.
*
* @return $this
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE);
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Robo\Collection;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
use Consolidation\AnnotatedCommand\CommandData;
use Robo\Contract\TaskInterface;
use Robo\Result;
/**
* The collection process hook is added to the annotation command
* hook manager in Runner::configureContainer(). This hook will be
* called every time a command runs. If the command result is a
* \Robo\Contract\TaskInterface (in particular, \Robo\Collection\Collection),
* then we run the collection, and return the result. We ignore results
* of any other type.
*/
class CollectionProcessHook implements ProcessResultInterface
{
/**
* @param \Robo\Result|\Robo\Contract\TaskInterface $result
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
*
* @return null|\Robo\Result
*/
public function process($result, CommandData $commandData)
{
if ($result instanceof TaskInterface) {
try {
return $result->run();
} catch (\Exception $e) {
return Result::fromException($result, $e);
}
}
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Robo\Collection;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
use Robo\Contract\RollbackInterface;
use Robo\Contract\CompletionInterface;
use Robo\Contract\WrappedTaskInterface;
/**
* Creates a task wrapper that will manage rollback and collection
* management to a task when it runs. Tasks are automatically
* wrapped in a CompletionWrapper when added to a task collection.
*
* Clients may need to wrap their task in a CompletionWrapper if it
* creates temporary objects.
*
* @see \Robo\Task\Filesystem\loadTasks::taskTmpDir
*/
class CompletionWrapper extends BaseTask implements WrappedTaskInterface
{
/**
* @var \Robo\Collection\Collection
*/
private $collection;
/**
* @var \Robo\Contract\TaskInterface
*/
private $task;
/**
* @var NULL|\Robo\Contract\TaskInterface
*/
private $rollbackTask;
/**
* Create a CompletionWrapper.
*
* Temporary tasks are always wrapped in a CompletionWrapper, as are
* any tasks that are added to a collection. If a temporary task
* is added to a collection, then it is first unwrapped from its
* CompletionWrapper (via its original() method), and then added to a
* new CompletionWrapper for the collection it is added to.
*
* In this way, when the CompletionWrapper is finally executed, the
* task's rollback and completion handlers will be registered on
* whichever collection it was registered on.
*
* @todo Why not CollectionInterface the type of the $collection argument?
*
* @param \Robo\Collection\Collection $collection
* @param \Robo\Contract\TaskInterface $task
* @param \Robo\Contract\TaskInterface|NULL $rollbackTask
*/
public function __construct(Collection $collection, TaskInterface $task, TaskInterface $rollbackTask = null)
{
$this->collection = $collection;
$this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->rollbackTask = $rollbackTask;
}
/**
* {@inheritdoc}
*/
public function original()
{
return $this->task;
}
/**
* Before running this task, register its rollback and completion
* handlers on its collection. The reason this class exists is to
* defer registration of rollback and completion tasks until 'run()' time.
*
* @return \Robo\Result
*/
public function run()
{
if ($this->rollbackTask) {
$this->collection->registerRollback($this->rollbackTask);
}
if ($this->task instanceof RollbackInterface) {
$this->collection->registerRollback(new CallableTask([$this->task, 'rollback'], $this->task));
}
if ($this->task instanceof CompletionInterface) {
$this->collection->registerCompletion(new CallableTask([$this->task, 'complete'], $this->task));
}
return $this->task->run();
}
/**
* Make this wrapper object act like the class it wraps.
*
* @param string $function
* @param array $args
*
* @return mixed
*/
public function __call($function, $args)
{
return call_user_func_array(array($this->task, $function), $args);
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Robo\Collection;
use Robo\Contract\TaskInterface;
use Robo\Contract\WrappedTaskInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
/**
* One element in a collection. Each element consists of a task
* all of its before tasks, and all of its after tasks.
*
* This class is internal to Collection; it should not be used directly.
*/
class Element
{
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @var array
*/
protected $before = [];
/**
* @var array
*/
protected $after = [];
public function __construct(TaskInterface $task)
{
$this->task = $task;
}
/**
* @param mixed $before
* @param string $name
*/
public function before($before, $name)
{
if ($name) {
$this->before[$name] = $before;
} else {
$this->before[] = $before;
}
}
/**
* @param mixed $after
* @param string $name
*/
public function after($after, $name)
{
if ($name) {
$this->after[$name] = $after;
} else {
$this->after[] = $after;
}
}
/**
* @return array
*/
public function getBefore()
{
return $this->before;
}
/**
* @return array
*/
public function getAfter()
{
return $this->after;
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function getTask()
{
return $this->task;
}
/**
* @return array
*/
public function getTaskList()
{
return array_merge($this->getBefore(), [$this->getTask()], $this->getAfter());
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
$steps = 0;
foreach ($this->getTaskList() as $task) {
if ($task instanceof WrappedTaskInterface) {
$task = $task->original();
}
// If the task is a ProgressIndicatorAwareInterface, then it
// will advance the progress indicator a number of times.
if ($task instanceof ProgressIndicatorAwareInterface) {
$steps += $task->progressIndicatorSteps();
}
// We also advance the progress indicator once regardless
// of whether it is progress-indicator aware or not.
$steps++;
}
return $steps;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Robo\Collection;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
interface NestedCollectionInterface
{
/**
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection);
}

View File

@@ -0,0 +1,198 @@
<?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;
/**
* Creates a task wrapper that converts any Callable into an
* object that will execute the callback once for each item in the
* provided collection.
*
* It is not necessary to use this class directly; Collection::addIterable
* will automatically create one when it is called.
*/
class TaskForEach extends BaseTask implements NestedCollectionInterface, BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var callable[]
*/
protected $functionStack = [];
/**
* @var callable[]
*/
protected $countingStack = [];
/**
* @var string
*/
protected $message;
/**
* @var array
*/
protected $context = [];
protected $iterable;
/**
* @var \Robo\Collection\NestedCollectionInterface
*/
protected $parentCollection;
public function __construct($iterable)
{
$this->iterable = $iterable;
}
/**
* @param string $message
* @param array $context
*
* @return $this
*/
public function iterationMessage($message, $context = [])
{
$this->message = $message;
$this->context = $context + ['name' => 'Progress'];
return $this;
}
/**
* @param int|string $key
* @param mixed $value
*/
protected function showIterationMessage($key, $value)
{
if ($this->message) {
$context = ['key' => $key, 'value' => $value];
$context += $this->context;
$context += TaskInfo::getTaskContext($this);
$this->printTaskInfo($this->message, $context);
}
}
/**
* @param callable $fn
*
* @return $this
*/
public function withEachKeyValueCall(callable $fn)
{
$this->functionStack[] = $fn;
return $this;
}
/**
* @param callable $fn
*
* @return \Robo\Collection\TaskForEach
*/
public function call(callable $fn)
{
return $this->withEachKeyValueCall(
function ($key, $value) use ($fn) {
return call_user_func($fn, $value);
}
);
}
/**
* @param callable $fn
*
* @return \Robo\Collection\TaskForEach
*/
public function withBuilder(callable $fn)
{
$this->countingStack[] =
function ($key, $value) use ($fn) {
// Create a new builder for every iteration
$builder = $this->collectionBuilder();
// The user function should build task operations using
// the $key / $value parameters; we will call run() on
// the builder thus constructed.
call_user_func($fn, $builder, $key, $value);
return $builder->getCollection()->progressIndicatorSteps();
};
return $this->withEachKeyValueCall(
function ($key, $value) use ($fn) {
// Create a new builder for every iteration
$builder = $this->collectionBuilder()
->setParentCollection($this->parentCollection);
// The user function should build task operations using
// the $key / $value parameters; we will call run() on
// the builder thus constructed.
call_user_func($fn, $builder, $key, $value);
return $builder->run();
}
);
}
/**
* {@inheritdoc}
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->parentCollection = $parentCollection;
return $this;
}
/**
* {@inheritdoc}
*/
public function progressIndicatorSteps()
{
$multiplier = count($this->functionStack);
if (!empty($this->countingStack)) {
$value = reset($this->iterable);
$key = key($this->iterable);
foreach ($this->countingStack as $fn) {
$multiplier += call_user_func($fn, $key, $value);
}
}
return count($this->iterable) * $multiplier;
}
/**
* {@inheritdoc}
*/
public function run()
{
$finalResult = Result::success($this);
$this->startProgressIndicator();
foreach ($this->iterable as $key => $value) {
$this->showIterationMessage($key, $value);
try {
foreach ($this->functionStack as $fn) {
$result = call_user_func($fn, $key, $value);
$this->advanceProgressIndicator();
if (!isset($result)) {
$result = Result::success($this);
}
// If the function returns a result, it must either return
// a \Robo\Result or an exit code. In the later case, we
// convert it to a \Robo\Result.
if (!$result instanceof Result) {
$result = new Result($this, $result);
}
if (!$result->wasSuccessful()) {
return $result;
}
$finalResult = $result->merge($finalResult);
}
} catch (\Exception $e) {
return Result::fromException($result, $e);
}
}
$this->stopProgressIndicator();
return $finalResult;
}
}

View File

@@ -0,0 +1,59 @@
<?php
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
* tasks are executed directly via their run() method, rather than
* as part of a collection.
*
* In general, temporary-generating tasks should always be run in
* a collection, as the cleanup functions registered with the
* Temporary collection will not run until requested.
*
* Since the results could be undefined if cleanup functions were called
* at arbitrary times during a program's execution, cleanup should only
* be done immeidately prior to program termination, when there is no
* danger of cleaning up after some unrelated task.
*
* An application need never use Temporary directly, save to
* call Temporary::wrap() inside loadTasks or loadShortcuts, and
* to call Temporary::complete() immediately prior to terminating.
* This is recommended, but not required; this function will be
* registered as a shutdown function, and called on termination.
*/
class Temporary
{
private static $collection;
/**
* Provides direct access to the collection of temporaries, if necessary.
*/
public static function getCollection()
{
if (!static::$collection) {
static::$collection = \Robo\Robo::getContainer()->get('collection');
register_shutdown_function(function () {
static::complete();
});
}
return static::$collection;
}
/**
* Call the complete method of all of the registered objects.
*/
public static function complete()
{
// Run the collection of tasks. This will also run the
// completion tasks.
$collection = static::getCollection();
$collection->run();
// Make sure that our completion functions do not run twice.
$collection->reset();
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Robo\Collection;
trait loadTasks
{
/**
* Run a callback function on each item in a collection
*
* @param array $collection
*
* @return \Robo\Collection\TaskForEach
*/
protected function taskForEach($collection)
{
return $this->task(TaskForEach::class, $collection);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\Collection\CollectionBuilder;
trait BuilderAwareTrait
{
/**
* @var \Robo\Collection\CollectionBuilder
*/
protected $builder;
/**
* @see \Robo\Contract\BuilderAwareInterface::setBuilder()
*
* @param \Robo\Collection\CollectionBuilder $builder
*
* @return $this
*/
public function setBuilder(CollectionBuilder $builder)
{
$this->builder = $builder;
return $this;
}
/**
* @see \Robo\Contract\BuilderAwareInterface::getBuilder()
*
* @return \Robo\Collection\CollectionBuilder
*/
public function getBuilder()
{
return $this->builder;
}
/**
* @return \Robo\Collection\CollectionBuilder
*/
protected function collectionBuilder()
{
return $this->getBuilder()->newBuilder();
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Robo\Common;
use Symfony\Component\Process\ProcessUtils;
/**
* Use this to add arguments and options to the $arguments property.
*/
trait CommandArguments
{
/**
* @var string
*/
protected $arguments = '';
/**
* Pass argument to executable. Its value will be automatically escaped.
*
* @param string $arg
*
* @return $this
*/
public function arg($arg)
{
return $this->args($arg);
}
/**
* 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();
}
$this->arguments .= ' ' . implode(' ', array_map('static::escape', $args));
return $this;
}
/**
* Pass the provided string in its raw (as provided) form as an argument to executable.
*
* @param string $arg
*/
public function rawArg($arg)
{
$this->arguments .= " $arg";
}
/**
* Escape the provided value, unless it contains only alphanumeric
* plus a few other basic characters.
*
* @param string $value
*
* @return string
*/
public static function escape($value)
{
if (preg_match('/^[a-zA-Z0-9\/\.@~_-]+$/', $value)) {
return $value;
}
return ProcessUtils::escapeArgument($value);
}
/**
* Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* Option values are automatically escaped.
*
* @param string $option
* @param string $value
*
* @return $this
*/
public function option($option, $value = null)
{
if ($option !== null and strpos($option, '-') !== 0) {
$option = "--$option";
}
$this->arguments .= null == $option ? '' : " " . $option;
$this->arguments .= null == $value ? '' : " " . static::escape($value);
return $this;
}
/**
* Pass multiple options to executable. Value can be a string or array.
* Option values are automatically escaped.
*
* @param string $option
* @param string|array $value
*
* @return $this
*/
public function optionList($option, $value = array())
{
if (is_array($value)) {
foreach ($value as $item) {
$this->optionList($option, $item);
}
} else {
$this->option($option, $value);
}
return $this;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Robo\Common;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
/**
* This task can receive commands from task implementing CommandInterface.
*/
trait CommandReceiver
{
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
protected function receiveCommand($command)
{
if (!is_object($command)) {
return $command;
}
if ($command instanceof CommandInterface) {
return $command->getCommand();
} else {
throw new TaskException($this, get_class($command) . " does not implement CommandInterface, so can't be passed into this task");
}
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\Config;
trait ConfigAwareTrait
{
/**
* @var \Robo\Config
*/
protected $config;
/**
* Set the config management object.
*
* @param \Robo\Config $config
*
* @return $this
*
* @see \Robo\Contract\ConfigAwareInterface::setConfig()
*/
public function setConfig(Config $config)
{
$this->config = $config;
return $this;
}
/**
* Get the config management object.
*
* @return \Robo\Config
*
* @see \Robo\Contract\ConfigAwareInterface::getConfig()
*/
public function getConfig()
{
return $this->config;
}
/**
* @param string $key
*
* @return string
*/
private static function getClassKey($key)
{
return sprintf('%s.%s', get_called_class(), $key);
}
/**
* @param string $key
* @param mixed $value
*
* @deprecated
*/
public static function configure($key, $value)
{
Robo::config()->set(static::getClassKey($key), $value);
}
/**
* @param string $key
* @param mixed|null $default
*
* @return mixed|null
*/
protected function getConfigValue($key, $default = null)
{
if (!$this->getConfig()) {
return $default;
}
return $this->getConfig()->get(static::getClassKey($key), $default);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Robo\Common;
/**
* Simplifies generating of configuration chanined methods.
* You can only define configuration properties and use magic methods to set them.
* Methods will be named the same way as properties.
* * Boolean properties are switched on/off if no values is provided.
* * Array properties can accept non-array values, in this case value will be appended to array.
* You should also define phpdoc for methods.
*/
trait DynamicParams
{
/**
* @param string $property
* @param array $args
*
* @return $this
*/
public function __call($property, $args)
{
if (!property_exists($this, $property)) {
throw new \RuntimeException("Property $property in task ".get_class($this).' does not exists');
}
// toggle boolean values
if (!isset($args[0]) and (is_bool($this->$property))) {
$this->$property = !$this->$property;
return $this;
}
// append item to array
if (is_array($this->$property)) {
if (is_array($args[0])) {
$this->$property = $args[0];
} else {
array_push($this->$property, $args[0]);
}
return $this;
}
$this->$property = $args[0];
return $this;
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace Robo\Common;
use Robo\Result;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
/**
* This task is supposed to be executed as shell command.
* You can specify working directory and if output is printed.
*/
trait ExecCommand
{
/**
* @var bool
*/
protected $isPrinted = true;
/**
* @var string
*/
protected $workingDirectory;
/**
* @var \Robo\Common\TimeKeeper
*/
protected $execTimer;
/**
* @return \Robo\Common\TimeKeeper
*/
protected function getExecTimer()
{
if (!isset($this->execTimer)) {
$this->execTimer = new TimeKeeper();
}
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
* found. Otherwise, look for an executable command
* of the same name via findExecutable.
*
* @param string $cmd
*
* @return bool|string
*/
protected function findExecutablePhar($cmd)
{
if (file_exists("{$cmd}.phar")) {
return "php {$cmd}.phar";
}
return $this->findExecutable($cmd);
}
/**
* Return the best path to the executable program
* with the provided name. Favor vendor/bin in the
* current project. If not found there, use
* whatever is on the $PATH.
*
* @param string $cmd
*
* @return bool|string
*/
protected function findExecutable($cmd)
{
$pathToCmd = $this->searchForExecutable($cmd);
if ($pathToCmd) {
return $this->useCallOnWindows($pathToCmd);
}
return false;
}
/**
* @param string $cmd
*
* @return string
*/
private function searchForExecutable($cmd)
{
$projectBin = $this->findProjectBin();
$localComposerInstallation = $projectBin . DIRECTORY_SEPARATOR . $cmd;
if (file_exists($localComposerInstallation)) {
return $localComposerInstallation;
}
$finder = new ExecutableFinder();
return $finder->find($cmd, null, []);
}
/**
* @return bool|string
*/
protected function findProjectBin()
{
$candidates = [ __DIR__ . '/../../vendor/bin', __DIR__ . '/../../bin' ];
// If this project is inside a vendor directory, give highest priority
// to that directory.
$vendorDirContainingUs = realpath(__DIR__ . '/../../../..');
if (is_dir($vendorDirContainingUs) && (basename($vendorDirContainingUs) == 'vendor')) {
array_unshift($candidates, $vendorDirContainingUs . '/bin');
}
foreach ($candidates as $dir) {
if (is_dir("$dir")) {
return realpath($dir);
}
}
return false;
}
/**
* Wrap Windows executables in 'call' per 7a88757d
*
* @param string $cmd
*
* @return string
*/
protected function useCallOnWindows($cmd)
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
if (file_exists("{$cmd}.bat")) {
$cmd = "{$cmd}.bat";
}
return "call $cmd";
}
return $cmd;
}
/**
* @param string $command
*
* @return \Robo\Result
*/
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()]);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Robo\Common;
/**
* This task specifies exactly one shell command.
* It can take additional arguments and options as config parameters.
*/
trait ExecOneCommand
{
use ExecCommand;
use CommandArguments;
}

View File

@@ -0,0 +1,176 @@
<?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;
trait IO
{
use InputAwareTrait;
use OutputAwareTrait;
/**
* @var \Symfony\Component\Console\Style\SymfonyStyle
*/
protected $io;
/**
* Provide access to SymfonyStyle object.
*
* @return SymfonyStyle
*
* @see http://symfony.com/blog/new-in-symfony-2-8-console-style-guide
*/
protected function io()
{
if (!$this->io) {
$this->io = new SymfonyStyle($this->input(), $this->output());
}
return $this->io;
}
/**
* @param string $nonDecorated
* @param string $decorated
*
* @return string
*/
protected function decorationCharacter($nonDecorated, $decorated)
{
if (!$this->output()->isDecorated() || (strncasecmp(PHP_OS, 'WIN', 3) == 0)) {
return $nonDecorated;
}
return $decorated;
}
/**
* @param string $text
*/
protected function say($text)
{
$char = $this->decorationCharacter('>', '➜');
$this->writeln("$char $text");
}
/**
* @param string $text
* @param int $length
* @param string $color
*/
protected function yell($text, $length = 40, $color = 'green')
{
$char = $this->decorationCharacter(' ', '➜');
$format = "$char <fg=white;bg=$color;options=bold>%s</fg=white;bg=$color;options=bold>";
$this->formattedOutput($text, $length, $format);
}
/**
* @param string $text
* @param int $length
* @param string $format
*/
private function formattedOutput($text, $length, $format)
{
$lines = explode("\n", trim($text, "\n"));
$maxLineLength = array_reduce(array_map('strlen', $lines), 'max');
$length = max($length, $maxLineLength);
$len = $length + 2;
$space = str_repeat(' ', $len);
$this->writeln(sprintf($format, $space));
foreach ($lines as $line) {
$line = str_pad($line, $length, ' ', STR_PAD_BOTH);
$this->writeln(sprintf($format, " $line "));
}
$this->writeln(sprintf($format, $space));
}
/**
* @param string $question
* @param bool $hideAnswer
*
* @return string
*/
protected function ask($question, $hideAnswer = false)
{
if ($hideAnswer) {
return $this->askHidden($question);
}
return $this->doAsk(new Question($this->formatQuestion($question)));
}
/**
* @param string $question
*
* @return string
*/
protected function askHidden($question)
{
$question = new Question($this->formatQuestion($question));
$question->setHidden(true);
return $this->doAsk($question);
}
/**
* @param string $question
* @param string $default
*
* @return string
*/
protected function askDefault($question, $default)
{
return $this->doAsk(new Question($this->formatQuestion("$question [$default]"), $default));
}
/**
* @param string $question
*
* @return string
*/
protected function confirm($question)
{
return $this->doAsk(new ConfirmationQuestion($this->formatQuestion($question . ' (y/n)'), false));
}
/**
* @param \Symfony\Component\Console\Question\Question $question
*
* @return string
*/
private function doAsk(Question $question)
{
return $this->getDialog()->ask($this->input(), $this->output(), $question);
}
/**
* @param string $message
*
* @return string
*/
private function formatQuestion($message)
{
return "<question>? $message</question> ";
}
/**
* @return \Symfony\Component\Console\Helper\QuestionHelper
*/
protected function getDialog()
{
return new QuestionHelper();
}
/**
* @param $text
*/
private function writeln($text)
{
$this->output()->writeln($text);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Robo\Common;
use Robo\Contract\InflectionInterface;
trait InflectionTrait
{
/**
* Ask the provided parent class to inject all of the dependencies
* that it has and we need.
*
* @param \Robo\Contract\InflectionInterface $parent
*
* @return $this
*/
public function inflect(InflectionInterface $parent)
{
$parent->injectDependencies($this);
return $this;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Robo\Common;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
trait InputAwareTrait
{
/**
* @var \Symfony\Component\Console\Input\InputInterface
*/
protected $input;
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
*
* @return $this
*
* @see \Symfony\Component\Console\Input\InputAwareInterface::setInput()
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
return $this;
}
/**
* @return \Symfony\Component\Console\Input\InputInterface
*/
protected function input()
{
if (!isset($this->input)) {
$this->setInput(new ArgvInput());
}
return $this->input;
}
/**
* Backwards compatibility.
*
* @return \Symfony\Component\Console\Input\InputInterface
*
* @deprecated
*/
protected function getInput()
{
return $this->input();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Robo\Common;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
trait OutputAwareTrait
{
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return $this
*
* @see \Robo\Contract\OutputAwareInterface::setOutput()
*/
public function setOutput(OutputInterface $output)
{
$this->output = $output;
return $this;
}
/**
* @return \Symfony\Component\Console\Output\OutputInterface
*/
protected function output()
{
if (!isset($this->output)) {
$this->setOutput(new NullOutput());
}
return $this->output;
}
/**
* Backwards compatibility
*
* @return \Symfony\Component\Console\Output\OutputInterface
*
* @deprecated
*/
protected function getOutput()
{
return $this->output();
}
}

View File

@@ -0,0 +1,201 @@
<?php
namespace Robo\Common;
/**
* Wrapper around \Symfony\Component\Console\Helper\ProgressBar
*/
class ProgressIndicator
{
use Timer;
/**
* @var \Symfony\Component\Console\Helper\ProgressBar
*/
protected $progressBar;
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* @var bool
*/
protected $progressIndicatorRunning = false;
/**
* @var int
*/
protected $autoDisplayInterval = 0;
/**
* @var int
*/
protected $cachedSteps = 0;
/**
* @var int
*/
protected $totalSteps = 0;
/**
* @var bool
*/
protected $progressBarDisplayed = false;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $owner;
/**
* @param \Symfony\Component\Console\Helper\ProgressBar $progressBar
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function __construct($progressBar, \Symfony\Component\Console\Output\OutputInterface $output)
{
$this->progressBar = $progressBar;
$this->output = $output;
}
/**
* @param int $interval
*/
public function setProgressBarAutoDisplayInterval($interval)
{
if ($this->progressIndicatorRunning) {
return;
}
$this->autoDisplayInterval = $interval;
}
/**
* @return bool
*/
public function hideProgressIndicator()
{
$result = $this->progressBarDisplayed;
if ($this->progressIndicatorRunning && $this->progressBarDisplayed) {
$this->progressBar->clear();
// Hack: progress indicator does not reset cursor to beginning of line on 'clear'
$this->output->write("\x0D");
$this->progressBarDisplayed = false;
}
return $result;
}
public function showProgressIndicator()
{
if ($this->progressIndicatorRunning && !$this->progressBarDisplayed && isset($this->progressBar)) {
$this->progressBar->display();
$this->progressBarDisplayed = true;
$this->advanceProgressIndicatorCachedSteps();
}
}
/**
* @param bool $visible
*/
public function restoreProgressIndicator($visible)
{
if ($visible) {
$this->showProgressIndicator();
}
}
/**
* @param int $totalSteps
* @param \Robo\Contract\TaskInterface $owner
*/
public function startProgressIndicator($totalSteps, $owner)
{
if (!isset($this->progressBar)) {
return;
}
$this->progressIndicatorRunning = true;
if (!isset($this->owner)) {
$this->owner = $owner;
$this->startTimer();
$this->totalSteps = $totalSteps;
$this->autoShowProgressIndicator();
}
}
public function autoShowProgressIndicator()
{
if (($this->autoDisplayInterval < 0) || !isset($this->progressBar) || !$this->output->isDecorated()) {
return;
}
if ($this->autoDisplayInterval <= $this->getExecutionTime()) {
$this->autoDisplayInterval = -1;
$this->progressBar->start($this->totalSteps);
$this->showProgressIndicator();
}
}
/**
* @return bool
*/
public function inProgress()
{
return $this->progressIndicatorRunning;
}
/**
* @param \Robo\Contract\TaskInterface $owner
*/
public function stopProgressIndicator($owner)
{
if ($this->progressIndicatorRunning && ($this->owner === $owner)) {
$this->cleanup();
}
}
protected function cleanup()
{
$this->progressIndicatorRunning = false;
$this->owner = null;
if ($this->progressBarDisplayed) {
$this->progressBar->finish();
// Hack: progress indicator does not always finish cleanly
$this->output->writeln('');
$this->progressBarDisplayed = false;
}
$this->stopTimer();
}
/**
* Erase progress indicator and ensure it never returns. Used
* only during error handlers.
*/
public function disableProgressIndicator()
{
$this->cleanup();
// ProgressIndicator is shared, so this permanently removes
// the program's ability to display progress bars.
$this->progressBar = null;
}
/**
* @param int $steps
*/
public function advanceProgressIndicator($steps = 1)
{
$this->cachedSteps += $steps;
if ($this->progressIndicatorRunning) {
$this->autoShowProgressIndicator();
// We only want to call `advance` if the progress bar is visible,
// because it always displays itself when it is advanced.
if ($this->progressBarDisplayed) {
return $this->advanceProgressIndicatorCachedSteps();
}
}
}
protected function advanceProgressIndicatorCachedSteps()
{
$this->progressBar->advance($this->cachedSteps);
$this->cachedSteps = 0;
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace Robo\Common;
trait ProgressIndicatorAwareTrait
{
use Timer;
/**
* @var null|\Robo\Common\ProgressIndicator
*/
protected $progressIndicator;
/**
* @return int
*/
public function progressIndicatorSteps()
{
return 0;
}
/**
* @param null|\Robo\Common\ProgressIndicator $progressIndicator
*/
public function setProgressIndicator($progressIndicator)
{
$this->progressIndicator = $progressIndicator;
}
/**
* @return null|bool
*/
protected function hideProgressIndicator()
{
if (!$this->progressIndicator) {
return;
}
return $this->progressIndicator->hideProgressIndicator();
}
protected function showProgressIndicator()
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->showProgressIndicator();
}
/**
* @param bool $visible
*/
protected function restoreProgressIndicator($visible)
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->restoreProgressIndicator($visible);
}
/**
* @return int
*/
protected function getTotalExecutionTime()
{
if (!$this->progressIndicator) {
return 0;
}
return $this->progressIndicator->getExecutionTime();
}
protected function startProgressIndicator()
{
$this->startTimer();
if (!$this->progressIndicator) {
return;
}
$totalSteps = $this->progressIndicatorSteps();
$this->progressIndicator->startProgressIndicator($totalSteps, $this);
}
/**
* @return bool
*/
protected function inProgress()
{
if (!$this->progressIndicator) {
return false;
}
return $this->progressIndicator->inProgress();
}
protected function stopProgressIndicator()
{
$this->stopTimer();
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->stopProgressIndicator($this);
}
protected function disableProgressIndicator()
{
$this->stopTimer();
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->disableProgressIndicator();
}
protected function detatchProgressIndicator()
{
$this->setProgressIndicator(null);
}
/**
* @param int $steps
*/
protected function advanceProgressIndicator($steps = 1)
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->advanceProgressIndicator($steps);
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Robo\Common;
trait ResourceExistenceChecker
{
/**
* Checks if the given input is a file or folder.
*
* @param string|string[] $resources
* @param string $type "file", "dir", "fileAndDir"
*
* @return bool True if no errors were encountered otherwise false.
*/
protected function checkResources($resources, $type = 'fileAndDir')
{
if (!in_array($type, ['file', 'dir', 'fileAndDir'])) {
throw new \InvalidArgumentException(sprintf('Invalid resource check of type "%s" used!', $type));
}
if (is_string($resources)) {
$resources = [$resources];
}
$success = true;
foreach ($resources as $resource) {
$glob = glob($resource);
if ($glob === false) {
$this->printTaskError(sprintf('Invalid glob "%s"!', $resource), $this);
$success = false;
continue;
}
foreach ($glob as $resource) {
if (!$this->checkResource($resource, $type)) {
$success = false;
}
}
}
return $success;
}
/**
* Checks a single resource, file or directory.
*
* It will print an error as well on the console.
*
* @param string $resource File or folder.
* @param string $type "file", "dir", "fileAndDir"
*
* @return bool
*/
protected function checkResource($resource, $type)
{
switch ($type) {
case 'file':
if (!$this->isFile($resource)) {
$this->printTaskError(sprintf('File "%s" does not exist!', $resource), $this);
return false;
}
return true;
case 'dir':
if (!$this->isDir($resource)) {
$this->printTaskError(sprintf('Directory "%s" does not exist!', $resource), $this);
return false;
}
return true;
case 'fileAndDir':
if (!$this->isDir($resource) && !$this->isFile($resource)) {
$this->printTaskError(sprintf('File or directory "%s" does not exist!', $resource), $this);
return false;
}
return true;
}
}
/**
* Convenience method to check the often uses "source => target" file / folder arrays.
*
* @param string|array $resources
*/
protected function checkSourceAndTargetResource($resources)
{
if (is_string($resources)) {
$resources = [$resources];
}
$sources = [];
$targets = [];
foreach ($resources as $source => $target) {
$sources[] = $source;
$target[] = $target;
}
$this->checkResources($sources);
$this->checkResources($targets);
}
/**
* Wrapper method around phps is_dir()
*
* @param string $directory
*
* @return bool
*/
protected function isDir($directory)
{
return is_dir($directory);
}
/**
* Wrapper method around phps file_exists()
*
* @param string $file
*
* @return bool
*/
protected function isFile($file)
{
return file_exists($file);
}
}

View File

@@ -0,0 +1,235 @@
<?php
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;
/**
* 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 TaskIO
{
use LoggerAwareTrait;
use ConfigAwareTrait;
/**
* @return mixed|null|\Psr\Log\LoggerInterface
*/
public function logger()
{
// $this->logger should always be set in Robo core tasks.
if ($this->logger) {
return $this->logger;
}
// TODO: Remove call to Robo::logger() once maintaining backwards
// compatibility with legacy external Robo tasks is no longer desired.
if (!Robo::logger()) {
return null;
}
static $gaveDeprecationWarning = false;
if (!$gaveDeprecationWarning) {
trigger_error('No logger set for ' . get_class($this) . '. Use $this->task(Foo::class) rather than new Foo() in loadTasks to ensure the builder can initialize task the task, or use $this->collectionBuilder()->taskFoo() if creating one task from within another.', E_USER_DEPRECATED);
$gaveDeprecationWarning = true;
}
return Robo::logger();
}
/**
* Print information about a task in progress.
*
* With the Symfony Console logger, NOTICE is displayed at VERBOSITY_VERBOSE
* and INFO is displayed at VERBOSITY_VERY_VERBOSE.
*
* Robo overrides the default such that NOTICE is displayed at
* VERBOSITY_NORMAL and INFO is displayed at VERBOSITY_VERBOSE.
*
* n.b. We should probably have printTaskNotice for our ordinary
* output, and use printTaskInfo for less interesting messages.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskInfo($text, $context = null)
{
// The 'note' style is used for both 'notice' and 'info' log levels;
// However, 'notice' is printed at VERBOSITY_NORMAL, whereas 'info'
// is only printed at VERBOSITY_VERBOSE.
$this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
}
/**
* Provide notification that some part of the task succeeded.
*
* With the Symfony Console logger, success messages are remapped to NOTICE,
* and displayed in VERBOSITY_VERBOSE. When used with the Robo logger,
* success messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskSuccess($text, $context = null)
{
// Not all loggers will recognize ConsoleLogLevel::SUCCESS.
// We therefore log as LogLevel::NOTICE, and apply a '_level'
// override in the context so that this message will be
// logged as SUCCESS if that log level is recognized.
$context['_level'] = ConsoleLogLevel::SUCCESS;
$this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
}
/**
* Provide notification that there is something wrong, but
* execution can continue.
*
* Warning messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskWarning($text, $context = null)
{
$this->printTaskOutput(LogLevel::WARNING, $text, $this->getTaskContext($context));
}
/**
* Provide notification that some operation in the task failed,
* and the task cannot continue.
*
* Error messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskError($text, $context = null)
{
$this->printTaskOutput(LogLevel::ERROR, $text, $this->getTaskContext($context));
}
/**
* Provide debugging notification. These messages are only
* displayed if the log level is VERBOSITY_DEBUG.
*
* @param string$text
* @param null|array $context
*/
protected function printTaskDebug($text, $context = null)
{
$this->printTaskOutput(LogLevel::DEBUG, $text, $this->getTaskContext($context));
}
/**
* @param string $level
* One of the \Psr\Log\LogLevel constant
* @param string $text
* @param null|array $context
*/
protected function printTaskOutput($level, $text, $context)
{
$logger = $this->logger();
if (!$logger) {
return;
}
// Hide the progress indicator, if it is visible.
$inProgress = $this->hideTaskProgress();
$logger->log($level, $text, $this->getTaskContext($context));
// After we have printed our log message, redraw the progress indicator.
$this->showTaskProgress($inProgress);
}
/**
* @return bool
*/
protected function hideTaskProgress()
{
$inProgress = false;
if ($this instanceof ProgressIndicatorAwareInterface) {
$inProgress = $this->inProgress();
}
// If a progress indicator is running on this task, then we mush
// hide it before we print anything, or its display will be overwritten.
if ($inProgress) {
$inProgress = $this->hideProgressIndicator();
}
return $inProgress;
}
/**
* @param $inProgress
*/
protected function showTaskProgress($inProgress)
{
if ($inProgress) {
$this->restoreProgressIndicator($inProgress);
}
}
/**
* Format a quantity of bytes.
*
* @param int $size
* @param int $precision
*
* @return string
*/
protected function formatBytes($size, $precision = 2)
{
if ($size === 0) {
return 0;
}
$base = log($size, 1024);
$suffixes = array('', 'k', 'M', 'G', 'T');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
/**
* Get the formatted task name for use in task output.
* This is placed in the task context under 'name', and
* used as the log label by Robo\Common\RoboLogStyle,
* which is inserted at the head of log messages by
* Robo\Common\CustomLogStyle::formatMessage().
*
* @param null|object $task
*
* @return string
*/
protected function getPrintedTaskName($task = null)
{
if (!$task) {
$task = $this;
}
return TaskInfo::formatTaskName($task);
}
/**
* @param null|array $context
*
* @return array with context information
*/
protected function getTaskContext($context = null)
{
if (!$context) {
$context = [];
}
if (!is_array($context)) {
$context = ['task' => $context];
}
if (!array_key_exists('task', $context)) {
$context['task'] = $this;
}
return $context + TaskInfo::getTaskContext($context['task']);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Robo\Common;
class TimeKeeper
{
const MINUTE = 60;
const HOUR = 3600;
const DAY = 86400;
/**
* @var float
*/
protected $startedAt;
/**
* @var float
*/
protected $finishedAt;
public function start()
{
if ($this->startedAt) {
return;
}
// Get time in seconds as a float, accurate to the microsecond.
$this->startedAt = microtime(true);
}
public function stop()
{
$this->finishedAt = microtime(true);
}
/**
* @return float|null
*/
public function elapsed()
{
$finished = $this->finishedAt ? $this->finishedAt : microtime(true);
if ($finished - $this->startedAt <= 0) {
return null;
}
return $finished - $this->startedAt;
}
/**
* Format a duration into a human-readable time
*
* @param float $duration Duration in seconds, with fractional component
*
* @return string
*/
public static function formatDuration($duration)
{
if ($duration >= self::DAY * 2) {
return gmdate('z \d\a\y\s H:i:s', $duration);
}
if ($duration > self::DAY) {
return gmdate('\1 \d\a\y H:i:s', $duration);
}
if ($duration > self::HOUR) {
return gmdate("H:i:s", $duration);
}
if ($duration > self::MINUTE) {
return gmdate("i:s", $duration);
}
return round($duration, 3).'s';
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Robo\Common;
trait Timer
{
/**
* @var \Robo\Common\TimeKeeper
*/
protected $timer;
protected function startTimer()
{
if (!isset($this->timer)) {
$this->timer = new TimeKeeper();
}
$this->timer->start();
}
protected function stopTimer()
{
if (!isset($this->timer)) {
return;
}
$this->timer->stop();
}
/**
* @return float|null
*/
protected function getExecutionTime()
{
if (!isset($this->timer)) {
return null;
}
return $this->timer->elapsed();
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace Robo;
class 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);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Robo\Contract;
use Robo\Collection\CollectionBuilder;
interface BuilderAwareInterface
{
/**
* Set the builder reference
*
* @param \Robo\Collection\CollectionBuilder $builder
*/
public function setBuilder(CollectionBuilder $builder);
/**
* Get the builder reference
*
* @return \Robo\Collection\CollectionBuilder
*/
public function getBuilder();
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Robo\Contract;
/**
* Task that implements this interface can be injected as a parameter for other task.
* This task can be represented as executable command.
*
* @package Robo\Contract
*/
interface CommandInterface
{
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand();
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Robo\Contract;
/**
* Any Robo tasks that implements this interface will
* be called when the task collection it is added to
* completes.
*
* Interface CompletionInterface
* @package Robo\Contract
*/
interface CompletionInterface extends TaskInterface
{
/**
* Revert an operation that can be rolled back
*/
public function complete();
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Robo\Contract;
use Robo\Config;
interface ConfigAwareInterface
{
/**
* Set the config reference
*
* @param \Robo\Config $config
*
* @return $this
*/
public function setConfig(Config $config);
/**
* Get the config reference
*
* @return \Robo\Config
*/
public function getConfig();
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* Marker interface for tasks that use the IO trait
*/
namespace Robo\Contract;
use \Symfony\Component\Console\Input\InputAwareInterface;
interface IOAwareInterface extends OutputAwareInterface, InputAwareInterface
{
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Robo\Contract;
interface InflectionInterface
{
/**
* Based on league/container inflection: http://container.thephpleague.com/inflectors/
*
* This allows us to run:
*
* (new SomeTask($args))
* ->inflect($this)
* ->initializer()
* ->...
*
* Instead of:
*
* (new SomeTask($args))
* ->setLogger($this->logger)
* ->initializer()
* ->...
*
* The reason `inflect` is better than the more explicit alternative is
* that subclasses of BaseTask that implement a new FooAwareInterface
* can override injectDependencies() as explained below, and add more
* dependencies that can be injected as needed.
*
* @param \Robo\Contract\InflectionInterface $parent
*/
public function inflect(InflectionInterface $parent);
/**
* Take all dependencies availble to this task and inject any that are
* needed into the provided task. The general pattern is that, for every
* FooAwareInterface that this class implements, it should test to see
* if the child also implements the same interface, and if so, should call
* $child->setFoo($this->foo).
*
* The benefits of this are pretty large. Any time an object that implements
* InflectionInterface is created, just call `$child->inflect($this)`, and
* any available optional dependencies will be hooked up via setter injection.
*
* The required dependencies of an object should be provided via constructor
* injection, not inflection.
*
* @param InflectionInterface $child An object created by this class that
* should have its dependencies injected.
*
* @see https://mwop.net/blog/2016-04-26-on-locators.html
*/
public function injectDependencies(InflectionInterface $child);
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Provide OutputAwareInterface, not present in Symfony Console
*/
namespace Robo\Contract;
use Symfony\Component\Console\Output\OutputInterface;
interface OutputAwareInterface
{
/**
* Sets the Console Output.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function setOutput(OutputInterface $output);
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Robo\Contract;
/**
* If task prints anything to console
*
* Interface PrintedInterface
* @package Robo\Contract
*/
interface PrintedInterface
{
/**
* @return bool
*/
public function getPrinted();
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Robo\Contract;
/**
* Any Robo task that uses the Timer trait and
* implements ProgressIndicatorAwareInterface will
* display a progress bar while the timer is running.
* Call advanceProgressIndicator to advance the indicator.
*
* Interface ProgressIndicatorAwareInterface
* @package Robo\Contract
*/
interface ProgressIndicatorAwareInterface
{
/**
* @return int
*/
public function progressIndicatorSteps();
/**
* @param \Robo\Common\ProgressIndicator $progressIndicator
*/
public function setProgressIndicator($progressIndicator);
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Robo\Contract;
/**
* Robo tasks that take multiple steps to complete should
* implement this interface.
*
* Interface ProgressInterface
* @package Robo\Contract
*/
interface ProgressInterface
{
/**
*
* @return int
*/
public function progressSteps();
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Robo\Contract;
/**
* Any Robo tasks that implements this interface will
* be called when the task collection it is added to
* fails, and runs its rollback operation.
*
* Interface RollbackInterface
* @package Robo\Contract
*/
interface RollbackInterface extends TaskInterface
{
/**
* Revert an operation that can be rolled back
*/
public function rollback();
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Robo\Contract;
/**
* Task that implements this interface can be injected as a parameter for other task.
* This task can be represented as executable command.
*
* @package Robo\Contract
*/
interface SimulatedInterface extends TaskInterface
{
/**
* Called in place of `run()` for simulated tasks.
*
* @param null|array $context
*/
public function simulate($context);
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Robo\Contract;
/**
* All Robo tasks should implement this interface.
* Task should be configured by chained methods.
*
* Interface TaskInterface
* @package Robo\Contract
*/
interface TaskInterface
{
/**
* @return \Robo\Result
*/
public function run();
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Robo\Contract;
interface WrappedTaskInterface extends TaskInterface
{
/**
* @return \Robo\Contract\TaskInterface
*/
public function original();
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Robo\Exception;
class TaskException extends \Exception
{
public function __construct($class, $message)
{
if (is_object($class)) {
$class = get_class($class);
}
parent::__construct(" in task $class \n\n $message");
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Robo\Exception;
class TaskExitException extends \Exception
{
public function __construct($class, $message, $status)
{
if (is_object($class)) {
$class = get_class($class);
}
parent::__construct(" in task $class \n\n $message", $status);
}
}

View File

@@ -0,0 +1,45 @@
<?php
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;
class GlobalOptionsEventListener implements EventSubscriberInterface, ConfigAwareInterface
{
use ConfigAwareTrait;
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'setGlobalOptions'];
}
/**
* Before a Console command runs, examine the global
* commandline options from the event Input, and set
* configuration values as appropriate.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function setGlobalOptions(ConsoleCommandEvent $event)
{
$config = $this->getConfig();
$input = $event->getInput();
$globalOptions = $config->getGlobalOptionDefaultValues();
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);
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Robo;
trait LoadAllTasks
{
use TaskAccessor;
use Collection\loadTasks;
// standard tasks
use Task\Base\loadTasks;
use Task\Development\loadTasks;
use Task\Filesystem\loadTasks;
use Task\File\loadTasks;
use Task\Archive\loadTasks;
use Task\Vcs\loadTasks;
// package managers
use Task\Composer\loadTasks;
use Task\Bower\loadTasks;
use Task\Npm\loadTasks;
// assets
use Task\Assets\loadTasks;
// 3rd-party tools
use Task\Remote\loadTasks;
use Task\Testing\loadTasks;
use Task\ApiGen\loadTasks;
use Task\Docker\loadTasks;
// task runners
use Task\Gulp\loadTasks;
// shortcuts
use Task\Base\loadShortcuts;
use Task\Filesystem\loadShortcuts;
use Task\Vcs\loadShortcuts;
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Robo\Log;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Contract\PrintedInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Consolidation\Log\ConsoleLogLevel;
/**
* Log the creation of Result objects.
*/
class ResultPrinter implements LoggerAwareInterface, ProgressIndicatorAwareInterface
{
use LoggerAwareTrait;
use ProgressIndicatorAwareTrait;
/**
* Log the result of a Robo task.
*
* Returns 'true' if the message is printed, or false if it isn't.
*
* @param \Robo\Result $result
*
* @return bool
*/
public function printResult(Result $result)
{
if (!$result->wasSuccessful()) {
return $this->printError($result);
} else {
return $this->printSuccess($result);
}
}
/**
* Log that we are about to abort due to an error being encountered
* in 'stop on fail' mode.
*
* @param \Robo\Result $result
*/
public function printStopOnFail($result)
{
$this->printMessage(LogLevel::NOTICE, 'Stopping on fail. Exiting....');
$this->printMessage(LogLevel::ERROR, 'Exit Code: {code}', ['code' => $result->getExitCode()]);
}
/**
* Log the result of a Robo task that returned an error.
*
* @param \Robo\Result $result
*
* @return bool
*/
protected function printError(Result $result)
{
$task = $result->getTask();
$context = $result->getContext() + ['timer-label' => 'Time', '_style' => []];
$context['_style']['message'] = '';
$printOutput = true;
if ($task instanceof PrintedInterface) {
$printOutput = !$task->getPrinted();
}
if ($printOutput) {
$this->printMessage(LogLevel::ERROR, "{message}", $context);
}
$this->printMessage(LogLevel::ERROR, 'Exit code {code}', $context);
return true;
}
/**
* Log the result of a Robo task that was successful.
*
* @param \Robo\Result $result
*
* @return bool
*/
protected function printSuccess(Result $result)
{
$task = $result->getTask();
$context = $result->getContext() + ['timer-label' => 'in'];
$time = $result->getExecutionTime();
if ($time) {
$this->printMessage(ConsoleLogLevel::SUCCESS, 'Done', $context);
}
return false;
}
/**
* @param string $level
* @param string $message
* @param array $context
*/
protected function printMessage($level, $message, $context = [])
{
$inProgress = $this->hideProgressIndicator();
$this->logger->log($level, $message, $context);
if ($inProgress) {
$this->restoreProgressIndicator($inProgress);
}
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Robo\Log;
class RoboLogLevel extends \Consolidation\Log\ConsoleLogLevel
{
/**
* Command did something in simulated mode.
* Displayed at VERBOSITY_NORMAL.
*/
const SIMULATED_ACTION = 'simulated';
}

View File

@@ -0,0 +1,76 @@
<?php
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.
*/
class RoboLogStyle extends LogOutputStyler
{
const TASK_STYLE_SIMULATED = 'options=reverse;bold';
/**
* RoboLogStyle constructor.
*
* @param array $labelStyles
* @param array $messageStyles
*/
public function __construct($labelStyles = [], $messageStyles = [])
{
parent::__construct($labelStyles, $messageStyles);
$this->labelStyles += [
RoboLogLevel::SIMULATED_ACTION => self::TASK_STYLE_SIMULATED,
];
$this->messageStyles += [
RoboLogLevel::SIMULATED_ACTION => '',
];
}
/**
* Log style customization for Robo: replace the log level with
* the task name.
*
* @param string $level
* @param string $message
* @param array $context
*
* @return string
*/
protected function formatMessageByLevel($level, $message, $context)
{
$label = $level;
if (array_key_exists('name', $context)) {
$label = $context['name'];
}
return $this->formatMessage($label, $message, $context, $this->labelStyles[$level], $this->messageStyles[$level]);
}
/**
* Log style customization for Robo: add the time indicator to the
* end of the log message if it exists in the context.
*
* @param string $label
* @param string $message
* @param array $context
* @param string $taskNameStyle
* @param string $messageStyle
*
* @return string
*/
protected function formatMessage($label, $message, $context, $taskNameStyle, $messageStyle = '')
{
$message = parent::formatMessage($label, $message, $context, $taskNameStyle, $messageStyle);
if (array_key_exists('time', $context) && !empty($context['time']) && array_key_exists('timer-label', $context)) {
$duration = TimeKeeper::formatDuration($context['time']);
$message .= ' ' . $context['timer-label'] . ' ' . $this->wrapFormatString($duration, 'fg=yellow');
}
return $message;
}
}

View File

@@ -0,0 +1,36 @@
<?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
*/
class RoboLogger extends Logger
{
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function __construct(OutputInterface $output)
{
// In Robo, we use log level 'notice' for messages that appear all
// the time, and 'info' for messages that appear only during verbose
// output. We have no 'very verbose' (-vv) level. 'Debug' is -vvv, as usual.
$roboVerbosityOverrides = [
RoboLogLevel::SIMULATED_ACTION => OutputInterface::VERBOSITY_NORMAL, // Default is "verbose"
LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, // Default is "verbose"
LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE, // Default is "very verbose"
];
parent::__construct($output, $roboVerbosityOverrides);
}
}

View File

@@ -0,0 +1,234 @@
<?php
namespace Robo;
use Robo\Contract\TaskInterface;
use Robo\Contract\LogResultInterface;
use Robo\Exception\TaskExitException;
class Result extends ResultData
{
/**
* @var bool
*/
public static $stopOnFail = false;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $exitCode
* @param string $message
* @param array $data
*/
public function __construct(TaskInterface $task, $exitCode, $message = '', $data = [])
{
parent::__construct($exitCode, $message, $data);
$this->task = $task;
$this->printResult();
if (self::$stopOnFail) {
$this->stopOnFail();
}
}
protected function printResult()
{
// For historic reasons, the Result constructor is responsible
// for printing task results.
// TODO: Make IO the responsibility of some other class. Maintaining
// existing behavior for backwards compatibility. This is undesirable
// in the long run, though, as it can result in unwanted repeated input
// in task collections et. al.
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
if ($resultPrinter->printResult($this)) {
$this->data['already-printed'] = true;
}
}
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $extension
* @param string $service
*
* @return \Robo\Result
*/
public static function errorMissingExtension(TaskInterface $task, $extension, $service)
{
$messageTpl = 'PHP extension required for %s. Please enable %s';
$message = sprintf($messageTpl, $service, $extension);
return self::error($task, $message);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $class
* @param string $package
*
* @return \Robo\Result
*/
public static function errorMissingPackage(TaskInterface $task, $class, $package)
{
$messageTpl = 'Class %s not found. Please install %s Composer package';
$message = sprintf($messageTpl, $class, $package);
return self::error($task, $message);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $message
* @param array $data
*
* @return \Robo\Result
*/
public static function error(TaskInterface $task, $message, $data = [])
{
return new self($task, self::EXITCODE_ERROR, $message, $data);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param \Exception $e
* @param array $data
*
* @return \Robo\Result
*/
public static function fromException(TaskInterface $task, \Exception $e, $data = [])
{
$exitCode = $e->getCode();
if (!$exitCode) {
$exitCode = self::EXITCODE_ERROR;
}
return new self($task, $exitCode, $e->getMessage(), $data);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $message
* @param array $data
*
* @return \Robo\Result
*/
public static function success(TaskInterface $task, $message = '', $data = [])
{
return new self($task, self::EXITCODE_OK, $message, $data);
}
/**
* Return a context useful for logging messages.
*
* @return array
*/
public function getContext()
{
$task = $this->getTask();
return TaskInfo::getTaskContext($task) + [
'code' => $this->getExitCode(),
'data' => $this->getArrayCopy(),
'time' => $this->getExecutionTime(),
'message' => $this->getMessage(),
];
}
/**
* Add the results from the most recent task to the accumulated
* results from all tasks that have run so far, merging data
* as necessary.
*
* @param int|string $key
* @param \Robo\Result $taskResult
*/
public function accumulate($key, Result $taskResult)
{
// If the task is unnamed, then all of its data elements
// just get merged in at the top-level of the final Result object.
if (static::isUnnamed($key)) {
$this->merge($taskResult);
} elseif (isset($this[$key])) {
// There can only be one task with a given name; however, if
// there are tasks added 'before' or 'after' the named task,
// then the results from these will be stored under the same
// name unless they are given a name of their own when added.
$current = $this[$key];
$this[$key] = $taskResult->merge($current);
} else {
$this[$key] = $taskResult;
}
}
/**
* We assume that named values (e.g. for associative array keys)
* are non-numeric; numeric keys are presumed to simply be the
* index of an array, and therefore insignificant.
*
* @param int|string $key
*
* @return bool
*/
public static function isUnnamed($key)
{
return is_numeric($key);
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function getTask()
{
return $this->task;
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function cloneTask()
{
$reflect = new \ReflectionClass(get_class($this->task));
return $reflect->newInstanceArgs(func_get_args());
}
/**
* @return bool
*
* @deprecated since 1.0.
*
* @see wasSuccessful()
*/
public function __invoke()
{
trigger_error(__METHOD__ . ' is deprecated: use wasSuccessful() instead.', E_USER_DEPRECATED);
return $this->wasSuccessful();
}
/**
* @return $this
*/
public function stopOnFail()
{
if (!$this->wasSuccessful()) {
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
$resultPrinter->printStopOnFail($this);
}
$this->exitEarly($this->getExitCode());
}
return $this;
}
/**
* @param int $status
*
* @throws \Robo\Exception\TaskExitException
*/
private function exitEarly($status)
{
throw new TaskExitException($this->getTask(), $this->getMessage(), $status);
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace Robo;
use Robo\Contract\LogResultInterface;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\OutputDataInterface;
class ResultData extends \ArrayObject 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
code selected by Symfony. These are here for documentation purposes. */
const EXITCODE_MISSING_OPTIONS = 2;
const EXITCODE_COMMAND_NOT_FOUND = 127;
/** The command was aborted because the user chose to cancel it at some prompt.
This exit code is arbitrarily the same as EX_TEMPFAIL in sysexits.h, although
note that shell error codes are distinct from C exit codes, so this alignment
not particularly meaningful. */
const EXITCODE_USER_CANCEL = 75;
/**
* @param int $exitCode
* @param string $message
* @param array $data
*/
public function __construct($exitCode = self::EXITCODE_OK, $message = '', $data = [])
{
$this->exitCode = $exitCode;
$this->message = $message;
parent::__construct($data);
}
/**
* @param string $message
* @param array $data
*
* @return \Robo\ResultData
*/
public static function message($message, $data = [])
{
return new self(self::EXITCODE_OK, $message, $data);
}
/**
* @param string $message
* @param array $data
*
* @return \Robo\ResultData
*/
public static function cancelled($message = '', $data = [])
{
return new ResultData(self::EXITCODE_USER_CANCEL, $message, $data);
}
/**
* @return array
*/
public function getData()
{
return $this->getArrayCopy();
}
/**
* @return int
*/
public function getExitCode()
{
return $this->exitCode;
}
/**
* @return null|string
*/
public function getOutputData()
{
if (!empty($this->message) && !isset($this['already-printed'])) {
return $this->message;
}
}
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* @return bool
*/
public function wasSuccessful()
{
return $this->exitCode === self::EXITCODE_OK;
}
/**
* @return bool
*/
public function wasCancelled()
{
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'];
}
}

View File

@@ -0,0 +1,337 @@
<?php
namespace Robo;
use League\Container\Container;
use League\Container\ContainerInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Application as SymfonyApplication;
/**
* Manages the container reference and other static data. Favor
* using dependency injection wherever possible. Avoid using
* this class directly, unless setting up a custom DI container.
*/
class Robo
{
const APPLICATION_NAME = 'Robo';
const VERSION = '1.0.4';
/**
* The currently active container object, or NULL if not initialized yet.
*
* @var ContainerInterface|null
*/
protected static $container;
/**
* Entrypoint for standalone Robo-based tools. See docs/framework.md.
*
* @param string[] $argv
* @param string $commandClasses
* @param null|string $appName
* @param null|string $appVersion
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*
* @return int
*/
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null)
{
$runner = new \Robo\Runner($commandClasses);
$statusCode = $runner->execute($argv, $appName, $appVersion, $output);
return $statusCode;
}
/**
* Sets a new global container.
*
* @param ContainerInterface $container
* A new container instance to replace the current.
*/
public static function setContainer(ContainerInterface $container)
{
static::$container = $container;
}
/**
* Unsets the global container.
*/
public static function unsetContainer()
{
static::$container = null;
}
/**
* Returns the currently active global container.
*
* @return \League\Container\ContainerInterface
*
* @throws \RuntimeException
*/
public static function getContainer()
{
if (static::$container === null) {
throw new \RuntimeException('container is not initialized yet. \Robo\Robo::setContainer() must be called with a real container.');
}
return static::$container;
}
/**
* Returns TRUE if the container has been initialized, FALSE otherwise.
*
* @return bool
*/
public static function hasContainer()
{
return static::$container !== null;
}
/**
* Create a container and initiailze it. If you wish to *change*
* anything defined in the container, then you should call
* \Robo::configureContainer() instead of this function.
*
* @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
*
* @return \League\Container\Container|\League\Container\ContainerInterface
*/
public static function createDefaultContainer($input = null, $output = null, $app = null, $config = null)
{
// Do not allow this function to be called more than once.
if (static::hasContainer()) {
return static::getContainer();
}
if (!$app) {
$app = static::createDefaultApplication();
}
if (!$config) {
$config = new Config();
}
// Set up our dependency injection container.
$container = new Container();
static::configureContainer($container, $app, $config, $input, $output);
// Set the application dispatcher
$app->setDispatcher($container->get('eventDispatcher'));
return $container;
}
/**
* Initialize a container with all of the default Robo services.
* IMPORTANT: after calling this method, clients MUST call:
*
* $dispatcher = $container->get('eventDispatcher');
* $app->setDispatcher($dispatcher);
*
* Any modification to the container should be done prior to fetching
* objects from it.
*
* It is recommended to use \Robo::createDefaultContainer()
* instead, which does all required setup for the caller, but has
* the limitation that the container it creates can only be
* extended, not modified.
*
* @param \League\Container\ContainerInterface $container
* @param \Symfony\Component\Console\Application $app
* @param \Robo\Config $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)
{
// Self-referential container refernce for the inflector
$container->add('container', $container);
static::setContainer($container);
// Create default input and output objects if they were not provided
if (!$input) {
$input = new StringInput('');
}
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
$config->setDecorated($output->isDecorated());
$container->share('application', $app);
$container->share('config', $config);
$container->share('input', $input);
$container->share('output', $output);
// Register logging and related services.
$container->share('logStyler', \Robo\Log\RoboLogStyle::class);
$container->share('logger', \Robo\Log\RoboLogger::class)
->withArgument('output')
->withMethodCall('setLogOutputStyler', ['logStyler']);
$container->add('progressBar', \Symfony\Component\Console\Helper\ProgressBar::class)
->withArgument('output');
$container->share('progressIndicator', \Robo\Common\ProgressIndicator::class)
->withArgument('progressBar')
->withArgument('output');
$container->share('resultPrinter', \Robo\Log\ResultPrinter::class);
$container->add('simulator', \Robo\Task\Simulator::class);
$container->share('globalOptionsEventListener', \Robo\GlobalOptionsEventListener::class);
$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('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('commandProcessor', \Consolidation\AnnotatedCommand\CommandProcessor::class)
->withArgument('hookManager')
->withMethodCall('setFormatterManager', ['formatterManager'])
->withMethodCall(
'setDisplayErrorFunction',
[
function ($output, $message) use ($container) {
$logger = $container->get('logger');
$logger->error($message);
}
]
);
$container->share('commandFactory', \Consolidation\AnnotatedCommand\AnnotatedCommandFactory::class)
->withMethodCall('setCommandProcessor', ['commandProcessor']);
$container->add('collection', \Robo\Collection\Collection::class);
$container->add('collectionBuilder', \Robo\Collection\CollectionBuilder::class);
static::addInflectors($container);
// Make sure the application is appropriately initialized.
$app->setAutoExit(false);
}
/**
* @param null|string $appName
* @param null|string $appVersion
*
* @return \Robo\Application
*/
public static function createDefaultApplication($appName = null, $appVersion = null)
{
$appName = $appName ?: self::APPLICATION_NAME;
$appVersion = $appVersion ?: self::VERSION;
$app = new \Robo\Application($appName, $appVersion);
$app->setAutoExit(false);
return $app;
}
/**
* Add the Robo League\Container inflectors to the container
*
* @param \League\Container\ContainerInterface $container
*/
public static function addInflectors($container)
{
// Register our various inflectors.
$container->inflector(\Robo\Contract\ConfigAwareInterface::class)
->invokeMethod('setConfig', ['config']);
$container->inflector(\Psr\Log\LoggerAwareInterface::class)
->invokeMethod('setLogger', ['logger']);
$container->inflector(\League\Container\ContainerAwareInterface::class)
->invokeMethod('setContainer', ['container']);
$container->inflector(\Symfony\Component\Console\Input\InputAwareInterface::class)
->invokeMethod('setInput', ['input']);
$container->inflector(\Robo\Contract\OutputAwareInterface::class)
->invokeMethod('setOutput', ['output']);
$container->inflector(\Robo\Contract\ProgressIndicatorAwareInterface::class)
->invokeMethod('setProgressIndicator', ['progressIndicator']);
}
/**
* Retrieves a service from the container.
*
* Use this method if the desired service is not one of those with a dedicated
* accessor method below. If it is listed below, those methods are preferred
* as they can return useful type hints.
*
* @param string $id
* The ID of the service to retrieve.
*
* @return mixed
* The specified service.
*/
public static function service($id)
{
return static::getContainer()->get($id);
}
/**
* Indicates if a service is defined in the container.
*
* @param string $id
* The ID of the service to check.
*
* @return bool
* TRUE if the specified service exists, FALSE otherwise.
*/
public static function hasService($id)
{
// Check hasContainer() first in order to always return a Boolean.
return static::hasContainer() && static::getContainer()->has($id);
}
/**
* Return the result printer object.
*
* @return \Robo\Log\ResultPrinter
*/
public static function resultPrinter()
{
return static::service('resultPrinter');
}
/**
* @return \Robo\Config
*/
public static function config()
{
return static::service('config');
}
/**
* @return \Consolidation\Log\Logger
*/
public static function logger()
{
return static::service('logger');
}
/**
* @return \Robo\Application
*/
public static function application()
{
return static::service('application');
}
/**
* Return the output object.
*
* @return \Symfony\Component\Console\Output\OutputInterface
*/
public static function output()
{
return static::service('output');
}
/**
* Return the input object.
*
* @return \Symfony\Component\Console\Input\InputInterface
*/
public static function input()
{
return static::service('input');
}
}

View File

@@ -0,0 +1,419 @@
<?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\Common\IO;
use Robo\Exception\TaskExitException;
use League\Container\ContainerInterface;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
class Runner implements ContainerAwareInterface
{
const ROBOCLASS = 'RoboFile';
const ROBOFILE = 'RoboFile.php';
use IO;
use ContainerAwareTrait;
/**
* @var string
*/
protected $roboClass;
/**
* @var string
*/
protected $roboFile;
/**
* @var string working dir of Robo
*/
protected $dir;
/**
* Class Constructor
*
* @param null|string $roboClass
* @param null|string $roboFile
*/
public function __construct($roboClass = null, $roboFile = null)
{
// set the const as class properties to allow overwriting in child classes
$this->roboClass = $roboClass ? $roboClass : self::ROBOCLASS ;
$this->roboFile = $roboFile ? $roboFile : self::ROBOFILE;
$this->dir = getcwd();
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return bool
*/
protected function loadRoboFile($output)
{
// If we have not been provided an output object, make a temporary one.
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
// If $this->roboClass is a single class that has not already
// been loaded, then we will try to obtain it from $this->roboFile.
// If $this->roboClass is an array, we presume all classes requested
// are available via the autoloader.
if (is_array($this->roboClass) || class_exists($this->roboClass)) {
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>");
return false;
}
$realDir = realpath($this->dir);
$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>");
return false;
}
require_once $roboFilePath;
if (!class_exists($this->roboClass)) {
$output->writeln("<error>Class ".$this->roboClass." was not loaded</error>");
return false;
}
return true;
}
/**
* @param array $argv
* @param null|string $appName
* @param null|string $appVersion
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*
* @return int
*/
public function execute($argv, $appName = null, $appVersion = null, $output = null)
{
$argv = $this->shebang($argv);
$argv = $this->processRoboOptions($argv);
$app = null;
if ($appName && $appVersion) {
$app = Robo::createDefaultApplication($appName, $appVersion);
}
$commandFiles = $this->getRoboFileCommands($output);
return $this->run($argv, $output, $app, $commandFiles);
}
/**
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Robo\Application $app
* @param array[] $commandFiles
*
* @return int
*/
public function run($input = null, $output = null, $app = null, $commandFiles = [])
{
// Create default input and output objects if they were not provided
if (!$input) {
$input = new StringInput('');
}
if (is_array($input)) {
$input = new ArgvInput($input);
}
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
$this->setInput($input);
$this->setOutput($output);
// If we were not provided a container, then create one
if (!$this->getContainer()) {
$container = Robo::createDefaultContainer($input, $output, $app);
$this->setContainer($container);
// Automatically register a shutdown function and
// an error handler when we provide the container.
$this->installRoboHandlers();
}
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 = [];
}
$this->registerCommandClasses($app, $commandFiles);
try {
$statusCode = $app->run($input, $output);
} catch (TaskExitException $e) {
$statusCode = $e->getCode() ?: 1;
}
return $statusCode;
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return null|string
*/
protected function getRoboFileCommands($output)
{
if (!$this->loadRoboFile($output)) {
return;
}
return $this->roboClass;
}
/**
* @param \Robo\Application $app
* @param array $commandClasses
*/
public function registerCommandClasses($app, $commandClasses)
{
foreach ((array)$commandClasses as $commandClass) {
$this->registerCommandClass($app, $commandClass);
}
}
/**
* @param \Robo\Application $app
* @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass
*
* @return mixed|void
*/
public function registerCommandClass($app, $commandClass)
{
$container = Robo::getContainer();
$roboCommandFileInstance = $this->instantiateCommandClass($commandClass);
if (!$roboCommandFileInstance) {
return;
}
// Register commands for all of the public methods in the RoboFile.
$commandFactory = $container->get('commandFactory');
$commandList = $commandFactory->createCommandsFromClass($roboCommandFileInstance);
foreach ($commandList as $command) {
$app->add($command);
}
return $roboCommandFileInstance;
}
/**
* @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass
*
* @return null|object
*/
protected function instantiateCommandClass($commandClass)
{
$container = Robo::getContainer();
// Register the RoboFile with the container and then immediately
// fetch it; this ensures that all of the inflectors will run.
// If the command class is already an instantiated object, then
// just use it exactly as it was provided to us.
if (is_string($commandClass)) {
$reflectionClass = new \ReflectionClass($commandClass);
if ($reflectionClass->isAbstract()) {
return;
}
$commandFileName = "{$commandClass}Commands";
$container->share($commandFileName, $commandClass);
$commandClass = $container->get($commandFileName);
}
// If the command class is a Builder Aware Interface, then
// 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]);
$commandClass->setBuilder($builder);
}
if ($commandClass instanceof ContainerAwareInterface) {
$commandClass->setContainer($container);
}
return $commandClass;
}
public function installRoboHandlers()
{
register_shutdown_function(array($this, 'shutdown'));
set_error_handler(array($this, 'handleError'));
}
/**
* Process a shebang script, if one was used to launch this Runner.
*
* @param array $args
*
* @return array $args with shebang script removed
*/
protected function shebang($args)
{
// Option 1: Shebang line names Robo, but includes no parameters.
// #!/bin/env robo
// The robo class may contain multiple commands; the user may
// select which one to run, or even get a list of commands or
// run 'help' on any of the available commands as usual.
if ((count($args) > 1) && $this->isShebangFile($args[1])) {
return array_merge([$args[0]], array_slice($args, 2));
}
// Option 2: Shebang line stipulates which command to run.
// #!/bin/env robo mycommand
// The robo class must contain a public method named 'mycommand'.
// This command will be executed every time. Arguments and options
// may be provided on the commandline as usual.
if ((count($args) > 2) && $this->isShebangFile($args[2])) {
return array_merge([$args[0]], explode(' ', $args[1]), array_slice($args, 3));
}
return $args;
}
/**
* Determine if the specified argument is a path to a shebang script.
* If so, load it.
*
* @param string $filepath file to check
*
* @return bool Returns TRUE if shebang script was processed
*/
protected function isShebangFile($filepath)
{
if (!is_file($filepath)) {
return false;
}
$fp = fopen($filepath, "r");
if ($fp === false) {
return false;
}
$line = fgets($fp);
$result = $this->isShebangLine($line);
if ($result) {
while ($line = fgets($fp)) {
$line = trim($line);
if ($line == '<?php') {
$script = stream_get_contents($fp);
if (preg_match('#^class *([^ ]+)#m', $script, $matches)) {
$this->roboClass = $matches[1];
eval($script);
$result = true;
}
}
}
}
fclose($fp);
return $result;
}
/**
* Test to see if the provided line is a robo 'shebang' line.
*
* @param string $line
*
* @return bool
*/
protected function isShebangLine($line)
{
return ((substr($line, 0, 2) == '#!') && (strstr($line, 'robo') !== false));
}
/**
* Check for Robo-specific arguments such as --load-from, process them,
* and remove them from the array. We have to process --load-from before
* we set up Symfony Console.
*
* @param array $argv
*
* @return array
*/
protected function processRoboOptions($argv)
{
// loading from other directory
$pos = $this->arraySearchBeginsWith('--load-from', $argv) ?: array_search('-f', $argv);
if ($pos === false) {
return $argv;
}
$passThru = array_search('--', $argv);
if (($passThru !== false) && ($passThru < $pos)) {
return $argv;
}
if (substr($argv[$pos], 0, 12) == '--load-from=') {
$this->dir = substr($argv[$pos], 12);
} elseif (isset($argv[$pos +1])) {
$this->dir = $argv[$pos +1];
unset($argv[$pos +1]);
}
unset($argv[$pos]);
// Make adjustments if '--load-from' points at a file.
if (is_file($this->dir) || (substr($this->dir, -4) == '.php')) {
$this->roboFile = basename($this->dir);
$this->dir = dirname($this->dir);
$className = basename($this->roboFile, '.php');
if ($className != $this->roboFile) {
$this->roboClass = $className;
}
}
// Convert directory to a real path, but only if the
// path exists. We do not want to lose the original
// directory if the user supplied a bad value.
$realDir = realpath($this->dir);
if ($realDir) {
chdir($realDir);
$this->dir = $realDir;
}
return $argv;
}
/**
* @param string $needle
* @param string[] $haystack
*
* @return bool|int
*/
protected function arraySearchBeginsWith($needle, $haystack)
{
for ($i = 0; $i < count($haystack); ++$i) {
if (substr($haystack[$i], 0, strlen($needle)) == $needle) {
return $i;
}
}
return false;
}
public function shutdown()
{
$error = error_get_last();
if (!is_array($error)) {
return;
}
$this->writeln(sprintf("<error>ERROR: %s \nin %s:%d\n</error>", $error['message'], $error['file'], $error['line']));
}
/**
* This is just a proxy error handler that checks the current error_reporting level.
* In case error_reporting is disabled the error is marked as handled, otherwise
* the normal internal error handling resumes.
*
* @return bool
*/
public function handleError()
{
if (error_reporting() === 0) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,482 @@
<?php
namespace Robo\Task\ApiGen;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Traversable;
/**
* Executes ApiGen command to generate documentation
*
* ``` php
* <?php
* // ApiGen Command
* $this->taskApiGen('./apigen.neon')
* ->templateConfig('vendor/apigen/apigen/templates/bootstrap/config.neon')
* ->wipeout(true)
* ->run();
* ?>
* ```
*/
class ApiGen extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
const BOOL_NO = 'no';
const BOOL_YES = 'yes';
/**
* @var string
*/
protected $command;
/**
* @param null|string $pathToApiGen
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToApiGen = null)
{
$this->command = $pathToApiGen;
if (!$this->command) {
$this->command = $this->findExecutablePhar('apigen');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "No apigen installation found");
}
}
/**
* @param array|Traversable|string $arg a single object or something traversable
*
* @return array|Traversable the provided argument if it was already traversable, or the given
* argument returned as a one-element array
*/
protected static function forceTraversable($arg)
{
$traversable = $arg;
if (!is_array($traversable) && !($traversable instanceof \Traversable)) {
$traversable = array($traversable);
}
return $traversable;
}
/**
* @param array|string $arg a single argument or an array of multiple string values
*
* @return string a comma-separated string of all of the provided arguments, suitable
* as a command-line "list" type argument for ApiGen
*/
protected static function asList($arg)
{
$normalized = is_array($arg) ? $arg : array($arg);
return implode(',', $normalized);
}
/**
* @param bool|string $val an argument to be normalized
* @param string $default one of self::BOOL_YES or self::BOOK_NO if the provided
* value could not deterministically be converted to a
* yes or no value
*
* @return string the given value as a command-line "yes|no" type of argument for ApiGen,
* or the default value if none could be determined
*/
protected static function asTextBool($val, $default)
{
if ($val === self::BOOL_YES || $val === self::BOOL_NO) {
return $val;
}
if (!$val) {
return self::BOOL_NO;
}
if ($val === true) {
return self::BOOL_YES;
}
if (is_numeric($val) && $val != 0) {
return self::BOOL_YES;
}
if (strcasecmp($val[0], 'y') === 0) {
return self::BOOL_YES;
}
if (strcasecmp($val[0], 'n') === 0) {
return self::BOOL_NO;
}
// meh, good enough, let apigen sort it out
return $default;
}
/**
* @param string $config
*
* @return $this
*/
public function config($config)
{
$this->option('config', $config);
return $this;
}
/**
* @param array|string|Traversable $src one or more source values
*
* @return $this
*/
public function source($src)
{
foreach (self::forceTraversable($src) as $source) {
$this->option('source', $source);
}
return $this;
}
/**
* @param string $dest
*
* @return $this
*/
public function destination($dest)
{
$this->option('destination', $dest);
return $this;
}
/**
* @param array|string $exts one or more extensions
*
* @return $this
*/
public function extensions($exts)
{
$this->option('extensions', self::asList($exts));
return $this;
}
/**
* @param array|string $exclude one or more exclusions
*
* @return $this
*/
public function exclude($exclude)
{
foreach (self::forceTraversable($exclude) as $excl) {
$this->option('exclude', $excl);
}
return $this;
}
/**
* @param array|string|Traversable $path one or more skip-doc-path values
*
* @return $this
*/
public function skipDocPath($path)
{
foreach (self::forceTraversable($path) as $skip) {
$this->option('skip-doc-path', $skip);
}
return $this;
}
/**
* @param array|string|Traversable $prefix one or more skip-doc-prefix values
*
* @return $this
*/
public function skipDocPrefix($prefix)
{
foreach (self::forceTraversable($prefix) as $skip) {
$this->option('skip-doc-prefix', $skip);
}
return $this;
}
/**
* @param array|string $charset one or more charsets
*
* @return $this
*/
public function charset($charset)
{
$this->option('charset', self::asList($charset));
return $this;
}
/**
* @param string $name
*
* @return $this
*/
public function mainProjectNamePrefix($name)
{
$this->option('main', $name);
return $this;
}
/**
* @param string $title
*
* @return $this
*/
public function title($title)
{
$this->option('title', $title);
return $this;
}
/**
* @param string $baseUrl
*
* @return $this
*/
public function baseUrl($baseUrl)
{
$this->option('base-url', $baseUrl);
return $this;
}
/**
* @param string $id
*
* @return $this
*/
public function googleCseId($id)
{
$this->option('google-cse-id', $id);
return $this;
}
/**
* @param string $trackingCode
*
* @return $this
*/
public function googleAnalytics($trackingCode)
{
$this->option('google-analytics', $trackingCode);
return $this;
}
/**
* @param mixed $templateConfig
*
* @return $this
*/
public function templateConfig($templateConfig)
{
$this->option('template-config', $templateConfig);
return $this;
}
/**
* @param array|string $tags one or more supported html tags
*
* @return $this
*/
public function allowedHtml($tags)
{
$this->option('allowed-html', self::asList($tags));
return $this;
}
/**
* @param string $groups
*
* @return $this
*/
public function groups($groups)
{
$this->option('groups', $groups);
return $this;
}
/**
* @param array|string $types or more supported autocomplete types
*
* @return $this
*/
public function autocomplete($types)
{
$this->option('autocomplete', self::asList($types));
return $this;
}
/**
* @param array|string $levels one or more access levels
*
* @return $this
*/
public function accessLevels($levels)
{
$this->option('access-levels', self::asList($levels));
return $this;
}
/**
* @param boolean|string $internal 'yes' or true if internal, 'no' or false if not
*
* @return $this
*/
public function internal($internal)
{
$this->option('internal', self::asTextBool($internal, self::BOOL_NO));
return $this;
}
/**
* @param boolean|string $php 'yes' or true to generate documentation for internal php classes,
* 'no' or false otherwise
*
* @return $this
*/
public function php($php)
{
$this->option('php', self::asTextBool($php, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $tree 'yes' or true to generate a tree view of classes, 'no' or false otherwise
*
* @return $this
*/
public function tree($tree)
{
$this->option('tree', self::asTextBool($tree, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $dep 'yes' or true to generate documentation for deprecated classes, 'no' or false otherwise
*
* @return $this
*/
public function deprecated($dep)
{
$this->option('deprecated', self::asTextBool($dep, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $todo 'yes' or true to document tasks, 'no' or false otherwise
*
* @return $this
*/
public function todo($todo)
{
$this->option('todo', self::asTextBool($todo, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $src 'yes' or true to generate highlighted source code, 'no' or false otherwise
*
* @return $this
*/
public function sourceCode($src)
{
$this->option('source-code', self::asTextBool($src, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $zipped 'yes' or true to generate downloadable documentation, 'no' or false otherwise
*
* @return $this
*/
public function download($zipped)
{
$this->option('download', self::asTextBool($zipped, self::BOOL_NO));
return $this;
}
public function report($path)
{
$this->option('report', $path);
return $this;
}
/**
* @param bool|string $wipeout 'yes' or true to clear out the destination directory, 'no' or false otherwise
*
* @return $this
*/
public function wipeout($wipeout)
{
$this->option('wipeout', self::asTextBool($wipeout, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $quiet 'yes' or true for quiet, 'no' or false otherwise
*
* @return $this
*/
public function quiet($quiet)
{
$this->option('quiet', self::asTextBool($quiet, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $bar 'yes' or true to display a progress bar, 'no' or false otherwise
*
* @return $this
*/
public function progressbar($bar)
{
$this->option('progressbar', self::asTextBool($bar, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $colors 'yes' or true colorize the output, 'no' or false otherwise
*
* @return $this
*/
public function colors($colors)
{
$this->option('colors', self::asTextBool($colors, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $check 'yes' or true to check for updates, 'no' or false otherwise
*
* @return $this
*/
public function updateCheck($check)
{
$this->option('update-check', self::asTextBool($check, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $debug 'yes' or true to enable debug mode, 'no' or false otherwise
*
* @return $this
*/
public function debug($debug)
{
$this->option('debug', self::asTextBool($debug, self::BOOL_NO));
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running ApiGen {args}', ['args' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Robo\Task\ApiGen;
trait loadTasks
{
/**
* @param null|string $pathToApiGen
*
* @return \Robo\Task\ApiGen\ApiGen
*/
protected function taskApiGen($pathToApiGen = null)
{
return $this->task(ApiGen::class, $pathToApiGen);
}
}

View File

@@ -0,0 +1,281 @@
<?php
namespace Robo\Task\Archive;
use Robo\Result;
use Robo\Task\BaseTask;
use Robo\Task\Filesystem\FilesystemStack;
use Robo\Task\Filesystem\DeleteDir;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Extracts an archive.
*
* Note that often, distributions are packaged in tar or zip archives
* where the topmost folder may contain variable information, such as
* the release date, or the version of the package. This information
* is very useful when unpacking by hand, but arbitrarily-named directories
* are much less useful to scripts. Therefore, by default, Extract will
* remove the top-level directory, and instead store all extracted files
* into the directory specified by $archivePath.
*
* To keep the top-level directory when extracting, use
* `preserveTopDirectory(true)`.
*
* ``` php
* <?php
* $this->taskExtract($archivePath)
* ->to($destination)
* ->preserveTopDirectory(false) // the default
* ->run();
* ?>
* ```
*
* @method to(string) location to store extracted files
*/
class Extract extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $to;
/**
* @var bool
*/
private $preserveTopDirectory = false;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* Location to store extracted files.
*
* @param string $to
*
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* @param bool $preserve
*
* @return $this
*/
public function preserveTopDirectory($preserve = true)
{
$this->preserveTopDirectory = $preserve;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!file_exists($this->filename)) {
$this->printTaskError("File {filename} does not exist", ['filename' => $this->filename]);
return false;
}
if (!($mimetype = static::archiveType($this->filename))) {
$this->printTaskError("Could not determine type of archive for {filename}", ['filename' => $this->filename]);
return false;
}
// We will first extract to $extractLocation and then move to $this->to
$extractLocation = static::getTmpDir();
@mkdir($extractLocation);
@mkdir(dirname($this->to));
$this->startTimer();
$this->printTaskInfo("Extracting {filename}", ['filename' => $this->filename]);
$result = $this->extractAppropriateType($mimetype, $extractLocation);
if ($result->wasSuccessful()) {
$this->printTaskInfo("{filename} extracted", ['filename' => $this->filename]);
// Now, we want to move the extracted files to $this->to. There
// are two possibilities that we must consider:
//
// (1) Archived files were encapsulated in a folder with an arbitrary name
// (2) There was no encapsulating folder, and all the files in the archive
// were extracted into $extractLocation
//
// In the case of (1), we want to move and rename the encapsulating folder
// to $this->to.
//
// In the case of (2), we will just move and rename $extractLocation.
$filesInExtractLocation = glob("$extractLocation/*");
$hasEncapsulatingFolder = ((count($filesInExtractLocation) == 1) && is_dir($filesInExtractLocation[0]));
if ($hasEncapsulatingFolder && !$this->preserveTopDirectory) {
$result = (new FilesystemStack())
->inflect($this)
->rename($filesInExtractLocation[0], $this->to)
->run();
(new DeleteDir($extractLocation))
->inflect($this)
->run();
} else {
$result = (new FilesystemStack())
->inflect($this)
->rename($extractLocation, $this->to)
->run();
}
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $mimetype
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractAppropriateType($mimetype, $extractLocation)
{
// Perform the extraction of a zip file.
if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
return $this->extractZip($extractLocation);
}
return $this->extractTar($extractLocation);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractZip($extractLocation)
{
if (!extension_loaded('zlib')) {
return Result::errorMissingExtension($this, 'zlib', 'zip extracting');
}
$zip = new \ZipArchive();
if (($status = $zip->open($this->filename)) !== true) {
return Result::error($this, "Could not open zip archive {$this->filename}");
}
if (!$zip->extractTo($extractLocation)) {
return Result::error($this, "Could not extract zip archive {$this->filename}");
}
$zip->close();
return Result::success($this);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractTar($extractLocation)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($this->filename);
if (!$tar_object->extract($extractLocation)) {
return Result::error($this, "Could not extract tar archive {$this->filename}");
}
return Result::success($this);
}
/**
* @param string $filename
*
* @return bool|string
*/
protected static function archiveType($filename)
{
$content_type = false;
if (class_exists('finfo')) {
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$content_type = $finfo->file($filename);
// If finfo cannot determine the content type, then we will try other methods
if ($content_type == 'application/octet-stream') {
$content_type = false;
}
}
// Examing the file's magic header bytes.
if (!$content_type) {
if ($file = fopen($filename, 'rb')) {
$first = fread($file, 2);
fclose($file);
if ($first !== false) {
// Interpret the two bytes as a little endian 16-bit unsigned int.
$data = unpack('v', $first);
switch ($data[1]) {
case 0x8b1f:
// First two bytes of gzip files are 0x1f, 0x8b (little-endian).
// See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
$content_type = 'application/x-gzip';
break;
case 0x4b50:
// First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
// See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
$content_type = 'application/zip';
break;
case 0x5a42:
// First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
// See http://en.wikipedia.org/wiki/Bzip2#File_format
$content_type = 'application/x-bzip2';
break;
}
}
}
}
// 3. Lastly if above methods didn't work, try to guess the mime type from
// the file extension. This is useful if the file has no identificable magic
// header bytes (for example tarballs).
if (!$content_type) {
// Remove querystring from the filename, if present.
$filename = basename(current(explode('?', $filename, 2)));
$extension_mimetype = array(
'.tar.gz' => 'application/x-gzip',
'.tgz' => 'application/x-gzip',
'.tar' => 'application/x-tar',
);
foreach ($extension_mimetype as $extension => $ct) {
if (substr($filename, -strlen($extension)) === $extension) {
$content_type = $ct;
break;
}
}
}
return $content_type;
}
/**
* @return string
*/
protected static function getTmpDir()
{
return getcwd().'/tmp'.rand().time();
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace Robo\Task\Archive;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Finder\Finder;
/**
* Creates a zip or tar archive.
*
* ``` php
* <?php
* $this->taskPack(
* <archiveFile>)
* ->add('README') // Puts file 'README' in archive at the root
* ->add('project') // Puts entire contents of directory 'project' in archinve inside 'project'
* ->addFile('dir/file.txt', 'file.txt') // Takes 'file.txt' from cwd and puts it in archive inside 'dir'.
* ->run();
* ?>
* ```
*/
class Pack extends BaseTask implements PrintedInterface
{
/**
* The list of items to be packed into the archive.
*
* @var array
*/
private $items = [];
/**
* The full path to the archive to be created.
*
* @var string
*/
private $archiveFile;
/**
* Construct the class.
*
* @param string $archiveFile The full path and name of the archive file to create.
*
* @since 1.0
*/
public function __construct($archiveFile)
{
$this->archiveFile = $archiveFile;
}
/**
* Satisfy the parent requirement.
*
* @return bool Always returns true.
*
* @since 1.0
*/
public function getPrinted()
{
return true;
}
/**
* @param string $archiveFile
*
* @return $this
*/
public function archiveFile($archiveFile)
{
$this->archiveFile = $archiveFile;
return $this;
}
/**
* Add an item to the archive. Like file_exists(), the parameter
* may be a file or a directory.
*
* @var string
* Relative path and name of item to store in archive
* @var string
* Absolute or relative path to file or directory's location in filesystem
*
* @return $this
*/
public function addFile($placementLocation, $filesystemLocation)
{
$this->items[$placementLocation] = $filesystemLocation;
return $this;
}
/**
* Alias for addFile, in case anyone has angst about using
* addFile with a directory.
*
* @var string
* Relative path and name of directory to store in archive
* @var string
* Absolute or relative path to directory or directory's location in filesystem
*
* @return $this
*/
public function addDir($placementLocation, $filesystemLocation)
{
$this->addFile($placementLocation, $filesystemLocation);
return $this;
}
/**
* Add a file or directory, or list of same to the archive.
*
* @var string|array
* If given a string, should contain the relative filesystem path to the
* the item to store in archive; this will also be used as the item's
* path in the archive, so absolute paths should not be used here.
* If given an array, the key of each item should be the path to store
* in the archive, and the value should be the filesystem path to the
* item to store.
* @return $this
*/
public function add($item)
{
if (is_array($item)) {
$this->items = array_merge($this->items, $item);
} else {
$this->addFile($item, $item);
}
return $this;
}
/**
* Create a zip archive for distribution.
*
* @return \Robo\Result
*
* @since 1.0
*/
public function run()
{
$this->startTimer();
// Use the file extension to determine what kind of archive to create.
$fileInfo = new \SplFileInfo($this->archiveFile);
$extension = strtolower($fileInfo->getExtension());
if (empty($extension)) {
return Result::error($this, "Archive filename must use an extension (e.g. '.zip') to specify the kind of archive to create.");
}
try {
// Inform the user which archive we are creating
$this->printTaskInfo("Creating archive {filename}", ['filename' => $this->archiveFile]);
if ($extension == 'zip') {
$result = $this->archiveZip($this->archiveFile, $this->items);
} else {
$result = $this->archiveTar($this->archiveFile, $this->items);
}
$this->printTaskSuccess("{filename} created.", ['filename' => $this->archiveFile]);
} catch (\Exception $e) {
$this->printTaskError("Could not create {filename}. {exception}", ['filename' => $this->archiveFile, 'exception' => $e->getMessage(), '_style' => ['exception' => '']]);
$result = Result::error($this, sprintf('Could not create %s. %s', $this->archiveFile, $e->getMessage()));
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveTar($archiveFile, $items)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($archiveFile);
foreach ($items as $placementLocation => $filesystemLocation) {
$p_remove_dir = $filesystemLocation;
$p_add_dir = $placementLocation;
if (is_file($filesystemLocation)) {
$p_remove_dir = dirname($filesystemLocation);
$p_add_dir = dirname($placementLocation);
if (basename($filesystemLocation) != basename($placementLocation)) {
return Result::error($this, "Tar archiver does not support renaming files during extraction; could not add $filesystemLocation as $placementLocation.");
}
}
if (!$tar_object->addModify([$filesystemLocation], $p_add_dir, $p_remove_dir)) {
return Result::error($this, "Could not add $filesystemLocation to the archive.");
}
}
return Result::success($this);
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveZip($archiveFile, $items)
{
if (!extension_loaded('zlib')) {
return Result::errorMissingExtension($this, 'zlib', 'zip packing');
}
$zip = new \ZipArchive($archiveFile, \ZipArchive::CREATE);
if (!$zip->open($archiveFile, \ZipArchive::CREATE)) {
return Result::error($this, "Could not create zip archive {$archiveFile}");
}
$result = $this->addItemsToZip($zip, $items);
$zip->close();
return $result;
}
/**
* @param \ZipArchive $zip
* @param array $items
*
* @return \Robo\Result
*/
protected function addItemsToZip($zip, $items)
{
foreach ($items as $placementLocation => $filesystemLocation) {
if (is_dir($filesystemLocation)) {
$finder = new Finder();
$finder->files()->in($filesystemLocation)->ignoreDotFiles(false);
foreach ($finder as $file) {
// Replace Windows slashes or resulting zip will have issues on *nixes.
$relativePathname = str_replace('\\', '/', $file->getRelativePathname());
if (!$zip->addFile($file->getRealpath(), "{$placementLocation}/{$relativePathname}")) {
return Result::error($this, "Could not add directory $filesystemLocation to the archive; error adding {$file->getRealpath()}.");
}
}
} elseif (is_file($filesystemLocation)) {
if (!$zip->addFile($filesystemLocation, $placementLocation)) {
return Result::error($this, "Could not add file $filesystemLocation to the archive.");
}
} else {
return Result::error($this, "Could not find $filesystemLocation for the archive.");
}
}
return Result::success($this);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Robo\Task\Archive;
trait loadTasks
{
/**
* @param $filename
*
* @return Pack
*/
protected function taskPack($filename)
{
return $this->task(Pack::class, $filename);
}
/**
* @param $filename
*
* @return Extract
*/
protected function taskExtract($filename)
{
return $this->task(Extract::class, $filename);
}
}

Some files were not shown because too many files have changed in this diff Show More