PK }?V3VK K phpunit.xml.distnu W+A
tests
src
PK }?V3c3c 3c
composer.locknu W+A {
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "180dd9d3779b6c35cdb29faac7f66db3",
"packages": [
{
"name": "consolidation/output-formatters",
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/consolidation/output-formatters.git",
"reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5",
"reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"symfony/console": "^2.8|^3|^4",
"symfony/finder": "^2.5|^3|^4"
},
"require-dev": {
"g1a/composer-test-scenarios": "^2",
"phpunit/phpunit": "^5.7.27",
"satooshi/php-coveralls": "^2",
"squizlabs/php_codesniffer": "^2.7",
"symfony/console": "3.2.3",
"symfony/var-dumper": "^2.8|^3|^4",
"victorjonsson/markdowndocs": "^1.3"
},
"suggest": {
"symfony/var-dumper": "For using the var_dump formatter"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Consolidation\\OutputFormatters\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"description": "Format text by applying transformations provided by plug-in formatters.",
"time": "2018-05-25T18:02:34+00:00"
},
{
"name": "psr/log",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Log\\": "Psr/Log/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for logging libraries",
"homepage": "https://github.com/php-fig/log",
"keywords": [
"log",
"psr",
"psr-3"
],
"time": "2016-10-10T12:19:37+00:00"
},
{
"name": "symfony/console",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/6b217594552b9323bcdcfc14f8a0ce126e84cd73",
"reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/debug": "~2.8|~3.0|~4.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/dependency-injection": "<3.4",
"symfony/process": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~3.3|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/event-dispatcher": "~2.8|~3.0|~4.0",
"symfony/lock": "~3.4|~4.0",
"symfony/process": "~3.3|~4.0"
},
"suggest": {
"psr/log-implementation": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T11:19:56+00:00"
},
{
"name": "symfony/debug",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
"reference": "c4625e75341e4fb309ce0c049cbf7fb84b8897cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/debug/zipball/c4625e75341e4fb309ce0c049cbf7fb84b8897cd",
"reference": "c4625e75341e4fb309ce0c049cbf7fb84b8897cd",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"psr/log": "~1.0"
},
"conflict": {
"symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
},
"require-dev": {
"symfony/http-kernel": "~2.8|~3.0|~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Debug\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
"time": "2018-08-03T10:42:44+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb",
"reference": "b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8"
},
"conflict": {
"symfony/dependency-injection": "<3.3"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.8|~3.0|~4.0",
"symfony/dependency-injection": "~3.3|~4.0",
"symfony/expression-language": "~2.8|~3.0|~4.0",
"symfony/stopwatch": "~2.8|~3.0|~4.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T09:06:28+00:00"
},
{
"name": "symfony/finder",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/8a84fcb207451df0013b2c74cbbf1b62d47b999a",
"reference": "8a84fcb207451df0013b2c74cbbf1b62d47b999a",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T11:19:56+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.9.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8",
"reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2018-08-06T14:22:27+00:00"
}
],
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"time": "2015-06-14T21:17:01+00:00"
},
{
"name": "g1a/composer-test-scenarios",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/g1a/composer-test-scenarios.git",
"reference": "a166fd15191aceab89f30c097e694b7cf3db4880"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880",
"reference": "a166fd15191aceab89f30c097e694b7cf3db4880",
"shasum": ""
},
"bin": [
"scripts/create-scenario",
"scripts/dependency-licenses",
"scripts/install-scenario"
],
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"description": "Useful scripts for testing multiple sets of Composer dependencies.",
"time": "2018-08-08T23:37:23+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.3-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"shasum": ""
},
"require": {
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2017-03-20T17:10:46+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
"reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
},
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
"phpunit/phpunit": "^4.1"
},
"type": "library",
"autoload": {
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
},
"files": [
"src/DeepCopy/deep_copy.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Create deep copies (clones) of your objects",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"time": "2017-10-19T19:58:43+00:00"
},
{
"name": "phar-io/manifest",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
"reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0",
"reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-phar": "*",
"phar-io/version": "^1.0.1",
"php": "^5.6 || ^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"time": "2017-03-05T18:14:27+00:00"
},
{
"name": "phar-io/version",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phar-io/version.git",
"reference": "a70c0ced4be299a63d32fa96d9281d03e94041df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df",
"reference": "a70c0ced4be299a63d32fa96d9281d03e94041df",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Library for handling version information and constraints",
"time": "2017-03-05T17:38:23+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jaap van Otterdijk",
"email": "opensource@ijaap.nl"
}
],
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
"homepage": "http://www.phpdoc.org",
"keywords": [
"FQSEN",
"phpDocumentor",
"phpdoc",
"reflection",
"static analysis"
],
"time": "2017-09-11T18:02:19+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "4.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "94fd0001232e47129dd3504189fa1c7225010d08"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
"reference": "94fd0001232e47129dd3504189fa1c7225010d08",
"shasum": ""
},
"require": {
"php": "^7.0",
"phpdocumentor/reflection-common": "^1.0.0",
"phpdocumentor/type-resolver": "^0.4.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
"doctrine/instantiator": "~1.0.5",
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^6.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2017-11-30T07:14:17+00:00"
},
{
"name": "phpdocumentor/type-resolver",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
"reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
"shasum": ""
},
"require": {
"php": "^5.5 || ^7.0",
"phpdocumentor/reflection-common": "^1.0"
},
"require-dev": {
"mockery/mockery": "^0.9.4",
"phpunit/phpunit": "^5.2||^4.8.24"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"time": "2017-07-14T14:27:02+00:00"
},
{
"name": "phpspec/prophecy",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
"reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
},
"require-dev": {
"phpspec/phpspec": "^2.5|^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev"
}
},
"autoload": {
"psr-0": {
"Prophecy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"time": "2018-08-05T17:53:17+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "5.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "c89677919c5dd6d3b3852f230a663118762218ac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac",
"reference": "c89677919c5dd6d3b3852f230a663118762218ac",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-xmlwriter": "*",
"php": "^7.0",
"phpunit/php-file-iterator": "^1.4.2",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-token-stream": "^2.0.1",
"sebastian/code-unit-reverse-lookup": "^1.0.1",
"sebastian/environment": "^3.0",
"sebastian/version": "^2.0.1",
"theseer/tokenizer": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"suggest": {
"ext-xdebug": "^2.5.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"time": "2018-04-06T15:36:58+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "1.4.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
"reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"time": "2017-11-27T13:52:08+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21T13:50:34+00:00"
},
{
"name": "phpunit/php-timer",
"version": "1.0.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
"reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"time": "2017-02-26T11:10:40+00:00"
},
{
"name": "phpunit/php-token-stream",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "791198a2c6254db10131eecfe8c06670700904db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
"reference": "791198a2c6254db10131eecfe8c06670700904db",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.2.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
"tokenizer"
],
"time": "2017-11-27T05:48:46+00:00"
},
{
"name": "phpunit/phpunit",
"version": "6.5.12",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "24da433d7384824d65ea93fbb462e2f31bbb494e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/24da433d7384824d65ea93fbb462e2f31bbb494e",
"reference": "24da433d7384824d65ea93fbb462e2f31bbb494e",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"myclabs/deep-copy": "^1.6.1",
"phar-io/manifest": "^1.0.1",
"phar-io/version": "^1.0",
"php": "^7.0",
"phpspec/prophecy": "^1.7",
"phpunit/php-code-coverage": "^5.3",
"phpunit/php-file-iterator": "^1.4.3",
"phpunit/php-text-template": "^1.2.1",
"phpunit/php-timer": "^1.0.9",
"phpunit/phpunit-mock-objects": "^5.0.9",
"sebastian/comparator": "^2.1",
"sebastian/diff": "^2.0",
"sebastian/environment": "^3.1",
"sebastian/exporter": "^3.1",
"sebastian/global-state": "^2.0",
"sebastian/object-enumerator": "^3.0.3",
"sebastian/resource-operations": "^1.0",
"sebastian/version": "^2.0.1"
},
"conflict": {
"phpdocumentor/reflection-docblock": "3.0.2",
"phpunit/dbunit": "<3.0"
},
"require-dev": {
"ext-pdo": "*"
},
"suggest": {
"ext-xdebug": "*",
"phpunit/php-invoker": "^1.1"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.5.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2018-08-22T06:32:48+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "5.0.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f",
"reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.5",
"php": "^7.0",
"phpunit/php-text-template": "^1.2.1",
"sebastian/exporter": "^3.1"
},
"conflict": {
"phpunit/phpunit": "<6.0"
},
"require-dev": {
"phpunit/phpunit": "^6.5.11"
},
"suggest": {
"ext-soap": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Mock Object library for PHPUnit",
"homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
"keywords": [
"mock",
"xunit"
],
"time": "2018-08-09T05:50:03+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "satooshi/php-coveralls",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-coveralls/php-coveralls.git",
"reference": "3eaf7eb689cdf6b86801a3843940d974dc657068"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/3eaf7eb689cdf6b86801a3843940d974dc657068",
"reference": "3eaf7eb689cdf6b86801a3843940d974dc657068",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^6.0",
"php": "^5.5 || ^7.0",
"psr/log": "^1.0",
"symfony/config": "^2.1 || ^3.0 || ^4.0",
"symfony/console": "^2.1 || ^3.0 || ^4.0",
"symfony/stopwatch": "^2.0 || ^3.0 || ^4.0",
"symfony/yaml": "^2.0 || ^3.0 || ^4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0"
},
"suggest": {
"symfony/http-kernel": "Allows Symfony integration"
},
"bin": [
"bin/php-coveralls"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"PhpCoveralls\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kitamura Satoshi",
"email": "with.no.parachute@gmail.com",
"homepage": "https://www.facebook.com/satooshi.jp",
"role": "Original creator"
},
{
"name": "Takashi Matsuo",
"email": "tmatsuo@google.com"
},
{
"name": "Google Inc"
},
{
"name": "Dariusz Ruminski",
"email": "dariusz.ruminski@gmail.com",
"homepage": "https://github.com/keradus"
},
{
"name": "Contributors",
"homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors"
}
],
"description": "PHP client library for Coveralls API",
"homepage": "https://github.com/php-coveralls/php-coveralls",
"keywords": [
"ci",
"coverage",
"github",
"test"
],
"abandoned": "php-coveralls/php-coveralls",
"time": "2017-12-08T14:28:16+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
"reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
"reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"time": "2017-03-04T06:30:41+00:00"
},
{
"name": "sebastian/comparator",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "34369daee48eafb2651bea869b4b15d75ccc35f9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9",
"reference": "34369daee48eafb2651bea869b4b15d75ccc35f9",
"shasum": ""
},
"require": {
"php": "^7.0",
"sebastian/diff": "^2.0 || ^3.0",
"sebastian/exporter": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^6.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"time": "2018-02-01T13:46:46+00:00"
},
{
"name": "sebastian/diff",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
"reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
"shasum": ""
},
"require": {
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff"
],
"time": "2017-08-03T08:09:46+00:00"
},
{
"name": "sebastian/environment",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
"reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
"shasum": ""
},
"require": {
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"time": "2017-07-01T08:51:00+00:00"
},
{
"name": "sebastian/exporter",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
"reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
"shasum": ""
},
"require": {
"php": "^7.0",
"sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-mbstring": "*",
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"time": "2017-04-03T13:19:02+00:00"
},
{
"name": "sebastian/global-state",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
"reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
"shasum": ""
},
"require": {
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"time": "2017-04-27T15:39:26+00:00"
},
{
"name": "sebastian/object-enumerator",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
"reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
"reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
"shasum": ""
},
"require": {
"php": "^7.0",
"sebastian/object-reflector": "^1.1.1",
"sebastian/recursion-context": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"time": "2017-08-03T12:35:26+00:00"
},
{
"name": "sebastian/object-reflector",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-reflector.git",
"reference": "773f97c67f28de00d397be301821b06708fca0be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
"reference": "773f97c67f28de00d397be301821b06708fca0be",
"shasum": ""
},
"require": {
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Allows reflection of object attributes, including inherited and non-public ones",
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"time": "2017-03-29T09:07:27+00:00"
},
{
"name": "sebastian/recursion-context",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
"reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
"shasum": ""
},
"require": {
"php": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2017-03-03T06:23:57+00:00"
},
{
"name": "sebastian/resource-operations",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
"reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
"reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
"shasum": ""
},
"require": {
"php": ">=5.6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"time": "2015-07-28T20:34:47+00:00"
},
{
"name": "sebastian/version",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
"reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2016-10-03T07:35:21+00:00"
},
{
"name": "squizlabs/php_codesniffer",
"version": "2.9.1",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "dcbed1074f8244661eecddfc2a675430d8d33f62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62",
"reference": "dcbed1074f8244661eecddfc2a675430d8d33f62",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=5.1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"bin": [
"scripts/phpcs",
"scripts/phpcbf"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"classmap": [
"CodeSniffer.php",
"CodeSniffer/CLI.php",
"CodeSniffer/Exception.php",
"CodeSniffer/File.php",
"CodeSniffer/Fixer.php",
"CodeSniffer/Report.php",
"CodeSniffer/Reporting.php",
"CodeSniffer/Sniff.php",
"CodeSniffer/Tokens.php",
"CodeSniffer/Reports/",
"CodeSniffer/Tokenizers/",
"CodeSniffer/DocGenerators/",
"CodeSniffer/Standards/AbstractPatternSniff.php",
"CodeSniffer/Standards/AbstractScopeSniff.php",
"CodeSniffer/Standards/AbstractVariableSniff.php",
"CodeSniffer/Standards/IncorrectPatternException.php",
"CodeSniffer/Standards/Generic/Sniffs/",
"CodeSniffer/Standards/MySource/Sniffs/",
"CodeSniffer/Standards/PEAR/Sniffs/",
"CodeSniffer/Standards/PSR1/Sniffs/",
"CodeSniffer/Standards/PSR2/Sniffs/",
"CodeSniffer/Standards/Squiz/Sniffs/",
"CodeSniffer/Standards/Zend/Sniffs/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Greg Sherwood",
"role": "lead"
}
],
"description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
"homepage": "http://www.squizlabs.com/php-codesniffer",
"keywords": [
"phpcs",
"standards"
],
"time": "2017-05-22T02:43:20+00:00"
},
{
"name": "symfony/config",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "7b08223b7f6abd859651c56bcabf900d1627d085"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/7b08223b7f6abd859651c56bcabf900d1627d085",
"reference": "7b08223b7f6abd859651c56bcabf900d1627d085",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/filesystem": "~2.8|~3.0|~4.0",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/dependency-injection": "<3.3",
"symfony/finder": "<3.3"
},
"require-dev": {
"symfony/dependency-injection": "~3.3|~4.0",
"symfony/event-dispatcher": "~3.3|~4.0",
"symfony/finder": "~3.3|~4.0",
"symfony/yaml": "~3.0|~4.0"
},
"suggest": {
"symfony/yaml": "To use the yaml reference dumper"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T11:19:56+00:00"
},
{
"name": "symfony/filesystem",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c",
"reference": "285ce5005cb01a0aeaa5b0cf590bd0cc40bb631c",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2018-08-10T07:29:05+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.9.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
"reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.9-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"time": "2018-08-06T14:22:27+00:00"
},
{
"name": "symfony/stopwatch",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "deda2765e8dab2fc38492e926ea690f2a681f59d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/deda2765e8dab2fc38492e926ea690f2a681f59d",
"reference": "deda2765e8dab2fc38492e926ea690f2a681f59d",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Stopwatch\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com",
"time": "2018-07-26T10:03:52+00:00"
},
{
"name": "symfony/yaml",
"version": "v3.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/c2f4812ead9f847cb69e90917ca7502e6892d6b8",
"reference": "c2f4812ead9f847cb69e90917ca7502e6892d6b8",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
},
"require-dev": {
"symfony/console": "~3.4|~4.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2018-08-10T07:34:36+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
"reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": "^7.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"time": "2017-04-07T12:08:54+00:00"
},
{
"name": "webmozart/assert",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "0df1908962e7a3071564e857d86874dad1ef204a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
"reference": "0df1908962e7a3071564e857d86874dad1ef204a",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
"sebastian/version": "^1.0.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
"assert",
"check",
"validate"
],
"time": "2018-01-29T19:49:41+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.4.0"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.0.8"
}
}
PK }?Vƻ
.editorconfignu W+A # 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
PK }?V LICENSEnu W+A The MIT License (MIT)
Copyright (c) 2016-2018 Consolidation Org Developers
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.
DEPENDENCY LICENSES:
Name Version License
consolidation/output-formatters 3.2.1 MIT
psr/log 1.0.2 MIT
symfony/console v3.4.15 MIT
symfony/debug v3.4.15 MIT
symfony/event-dispatcher v3.4.15 MIT
symfony/finder v3.4.15 MIT
symfony/polyfill-mbstring v1.9.0 MIT
PK }?V9ཚ infection.json.distnu W+A {
"timeout": 10,
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "infection-log.txt"
}
}PK }?VѩS S CONTRIBUTING.mdnu W+A # Contributing to Consolidation
Thank you for your interest in contributing to the Consolidation effort! Consolidation aims to provide reusable, loosely-coupled components useful for building command-line tools. Consolidation is built on top of Symfony Console, but aims to separate the tool from the implementation details of Symfony.
Here are some of the guidelines you should follow to make the most of your efforts:
## Code Style Guidelines
Consolidation adheres to the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/) for PHP code.
## Pull Request Guidelines
Every pull request is run through:
- phpcs -n --standard=PSR2 src
- phpunit
- [Scrutinizer](https://scrutinizer-ci.com/g/consolidation/annotated-command/)
It is easy to run the unit tests and code sniffer locally; just run:
- composer cs
To run the code beautifier, which will fix many of the problems reported by phpcs:
- composer cbf
These two commands (`composer cs` and `composer cbf`) are defined in the `scripts` section of [composer.json](composer.json).
After submitting a pull request, please examine the Scrutinizer report. It is not required to fix all Scrutinizer issues; you may ignore recommendations that you disagree with. The spacing patches produced by Scrutinizer do not conform to PSR2 standards, and therefore should never be applied. DocBlock patches may be applied at your discression. Things that Scrutinizer identifies as a bug nearly always need to be addressed.
Pull requests must pass phpcs and phpunit in order to be merged; ideally, new functionality will also include new unit tests.
PK }?V|)Y Y
composer.jsonnu W+A {
"name": "consolidation/annotated-command",
"description": "Initialize Symfony Console commands from annotated command class methods.",
"license": "MIT",
"authors": [
{
"name": "Greg Anderson",
"email": "greg.1.anderson@greenknowe.org"
}
],
"autoload":{
"psr-4":{
"Consolidation\\AnnotatedCommand\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Consolidation\\TestUtils\\": "tests/src"
}
},
"require": {
"php": ">=5.4.0",
"consolidation/output-formatters": "^3.1.12",
"psr/log": "^1",
"symfony/console": "^2.8|^3|^4",
"symfony/event-dispatcher": "^2.5|^3|^4",
"symfony/finder": "^2.5|^3|^4"
},
"require-dev": {
"phpunit/phpunit": "^6",
"satooshi/php-coveralls": "^2",
"g1a/composer-test-scenarios": "^2",
"squizlabs/php_codesniffer": "^2.7"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "7.0.8"
}
},
"scripts": {
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"unit": "SHELL_INTERACTIVE=true phpunit --colors=always",
"lint": [
"find src -name '*.php' -print0 | xargs -0 -n1 php -l",
"find tests/src -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
"@lint",
"@unit",
"@cs"
],
"scenario": "scenarios/install",
"post-update-cmd": [
"create-scenario symfony4 'symfony/console:^4.0' --platform-php '7.1.3'",
"create-scenario symfony2 'symfony/console:^2.8' 'phpunit/phpunit:^4.8.36' --remove 'satooshi/php-coveralls' --platform-php '5.4' --no-lockfile",
"create-scenario phpunit4 'phpunit/phpunit:^4.8.36' --remove 'satooshi/php-coveralls' --platform-php '5.4'",
"dependency-licenses"
]
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}
PK }?Vs۞ dependencies.ymlnu W+A version: 2
dependencies:
- type: php
path: /
settings:
composer_options: ""
manifest_updates:
filters:
- name: ".*"
versions: "L.Y.Y"
PK }?Vq$8 CHANGELOG.mdnu W+A # Change Log
### 2.9.0 & 2.9.1 - 19 Sept 2018
- Improve commandfile discovery for extensions installed via Composer. (#156)
### 2.8.5 - 18 Aug 2018
- Add dependencies.yml for dependencies.io
- Fix warning in AnnotatedCommandFactory when getCommandInfoListFromCache called with null.
### 2.8.4 - 25 May 2018
- Use g1a/composer-test-scenarios for better PHP version matrix testing.
### 2.8.3 - 23 Feb 2018
- BUGFIX: Do not shift off the command name unless it is there. (#139)
- Use test scenarios to test multiple versions of Symfony. (#136, #137)
### 2.8.2 - 29 Nov 2017
- Allow Symfony 4 components.
### 2.8.1 - 16 Oct 2017
- Add hook methods to allow Symfony command events to be added directly to the hook manager, givig better control of hook order. (#131)
### 2.8.0 - 13 Oct 2017
- Remove phpdocumentor/reflection-docblock in favor of using a bespoke parser (#130)
### 2.7.0 - 18 Sept 2017
- Add support for options with a default value of 'true' (#119)
- BUGFIX: Improve handling of options with optional values, which previously was not working correctly. (#118)
### 2.6.1 - 18 Sep 2017
- Reverts to contents of the 2.4.13 release.
### 2.5.0 & 2.5.1 - 17 Sep 2017
- BACKED OUT. These releases accidentally introduced breaking changes.
### 2.4.13 - 28 Aug 2017
- Add a followLinks() method (#108)
### 2.4.12 - 24 Aug 2017
- BUGFIX: Allow annotated commands to directly use InputInterface and OutputInterface (#106)
### 2.4.11 - 27 July 2017
- Back out #102: do not change behavior of word wrap based on STDOUT redirection.
### 2.4.10 - 21 July 2017
- Add a method CommandProcessor::setPassExceptions() to allow applicationsto prevent the command processor from catching exceptions thrown by command methods and hooks. (#103)
### 2.4.9 - 20 Jul 2017
- Automatically disable wordwrap when the terminal is not connected to STDOUT (#102)
### 2.4.8 - 3 Apr 2017
- Allow multiple annotations with the same key. These are returned as a csv, or, alternately, can be accessed as an array via the new accessor.
- Unprotect two methods for benefit of Drush help. (#99)
- BUGFIX: Remove symfony/console pin (#100)
### 2.4.7 & 2.4.6 - 17 Mar 2017
- Avoid wrapping help text (#93)
- Pin symfony/console to version < 3.2.5 (#94)
- Add getExampleUsages() to AnnotatedCommand. (#92)
### 2.4.5 - 28 Feb 2017
- Ensure that placeholder entries are written into the commandfile cache. (#86)
### 2.4.4 - 27 Feb 2017
- BUGFIX: Avoid rewriting the command cache unless something has changed.
- BUGFIX: Ensure that the default value of options are correctly cached.
### 2.4.2 - 24 Feb 2017
- Add SimpleCacheInterface as a documentation interface (not enforced).
### 2.4.1 - 20 Feb 2017
- Support array options: multiple options on the commandline may be passed in to options array as an array of values.
- Add php 7.1 to the test matrix.
### 2.4.0 - 3 Feb 2017
- Automatically rebuild cached commandfile data when commandfile changes.
- Provide path to command file in AnnotationData objects.
- Bugfix: Add dynamic options when user runs '--help my:command' (previously, only 'help my:command' worked).
- Bugfix: Include description of last parameter in help (was omitted if no options present)
- Add Windows testing with Appveyor
### 2.3.0 - 19 Jan 2017
- Add a command info cache to improve performance of applications with many commands
- Bugfix: Allow trailing backslashes in namespaces in CommandFileDiscovery
- Bugfix: Rename @topic to @topics
### 2.2.0 - 23 November 2016
- Support custom events
- Add xml and json output for replacement help command. Text / html format for replacement help command not available yet.
### 2.1.0 - 14 November 2016
- Add support for output formatter wordwrapping
- Fix version requirement for output-formatters in composer.json
- Use output-formatters ~3
- Move php_codesniffer back to require-dev (moved to require by mistake)
### 2.0.0 - 30 September 2016
- **Breaking** Hooks with no command name now apply to all commands defined in the same class. This is a change of behavior from the 1.x branch, where hooks with no command name applied to a command with the same method name in a *different* class.
- **Breaking** The interfaces ValidatorInterface, ProcessResultInterface and AlterResultInterface have been updated to be passed a CommandData object, which contains an Input and Output object, plus the AnnotationData.
- **Breaking** The Symfony Command Event hook has been renamed to COMMAND_EVENT. There is a new COMMAND hook that behaves like the existing Drush command hook (i.e. the post-command event is called after the primary command method runs).
- Add an accessor function AnnotatedCommandFactory::setIncludeAllPublicMethods() to control whether all public methods of a command class, or only those with a @command annotation will be treated as commands. Default remains to treat all public methods as commands. The parameters to AnnotatedCommandFactory::createCommandsFromClass() and AnnotatedCommandFactory::createCommandsFromClassInfo() still behave the same way, but are deprecated. If omitted, the value set by the accessor will be used.
- @option and @usage annotations provided with @hook methods will be added to the help text of the command they hook. This should be done if a hook needs to add a new option, e.g. to control the behavior of the hook.
- @option annotations can now be either `@option type $name description`, or just `@option name description`.
- `@hook option` can be used to programatically add options to a command.
- A CommandInfoAltererInterface can be added via AnnotatedCommandFactory::addCommandInfoAlterer(); it will be given the opportunity to adjust every CommandInfo object parsed from a command file prior to the creation of commands.
- AnnotatedCommandFactory::setIncludeAllPublicMethods(false) may be used to require methods to be annotated with @commnad in order to be considered commands. This is in preference to the existing parameters of various command-creation methods of AnnotatedCommandFactory, which are now all deprecated in favor of this setter function.
- If a --field option is given, it will also force the output format to 'string'.
- Setter methods more consistently return $this.
- Removed PassThroughArgsInput. This class was unnecessary.
### 1.4.0 - 13 September 2016
- Add basic annotation hook capability, to allow hook functions to be attached to commands with arbitrary annotations.
### 1.3.0 - 8 September 2016
- Add ComandFileDiscovery::setSearchDepth(). The search depth applies to each search location, unless there are no search locations, in which case it applies to the base directory.
### 1.2.0 - 2 August 2016
- Support both the 2.x and 3.x versions of phpdocumentor/reflection-docblock.
- Support php 5.4.
- **Bug** Do not allow an @param docblock comment for the options to override the meaning of the options.
### 1.1.0 - 6 July 2016
- Introduce AnnotatedCommandFactory::createSelectedCommandsFromClassInfo() method.
### 1.0.0 - 20 May 2016
- First stable release.
PK }?VCN~i i README.mdnu W+A # Consolidation\AnnotatedCommand
Initialize Symfony Console commands from annotated command class methods.
[![Travis CI](https://travis-ci.org/consolidation/annotated-command.svg?branch=master)](https://travis-ci.org/consolidation/annotated-command)
[![Windows CI](https://ci.appveyor.com/api/projects/status/c2c4lcf43ux4c30p?svg=true)](https://ci.appveyor.com/project/greg-1-anderson/annotated-command)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/consolidation/annotated-command/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/consolidation/annotated-command/?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/consolidation/annotated-command/badge.svg?branch=master)](https://coveralls.io/github/consolidation/annotated-command?branch=master)
[![License](https://poser.pugx.org/consolidation/annotated-command/license)](https://packagist.org/packages/consolidation/annotated-command)
## Component Status
Currently in use in [Robo](https://github.com/consolidation/Robo) (1.x+), [Drush](https://github.com/drush-ops/drush) (9.x+) and [Terminus](https://github.com/pantheon-systems/terminus) (1.x+).
## Motivation
Symfony Console provides a set of classes that are widely used to implement command line tools. Increasingly, it is becoming popular to use annotations to describe the characteristics of the command (e.g. its arguments, options and so on) implemented by the annotated method.
Extant commandline tools that utilize this technique include:
- [Robo](https://github.com/consolidation/Robo)
- [wp-cli](https://github.com/wp-cli/wp-cli)
- [Pantheon Terminus](https://github.com/pantheon-systems/terminus)
This library provides routines to produce the Symfony\Component\Console\Command\Command from all public methods defined in the provided class.
**Note** If you are looking for a very fast way to write a Symfony Console-base command-line tool, you should consider using [Robo](https://github.com/consolidation/Robo), which is built on top of this library, and adds additional conveniences to get you going quickly. See [Using Robo as a Framework](http://robo.li/framework/). It is possible to use this project without Robo if desired, of course.
## Library Usage
This is a library intended to be used in some other project. Require from your composer.json file:
```
"require": {
"consolidation/annotated-command": "~2"
},
```
## Example Annotated Command Class
The public methods of the command class define its commands, and the parameters of each method define its arguments and options. The command options, if any, are declared as the last parameter of the methods. The options will be passed in as an associative array; the default options of the last parameter should list the options recognized by the command.
The rest of the parameters are arguments. Parameters with a default value are optional; those without a default value are required.
```php
class MyCommandClass
{
/**
* This is the my:cat command
*
* This command will concatenate two parameters. If the --flip flag
* is provided, then the result is the concatenation of two and one.
*
* @command my:cat
* @param integer $one The first parameter.
* @param integer $two The other parameter.
* @option arr An option that takes multiple values.
* @option flip Whether or not the second parameter should come first in the result.
* @aliases c
* @usage bet alpha --flip
* Concatenate "alpha" and "bet".
*/
public function myCat($one, $two, $options = ['flip' => false])
{
if ($options['flip']) {
return "{$two}{$one}";
}
return "{$one}{$two}";
}
}
```
## Option Default Values
The `$options` array must be an associative array whose key is the name of the option, and whose value is one of:
- The boolean value `false`, which indicates that the option takes no value.
- A **string** containing the default value for options that may be provided a value, but are not required to.
- The special value InputOption::VALUE_REQUIRED, which indicates that the user must provide a value for the option whenever it is used.
- The special value InputOption::VALUE_OPTIONAL, which produces the following behavior:
- If the option is given a value (e.g. `--foo=bar`), then the value will be a string.
- If the option exists on the commandline, but has no value (e.g. `--foo`), then the value will be `true`.
- If the option does not exist on the commandline at all, then the value will be `null`.
- If the user explicitly sets `--foo=0`, then the value will be converted to `false`.
- LIMITATION: If any Input object other than ArgvInput (or a subclass thereof) is used, then the value will be `null` for both the no-value case (`--foo`) and the no-option case. When using a StringInput, use `--foo=1` instead of `--foo` to avoid this problem.
- The special value `true` produces the following behavior:
- If the option is given a value (e.g. `--foo=bar`), then the value will be a string.
- If the option exists on the commandline, but has no value (e.g. `--foo`), then the value will be `true`.
- If the option does not exist on the commandline at all, then the value will also be `true`.
- If the user explicitly sets `--foo=0`, then the value will be converted to `false`.
- If the user adds `--no-foo` on the commandline, then the value of `foo` will be `false`.
- An empty array, which indicates that the option may appear multiple times on the command line.
No other values should be used for the default value. For example, `$options = ['a' => 1]` is **incorrect**; instead, use `$options = ['a' => '1']`.
Default values for options may also be provided via the `@default` annotation. See hook alter, below.
## Hooks
Commandfiles may provide hooks in addition to commands. A commandfile method that contains a @hook annotation is registered as a hook instead of a command. The format of the hook annotation is:
```
@hook type target
```
The hook **type** determines when during the command lifecycle this hook will be called. The available hook types are described in detail below.
The hook **target** specifies which command or commands the hook will be attached to. There are several different ways to specify the hook target.
- The command's primary name (e.g. `my:command`) or the command's method name (e.g. myCommand) will attach the hook to only that command.
- An annotation (e.g. `@foo`) will attach the hook to any command that is annotated with the given label.
- If the target is omitted, then the hook will be attached to every command defined in the same class as the hook implementation.
There are ten types of hooks in the command processing request flow:
- [Command Event](#command-event-hook) (Symfony)
- @pre-command-event
- @command-event
- @post-command-event
- [Option](#option-event-hook)
- @pre-option
- @option
- @post-option
- [Initialize](#initialize-hook) (Symfony)
- @pre-init
- @init
- @post-init
- [Interact](#interact-hook) (Symfony)
- @pre-interact
- @interact
- @post-interact
- [Validate](#validate-hook)
- @pre-validate
- @validate
- @post-validate
- [Command](#command-hook)
- @pre-command
- @command
- @command-init
- [Process](#process-hook)
- @pre-process
- @process
- @post-process
- [Alter](#alter-hook)
- @pre-alter
- @alter
- @post-alter
- [Status](#status-hook)
- @status
- [Extract](#extract-hook)
- @extract
In addition to these, there are two more hooks available:
- [On-event](#on-event-hook)
- @on-event
- [Replace Command](#replace-command-hook)
- @replace-command
The "pre" and "post" varieties of these hooks, where avalable, give more flexibility vis-a-vis hook ordering (and for consistency). Within one type of hook, the running order is undefined and not guaranteed. Note that many validate, process and alter hooks may run, but the first status or extract hook that successfully returns a result will halt processing of further hooks of the same type.
Each hook has an interface that defines its calling conventions; however, any callable may be used when registering a hook, which is convenient if versions of PHP prior to 7.0 (with no anonymous classes) need to be supported.
### Command Event Hook
The command-event hook is called via the Symfony Console command event notification callback mechanism. This happens prior to event dispatching and command / option validation. Note that Symfony does not allow the $input object to be altered in this hook; any change made here will be reset, as Symfony re-parses the object. Changes to arguments and options should be done in the initialize hook (non-interactive alterations) or the interact hook (which is naturally for interactive alterations).
### Option Event Hook
The option event hook ([OptionHookInterface](src/Hooks/OptionHookInterface.php)) is called for a specific command, whenever it is executed, or its help command is called. Any additional options for the command may be added here by calling the `addOption` method of the provided `$command` object. Note that the option hook is only necessary for calculating dynamic options. Static options may be added via the @option annotation on any hook that uses them. See the [Alter Hook](https://github.com/consolidation/annotated-command#alter-hook) documentation below for an example.
```
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Command\Command;
/**
* @hook option some:command
*/
public function additionalOption(Command $command, AnnotationData $annotationData)
{
$command->addOption(
'dynamic',
'',
InputOption::VALUE_NONE,
'Option added by @hook option some:command'
);
}
```
### Initialize Hook
The initialize hook ([InitializeHookInterface](src/Hooks/InitializeHookInterface.php)) runs prior to the interact hook. It may supply command arguments and options from a configuration file or other sources. It should never do any user interaction.
The [consolidation/config](https://github.com/consolidation/config) project (which is used in [Robo PHP](https://github.com/consolidation/robo)) uses `@hook init` to automatically inject values from `config.yml` configuration files for options that were not provided on the command line.
```
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputInterface;
/**
* @hook init some:command
*/
public function initSomeCommand(InputInterface $input, AnnotationData $annotationData)
{
$value = $input->getOption('some-option');
if (!$value) {
$input->setOption('some-option', $this->generateRandomOptionValue());
}
}
```
### Interact Hook
The interact hook ([InteractorInterface](src/Hooks/InteractorInterface.php)) runs prior to argument and option validation. Required arguments and options not supplied on the command line may be provided during this phase by prompting the user. Note that the interact hook is not called if the --no-interaction flag is supplied, whereas the command-event hook and the init hook are.
```
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* @hook interact some:command
*/
public function interact(InputInterface $input, OutputInterface $output, AnnotationData $annotationData)
{
$io = new SymfonyStyle($input, $output);
// If the user did not specify a password, then prompt for one.
$password = $input->getOption('password');
if (empty($password)) {
$password = $io->askHidden("Enter a password:", function ($value) { return $value; });
$input->setOption('password', $password);
}
}
```
### Validate Hook
The purpose of the validate hook ([ValidatorInterface](src/Hooks/ValidatorInterface.php)) is to ensure the state of the targets of the current command are usabe in the context required by that command. Symfony has already validated the arguments and options prior to this hook. It is possible to alter the values of the arguments and options if necessary, although this is better done in the configure hook. A validation hook may take one of several actions:
- Do nothing. This indicates that validation succeeded.
- Return a CommandError. Validation fails, and execution stops. The CommandError contains a status result code and a message, which is printed.
- Throw an exception. The exception is converted into a CommandError.
- Return false. Message is empty, and status is 1. Deprecated.
The validate hook may change the arguments and options of the command by modifying the Input object in the provided CommandData parameter. Any number of validation hooks may run, but if any fails, then execution of the command stops.
```
use Consolidation\AnnotatedCommand\CommandData;
/**
* @hook validate some:command
*/
public function validatePassword(CommandData $commandData)
{
$input = $commandData->input();
$password = $input->getOption('password');
if (strpbrk($password, '!;$`') === false) {
throw new \Exception("Your password MUST contain at least one of the characters ! ; ` or $, for no rational reason whatsoever.");
}
}
```
### Command Hook
The command hook is provided for semantic purposes. The pre-command and command hooks are equivalent to the post-validate hook, and should confirm to the interface ([ValidatorInterface](src/Hooks/ValidatorInterface.php)). All of the post-validate hooks will be called before the first pre-command hook is called. Similarly, the post-command hook is equivalent to the pre-process hook, and should implement the interface ([ProcessResultInterface](src/Hooks/ProcessResultInterface.php)).
The command callback itself (the method annotated @command) is called after the last command hook, and prior to the first post-command hook.
```
use Consolidation\AnnotatedCommand\CommandData;
/**
* @hook pre-command some:command
*/
public function preCommand(CommandData $commandData)
{
// Do something before some:command
}
/**
* @hook post-command some:command
*/
public function postCommand($result, CommandData $commandData)
{
// Do something after some:command
}
```
### Process Hook
The process hook ([ProcessResultInterface](src/Hooks/ProcessResultInterface.php)) is specifically designed to convert a series of processing instructions into a final result. An example of this is implemented in Robo in the [CollectionProcessHook](https://github.com/consolidation/Robo/blob/master/src/Collection/CollectionProcessHook.php) class; if a Robo command returns a TaskInterface, then a Robo process hook will execute the task and return the result. This allows a pre-process hook to alter the task, e.g. by adding more operations to a task collection.
The process hook should not be used for other purposes.
```
use Consolidation\AnnotatedCommand\CommandData;
/**
* @hook process some:command
*/
public function process($result, CommandData $commandData)
{
if ($result instanceof MyInterimType) {
$result = $this->convertInterimResult($result);
}
}
```
### Alter Hook
An alter hook ([AlterResultInterface](src/Hooks/AlterResultInterface.php)) changes the result object. Alter hooks should only operate on result objects of a type they explicitly recognize. They may return an object of the same type, or they may convert the object to some other type.
If something goes wrong, and the alter hooks wishes to force the command to fail, then it may either return a CommandError object, or throw an exception.
```
use Consolidation\AnnotatedCommand\CommandData;
/**
* Demonstrate an alter hook with an option
*
* @hook alter some:command
* @option $alteration Alter the result of the command in some way.
* @usage some:command --alteration
*/
public function alterSomeCommand($result, CommandData $commandData)
{
if ($commandData->input()->getOption('alteration')) {
$result[] = $this->getOneMoreRow();
}
return $result;
}
```
If an option needs to be provided with a default value, that may be done via the `@default` annotation.
```
use Consolidation\AnnotatedCommand\CommandData;
/**
* Demonstrate an alter hook with an option that has a default value
*
* @hook alter some:command
* @option $name Give the result a name.
* @default $name George
* @usage some:command --name=George
*/
public function nameSomeCommand($result, CommandData $commandData)
{
$result['name'] = $commandData->input()->getOption('name')
return $result;
}
```
### Status Hook
The status hook ([StatusDeterminerInterface](src/Hooks/StatusDeterminerInterface.php)) is responsible for determing whether a command succeeded (status code 0) or failed (status code > 0). The result object returned by a command may be a compound object that contains multiple bits of information about the command result. If the result object implements [ExitCodeInterface](ExitCodeInterface.php), then the `getExitCode()` method of the result object is called to determine what the status result code for the command should be. If ExitCodeInterface is not implemented, then all of the status hooks attached to this command are executed; the first one that successfully returns a result will stop further execution of status hooks, and the result it returned will be used as the status result code for this operation.
If no status hook returns any result, then success is presumed.
### Extract Hook
The extract hook ([ExtractOutputInterface](src/Hooks/ExtractOutputInterface.php)) is responsible for determining what the actual rendered output for the command should be. The result object returned by a command may be a compound object that contains multiple bits of information about the command result. If the result object implements [OutputDataInterface](OutputDataInterface.php), then the `getOutputData()` method of the result object is called to determine what information should be displayed to the user as a result of the command's execution. If OutputDataInterface is not implemented, then all of the extract hooks attached to this command are executed; the first one that successfully returns output data will stop further execution of extract hooks.
If no extract hook returns any data, then the result object itself is printed if it is a string; otherwise, no output is emitted (other than any produced by the command itself).
### On-Event hook
Commands can define their own custom events; to do so, they need only implement the CustomEventAwareInterface, and use the CustomEventAwareTrait. Event handlers for each custom event can then be defined using the on-event hook.
A handler using an on-event hook looks something like the following:
```
/**
* @hook on-event custom-event
*/
public function handlerForCustomEvent(/* arbitrary parameters, as defined by custom-event */)
{
// do the needful, return what custom-event expects
}
```
Then, to utilize this in a command:
```
class MyCommands implements CustomEventAwareInterface
{
use CustomEventAwareTrait;
/**
* @command my-command
*/
public myCommand($options = [])
{
$handlers = $this->getCustomEventHandlers('custom-event');
// iterate and call $handlers
}
}
```
It is up to the command that defines the custom event to declare what the expected parameters for the callback function should be, and what the return value is and how it should be used.
### Replace Command Hook
The replace-command ([ReplaceCommandHookInterface](src/Hooks/ReplaceCommandHookInterface.php)) hook permits you to replace a command's method with another method of your own.
For instance, if you'd like to replace the `foo:bar` command, you could utilize the following code:
```php
setIncludeAllPublicMethods(true);
$commandFactory->commandProcessor()->setFormatterManager(new FormatterManager());
$commandList = $commandFactory->createCommandsFromClass($myCommandClassInstance);
foreach ($commandList as $command) {
$application->add($command);
}
```
You may have more than one command class, if you wish. If so, simply call AnnotatedCommandFactory::createCommandsFromClass() multiple times.
If you do not wish every public method in your classes to be added as commands, use `AnnotatedCommandFactory::setIncludeAllPublicMethods(false)`, and only methods annotated with @command will become commands.
Note that the `setFormatterManager()` operation is optional; omit this if not using [Consolidation/OutputFormatters](https://github.com/consolidation/output-formatters).
A CommandInfoAltererInterface can be added via AnnotatedCommandFactory::addCommandInfoAlterer(); it will be given the opportunity to adjust every CommandInfo object parsed from a command file prior to the creation of commands.
### Command File Discovery
A discovery class, CommandFileDiscovery, is also provided to help find command files on the filesystem. Usage is as follows:
```php
$discovery = new CommandFileDiscovery();
$myCommandFiles = $discovery->discover($path, '\Drupal');
foreach ($myCommandFiles as $myCommandClass) {
$myCommandClassInstance = new $myCommandClass();
// ... as above
}
```
For a discussion on command file naming conventions and search locations, see https://github.com/consolidation/annotated-command/issues/12.
If different namespaces are used at different command file paths, change the call to discover as follows:
```php
$myCommandFiles = $discovery->discover(['\Ns1' => $path1, '\Ns2' => $path2]);
```
As a shortcut for the above, the method `discoverNamespaced()` will take the last directory name of each path, and append it to the base namespace provided. This matches the conventions used by Drupal modules, for example.
### Configuring Output Formatts (e.g. to enable wordwrap)
The Output Formatters project supports automatic formatting of tabular output. In order for wordwrapping to work correctly, the terminal width must be passed in to the Output Formatters handlers via `FormatterOptions::setWidth()`.
In the Annotated Commands project, this is done via dependency injection. If a `PrepareFormatter` object is passed to `CommandProcessor::addPrepareFormatter()`, then it will be given an opportunity to set properties on the `FormatterOptions` when it is created.
A `PrepareTerminalWidthOption` class is provided to use the Symfony Application class to fetch the terminal width, and provide it to the FormatterOptions. It is injected as follows:
```php
$terminalWidthOption = new PrepareTerminalWidthOption();
$terminalWidthOption->setApplication($application);
$commandFactory->commandProcessor()->addPrepareFormatter($terminalWidthOption);
```
To provide greater control over the width used, create your own `PrepareTerminalWidthOption` subclass, and adjust the width as needed.
## Other Callbacks
In addition to the hooks provided by the hook manager, there are additional callbacks available to alter the way the annotated command library operates.
### Factory Listeners
Factory listeners are notified every time a command file instance is used to create annotated commands.
```
public function AnnotatedCommandFactory::addListener(CommandCreationListenerInterface $listener);
```
Listeners can be used to construct command file instances as they are provided to the command factory.
### Option Providers
An option provider is given an opportunity to add options to a command as it is being constructed.
```
public function AnnotatedCommandFactory::addAutomaticOptionProvider(AutomaticOptionsProviderInterface $listener);
```
The complete CommandInfo record with all of the annotation data is available, so you can, for example, add an option `--foo` to every command whose method is annotated `@fooable`.
### CommandInfo Alterers
CommandInfo alterers can adjust information about a command immediately before it is created. Typically, these will be used to supply default values for annotations custom to the command, or take other actions based on the interfaces implemented by the commandfile instance.
```
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance);
```
## Comparison to Existing Solutions
The existing solutions used their own hand-rolled regex-based parsers to process the contents of the DocBlock comments. consolidation/annotated-command uses the [phpdocumentor/reflection-docblock](https://github.com/phpDocumentor/ReflectionDocBlock) project (which is itself a regex-based parser) to interpret DocBlock contents.
PK }?V6s'\ \ $ src/Events/CustomEventAwareTrait.phpnu W+A hookManager = $hookManager;
}
/**
* {@inheritdoc}
*/
public function getCustomEventHandlers($eventName)
{
if (!$this->hookManager) {
return [];
}
return $this->hookManager->getHook($eventName, HookManager::ON_EVENT);
}
}
PK }?V%Ct ( src/Events/CustomEventAwareInterface.phpnu W+A hookManager = $hookManager;
}
/**
* Return the hook manager
* @return HookManager
*/
public function hookManager()
{
return $this->hookManager;
}
public function addPrepareFormatter(PrepareFormatter $preparer)
{
$this->prepareOptionsList[] = $preparer;
}
public function setFormatterManager(FormatterManager $formatterManager)
{
$this->formatterManager = $formatterManager;
return $this;
}
public function setDisplayErrorFunction(callable $fn)
{
$this->displayErrorFunction = $fn;
return $this;
}
/**
* Set a mode to make the annotated command library re-throw
* any exception that it catches while processing a command.
*
* The default behavior in the current (2.x) branch is to catch
* the exception and replace it with a CommandError object that
* may be processed by the normal output processing passthrough.
*
* In the 3.x branch, exceptions will never be caught; they will
* be passed through, as if setPassExceptions(true) were called.
* This is the recommended behavior.
*/
public function setPassExceptions($passExceptions)
{
$this->passExceptions = $passExceptions;
return $this;
}
public function commandErrorForException(\Exception $e)
{
if ($this->passExceptions) {
throw $e;
}
return new CommandError($e->getMessage(), $e->getCode());
}
/**
* Return the formatter manager
* @return FormatterManager
*/
public function formatterManager()
{
return $this->formatterManager;
}
public function initializeHook(
InputInterface $input,
$names,
AnnotationData $annotationData
) {
$initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
return $initializeDispatcher->initialize($input, $annotationData);
}
public function optionsHook(
AnnotatedCommand $command,
$names,
AnnotationData $annotationData
) {
$optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
$optionsDispatcher->getOptions($command, $annotationData);
}
public function interact(
InputInterface $input,
OutputInterface $output,
$names,
AnnotationData $annotationData
) {
$interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
return $interactDispatcher->interact($input, $output, $annotationData);
}
public function process(
OutputInterface $output,
$names,
$commandCallback,
CommandData $commandData
) {
$result = [];
try {
$result = $this->validateRunAndAlter(
$names,
$commandCallback,
$commandData
);
return $this->handleResults($output, $names, $result, $commandData);
} catch (\Exception $e) {
$result = $this->commandErrorForException($e);
return $this->handleResults($output, $names, $result, $commandData);
}
}
public function validateRunAndAlter(
$names,
$commandCallback,
CommandData $commandData
) {
// Validators return any object to signal a validation error;
// if the return an array, it replaces the arguments.
$validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
$validated = $validateDispatcher->validate($commandData);
if (is_object($validated)) {
return $validated;
}
$replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
if ($this->logger) {
$replaceDispatcher->setLogger($this->logger);
}
if ($replaceDispatcher->hasReplaceCommandHook()) {
$commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
}
// Run the command, alter the results, and then handle output and status
$result = $this->runCommandCallback($commandCallback, $commandData);
return $this->processResults($names, $result, $commandData);
}
public function processResults($names, $result, CommandData $commandData)
{
$processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
return $processDispatcher->process($result, $commandData);
}
/**
* Handle the result output and status code calculation.
*/
public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
{
$statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
$status = $statusCodeDispatcher->determineStatusCode($result);
// If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
if (is_integer($result) && !isset($status)) {
return $result;
}
$status = $this->interpretStatusCode($status);
// Get the structured output, the output stream and the formatter
$extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
$structuredOutput = $extractDispatcher->extractOutput($result);
$output = $this->chooseOutputStream($output, $status);
if ($status != 0) {
return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
}
if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
return $this->writeUsingFormatter($output, $structuredOutput, $commandData);
}
return $this->writeCommandOutput($output, $structuredOutput);
}
protected function dataCanBeFormatted($structuredOutput)
{
if (!isset($this->formatterManager)) {
return false;
}
return
is_object($structuredOutput) ||
is_array($structuredOutput);
}
/**
* Run the main command callback
*/
protected function runCommandCallback($commandCallback, CommandData $commandData)
{
$result = false;
try {
$args = $commandData->getArgsAndOptions();
$result = call_user_func_array($commandCallback, $args);
} catch (\Exception $e) {
$result = $this->commandErrorForException($e);
}
return $result;
}
/**
* Determine the formatter that should be used to render
* output.
*
* If the user specified a format via the --format option,
* then always return that. Otherwise, return the default
* format, unless --pipe was specified, in which case
* return the default pipe format, format-pipe.
*
* n.b. --pipe is a handy option introduced in Drush 2
* (or perhaps even Drush 1) that indicates that the command
* should select the output format that is most appropriate
* for use in scripts (e.g. to pipe to another command).
*
* @return string
*/
protected function getFormat(FormatterOptions $options)
{
// In Symfony Console, there is no way for us to differentiate
// between the user specifying '--format=table', and the user
// not specifying --format when the default value is 'table'.
// Therefore, we must make --field always override --format; it
// cannot become the default value for --format.
if ($options->get('field')) {
return 'string';
}
$defaults = [];
if ($options->get('pipe')) {
return $options->get('pipe-format', [], 'tsv');
}
return $options->getFormat($defaults);
}
/**
* Determine whether we should use stdout or stderr.
*/
protected function chooseOutputStream(OutputInterface $output, $status)
{
// If the status code indicates an error, then print the
// result to stderr rather than stdout
if ($status && ($output instanceof ConsoleOutputInterface)) {
return $output->getErrorOutput();
}
return $output;
}
/**
* Call the formatter to output the provided data.
*/
protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData)
{
$formatterOptions = $this->createFormatterOptions($commandData);
$format = $this->getFormat($formatterOptions);
$this->formatterManager->write(
$output,
$format,
$structuredOutput,
$formatterOptions
);
return 0;
}
/**
* Create a FormatterOptions object for use in writing the formatted output.
* @param CommandData $commandData
* @return FormatterOptions
*/
protected function createFormatterOptions($commandData)
{
$options = $commandData->input()->getOptions();
$formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
foreach ($this->prepareOptionsList as $preparer) {
$preparer->prepare($commandData, $formatterOptions);
}
return $formatterOptions;
}
/**
* Description
* @param OutputInterface $output
* @param int $status
* @param string $structuredOutput
* @param mixed $originalResult
* @return type
*/
protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
{
if (isset($this->displayErrorFunction)) {
call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
} else {
$this->writeCommandOutput($output, $structuredOutput);
}
return $status;
}
/**
* If the result object is a string, then print it.
*/
protected function writeCommandOutput(
OutputInterface $output,
$structuredOutput
) {
// If there is no formatter, we will print strings,
// but can do no more than that.
if (is_string($structuredOutput)) {
$output->writeln($structuredOutput);
}
return 0;
}
/**
* If a status code was set, then return it; otherwise,
* presume success.
*/
protected function interpretStatusCode($status)
{
if (isset($status)) {
return $status;
}
return 0;
}
}
PK }?V08?A A src/AnnotatedCommandFactory.phpnu W+A dataStore = new NullCache();
$this->commandProcessor = new CommandProcessor(new HookManager());
$this->addAutomaticOptionProvider($this);
}
public function setCommandProcessor(CommandProcessor $commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
/**
* @return CommandProcessor
*/
public function commandProcessor()
{
return $this->commandProcessor;
}
/**
* Set the 'include all public methods flag'. If true (the default), then
* every public method of each commandFile will be used to create commands.
* If it is false, then only those public methods annotated with @command
* or @name (deprecated) will be used to create commands.
*/
public function setIncludeAllPublicMethods($includeAllPublicMethods)
{
$this->includeAllPublicMethods = $includeAllPublicMethods;
return $this;
}
public function getIncludeAllPublicMethods()
{
return $this->includeAllPublicMethods;
}
/**
* @return HookManager
*/
public function hookManager()
{
return $this->commandProcessor()->hookManager();
}
/**
* Add a listener that is notified immediately before the command
* factory creates commands from a commandFile instance. This
* listener can use this opportunity to do more setup for the commandFile,
* and so on.
*
* @param CommandCreationListenerInterface $listener
*/
public function addListener(CommandCreationListenerInterface $listener)
{
$this->listeners[] = $listener;
return $this;
}
/**
* Add a listener that's just a simple 'callable'.
* @param callable $listener
*/
public function addListernerCallback(callable $listener)
{
$this->addListener(new CommandCreationListener($listener));
return $this;
}
/**
* Call all command creation listeners
*
* @param object $commandFileInstance
*/
protected function notify($commandFileInstance)
{
foreach ($this->listeners as $listener) {
$listener->notifyCommandFileAdded($commandFileInstance);
}
}
public function addAutomaticOptionProvider(AutomaticOptionsProviderInterface $optionsProvider)
{
$this->automaticOptionsProviderList[] = $optionsProvider;
}
public function addCommandInfoAlterer(CommandInfoAltererInterface $alterer)
{
$this->commandInfoAlterers[] = $alterer;
}
/**
* n.b. This registers all hooks from the commandfile instance as a side-effect.
*/
public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
$this->notify($commandFileInstance);
$commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
$this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods);
}
public function getCommandInfoListFromClass($commandFileInstance)
{
$cachedCommandInfoList = $this->getCommandInfoListFromCache($commandFileInstance);
$commandInfoList = $this->createCommandInfoListFromClass($commandFileInstance, $cachedCommandInfoList);
if (!empty($commandInfoList)) {
$cachedCommandInfoList = array_merge($commandInfoList, $cachedCommandInfoList);
$this->storeCommandInfoListInCache($commandFileInstance, $cachedCommandInfoList);
}
return $cachedCommandInfoList;
}
protected function storeCommandInfoListInCache($commandFileInstance, $commandInfoList)
{
if (!$this->hasDataStore()) {
return;
}
$cache_data = [];
$serializer = new CommandInfoSerializer();
foreach ($commandInfoList as $i => $commandInfo) {
$cache_data[$i] = $serializer->serialize($commandInfo);
}
$className = get_class($commandFileInstance);
$this->getDataStore()->set($className, $cache_data);
}
/**
* Get the command info list from the cache
*
* @param mixed $commandFileInstance
* @return array
*/
protected function getCommandInfoListFromCache($commandFileInstance)
{
$commandInfoList = [];
if (!is_object($commandFileInstance)) {
return [];
}
$className = get_class($commandFileInstance);
if (!$this->getDataStore()->has($className)) {
return [];
}
$deserializer = new CommandInfoDeserializer();
$cache_data = $this->getDataStore()->get($className);
foreach ($cache_data as $i => $data) {
if (CommandInfoDeserializer::isValidSerializedData((array)$data)) {
$commandInfoList[$i] = $deserializer->deserialize((array)$data);
}
}
return $commandInfoList;
}
/**
* Check to see if this factory has a cache datastore.
* @return boolean
*/
public function hasDataStore()
{
return !($this->dataStore instanceof NullCache);
}
/**
* Set a cache datastore for this factory. Any object with 'set' and
* 'get' methods is acceptable. The key is the classname being cached,
* and the value is a nested associative array of strings.
*
* TODO: Typehint this to SimpleCacheInterface
*
* This is not done currently to allow clients to use a generic cache
* store that does not itself depend on the annotated-command library.
*
* @param Mixed $dataStore
* @return type
*/
public function setDataStore($dataStore)
{
if (!($dataStore instanceof SimpleCacheInterface)) {
$dataStore = new CacheWrapper($dataStore);
}
$this->dataStore = $dataStore;
return $this;
}
/**
* Get the data store attached to this factory.
*/
public function getDataStore()
{
return $this->dataStore;
}
protected function createCommandInfoListFromClass($classNameOrInstance, $cachedCommandInfoList)
{
$commandInfoList = [];
// Ignore special functions, such as __construct and __call, which
// can never be commands.
$commandMethodNames = array_filter(
get_class_methods($classNameOrInstance) ?: [],
function ($m) use ($classNameOrInstance) {
$reflectionMethod = new \ReflectionMethod($classNameOrInstance, $m);
return !$reflectionMethod->isStatic() && !preg_match('#^_#', $m);
}
);
foreach ($commandMethodNames as $commandMethodName) {
if (!array_key_exists($commandMethodName, $cachedCommandInfoList)) {
$commandInfo = CommandInfo::create($classNameOrInstance, $commandMethodName);
if (!static::isCommandOrHookMethod($commandInfo, $this->getIncludeAllPublicMethods())) {
$commandInfo->invalidate();
}
$commandInfoList[$commandMethodName] = $commandInfo;
}
}
return $commandInfoList;
}
public function createCommandInfo($classNameOrInstance, $commandMethodName)
{
return CommandInfo::create($classNameOrInstance, $commandMethodName);
}
public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
return $this->createSelectedCommandsFromClassInfo(
$commandInfoList,
$commandFileInstance,
function ($commandInfo) use ($includeAllPublicMethods) {
return static::isCommandMethod($commandInfo, $includeAllPublicMethods);
}
);
}
public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
{
$commandInfoList = $this->filterCommandInfoList($commandInfoList, $commandSelector);
return array_map(
function ($commandInfo) use ($commandFileInstance) {
return $this->createCommand($commandInfo, $commandFileInstance);
},
$commandInfoList
);
}
protected function filterCommandInfoList($commandInfoList, callable $commandSelector)
{
return array_filter($commandInfoList, $commandSelector);
}
public static function isCommandOrHookMethod($commandInfo, $includeAllPublicMethods)
{
return static::isHookMethod($commandInfo) || static::isCommandMethod($commandInfo, $includeAllPublicMethods);
}
public static function isHookMethod($commandInfo)
{
return $commandInfo->hasAnnotation('hook');
}
public static function isCommandMethod($commandInfo, $includeAllPublicMethods)
{
// Ignore everything labeled @hook
if (static::isHookMethod($commandInfo)) {
return false;
}
// Include everything labeled @command
if ($commandInfo->hasAnnotation('command')) {
return true;
}
// Skip anything that has a missing or invalid name.
$commandName = $commandInfo->getName();
if (empty($commandName) || preg_match('#[^a-zA-Z0-9:_-]#', $commandName)) {
return false;
}
// Skip anything named like an accessor ('get' or 'set')
if (preg_match('#^(get[A-Z]|set[A-Z])#', $commandInfo->getMethodName())) {
return false;
}
// Default to the setting of 'include all public methods'.
return $includeAllPublicMethods;
}
public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
{
foreach ($commandInfoList as $commandInfo) {
if (static::isHookMethod($commandInfo)) {
$this->registerCommandHook($commandInfo, $commandFileInstance);
}
}
}
/**
* Register a command hook given the CommandInfo for a method.
*
* The hook format is:
*
* @hook type name type
*
* For example, the pre-validate hook for the core:init command is:
*
* @hook pre-validate core:init
*
* If no command name is provided, then this hook will affect every
* command that is defined in the same file.
*
* If no hook is provided, then we will presume that ALTER_RESULT
* is intended.
*
* @param CommandInfo $commandInfo Information about the command hook method.
* @param object $commandFileInstance An instance of the CommandFile class.
*/
public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
{
// Ignore if the command info has no @hook
if (!static::isHookMethod($commandInfo)) {
return;
}
$hookData = $commandInfo->getAnnotation('hook');
$hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
$commandName = $this->getNthWord($hookData, 1);
// Register the hook
$callback = [$commandFileInstance, $commandInfo->getMethodName()];
$this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
// If the hook has options, then also register the commandInfo
// with the hook manager, so that we can add options and such to
// the commands they hook.
if (!$commandInfo->options()->isEmpty()) {
$this->commandProcessor()->hookManager()->recordHookOptions($commandInfo, $commandName);
}
}
protected function getNthWord($string, $n, $default = '', $delimiter = ' ')
{
$words = explode($delimiter, $string);
if (!empty($words[$n])) {
return $words[$n];
}
return $default;
}
public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
{
$this->alterCommandInfo($commandInfo, $commandFileInstance);
$command = new AnnotatedCommand($commandInfo->getName());
$commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
$command->setCommandCallback($commandCallback);
$command->setCommandProcessor($this->commandProcessor);
$command->setCommandInfo($commandInfo);
$automaticOptions = $this->callAutomaticOptionsProviders($commandInfo);
$command->setCommandOptions($commandInfo, $automaticOptions);
// Annotation commands are never bootstrap-aware, but for completeness
// we will notify on every created command, as some clients may wish to
// use this notification for some other purpose.
$this->notify($command);
return $command;
}
/**
* Give plugins an opportunity to update the commandInfo
*/
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance)
{
foreach ($this->commandInfoAlterers as $alterer) {
$alterer->alterCommandInfo($commandInfo, $commandFileInstance);
}
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function callAutomaticOptionsProviders(CommandInfo $commandInfo)
{
$automaticOptions = [];
foreach ($this->automaticOptionsProviderList as $automaticOptionsProvider) {
$automaticOptions += $automaticOptionsProvider->automaticOptions($commandInfo);
}
return $automaticOptions;
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function automaticOptions(CommandInfo $commandInfo)
{
$automaticOptions = [];
$formatManager = $this->commandProcessor()->formatterManager();
if ($formatManager) {
$annotationData = $commandInfo->getAnnotations()->getArrayCopy();
$formatterOptions = new FormatterOptions($annotationData);
$dataType = $commandInfo->getReturnType();
$automaticOptions = $formatManager->automaticOptions($formatterOptions, $dataType);
}
return $automaticOptions;
}
}
PK }?V0!= = src/AnnotatedCommand.phpnu W+A getName();
}
}
parent::__construct($name);
if ($commandInfo && $commandInfo->hasAnnotation('command')) {
$this->setCommandInfo($commandInfo);
$this->setCommandOptions($commandInfo);
}
}
public function setCommandCallback($commandCallback)
{
$this->commandCallback = $commandCallback;
return $this;
}
public function setCommandProcessor($commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
public function commandProcessor()
{
// If someone is using an AnnotatedCommand, and is NOT getting
// it from an AnnotatedCommandFactory OR not correctly injecting
// a command processor via setCommandProcessor() (ideally via the
// DI container), then we'll just give each annotated command its
// own command processor. This is not ideal; preferably, there would
// only be one instance of the command processor in the application.
if (!isset($this->commandProcessor)) {
$this->commandProcessor = new CommandProcessor(new HookManager());
}
return $this->commandProcessor;
}
public function getReturnType()
{
return $this->returnType;
}
public function setReturnType($returnType)
{
$this->returnType = $returnType;
return $this;
}
public function getAnnotationData()
{
return $this->annotationData;
}
public function setAnnotationData($annotationData)
{
$this->annotationData = $annotationData;
return $this;
}
public function getTopics()
{
return $this->topics;
}
public function setTopics($topics)
{
$this->topics = $topics;
return $this;
}
public function setCommandInfo($commandInfo)
{
$this->setDescription($commandInfo->getDescription());
$this->setHelp($commandInfo->getHelp());
$this->setAliases($commandInfo->getAliases());
$this->setAnnotationData($commandInfo->getAnnotations());
$this->setTopics($commandInfo->getTopics());
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
$this->addUsageOrExample($usage, $description);
}
$this->setCommandArguments($commandInfo);
$this->setReturnType($commandInfo->getReturnType());
// Hidden commands available since Symfony 3.2
// http://symfony.com/doc/current/console/hide_commands.html
if (method_exists($this, 'setHidden')) {
$this->setHidden($commandInfo->getHidden());
}
return $this;
}
public function getExampleUsages()
{
return $this->examples;
}
protected function addUsageOrExample($usage, $description)
{
$this->addUsage($usage);
if (!empty($description)) {
$this->examples[$usage] = $description;
}
}
public function helpAlter(\DomDocument $originalDom)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$commandXML->setAttribute('id', $this->getName());
$commandXML->setAttribute('name', $this->getName());
// Get the original element and its top-level elements.
$originalCommandXML = $this->getSingleElementByTagName($dom, $originalDom, 'command');
$originalUsagesXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'usages');
$originalDescriptionXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'description');
$originalHelpXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'help');
$originalArgumentsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'arguments');
$originalOptionsXML = $this->getSingleElementByTagName($dom, $originalCommandXML, 'options');
// Keep only the first of the elements
$newUsagesXML = $dom->createElement('usages');
$firstUsageXML = $this->getSingleElementByTagName($dom, $originalUsagesXML, 'usage');
$newUsagesXML->appendChild($firstUsageXML);
// Create our own elements
$newExamplesXML = $dom->createElement('examples');
foreach ($this->examples as $usage => $description) {
$newExamplesXML->appendChild($exampleXML = $dom->createElement('example'));
$exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage));
$exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description));
}
// Create our own elements
$newAliasesXML = $dom->createElement('aliases');
foreach ($this->getAliases() as $alias) {
$newAliasesXML->appendChild($dom->createElement('alias', $alias));
}
// Create our own elements
$newTopicsXML = $dom->createElement('topics');
foreach ($this->getTopics() as $topic) {
$newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic));
}
// Place the different elements into the element in the desired order
$commandXML->appendChild($newUsagesXML);
$commandXML->appendChild($newExamplesXML);
$commandXML->appendChild($originalDescriptionXML);
$commandXML->appendChild($originalArgumentsXML);
$commandXML->appendChild($originalOptionsXML);
$commandXML->appendChild($originalHelpXML);
$commandXML->appendChild($newAliasesXML);
$commandXML->appendChild($newTopicsXML);
return $dom;
}
protected function getSingleElementByTagName($dom, $parent, $tagName)
{
// There should always be exactly one '' element.
$elements = $parent->getElementsByTagName($tagName);
$result = $elements->item(0);
$result = $dom->importNode($result, true);
return $result;
}
protected function setCommandArguments($commandInfo)
{
$this->setUsesInputInterface($commandInfo);
$this->setUsesOutputInterface($commandInfo);
$this->setCommandArgumentsFromParameters($commandInfo);
return $this;
}
/**
* Check whether the first parameter is an InputInterface.
*/
protected function checkUsesInputInterface($params)
{
/** @var \ReflectionParameter $firstParam */
$firstParam = reset($params);
return $firstParam && $firstParam->getClass() && $firstParam->getClass()->implementsInterface(
'\\Symfony\\Component\\Console\\Input\\InputInterface'
);
}
/**
* Determine whether this command wants to get its inputs
* via an InputInterface or via its command parameters
*/
protected function setUsesInputInterface($commandInfo)
{
$params = $commandInfo->getParameters();
$this->usesInputInterface = $this->checkUsesInputInterface($params);
return $this;
}
/**
* Determine whether this command wants to send its output directly
* to the provided OutputInterface, or whether it will returned
* structured output to be processed by the command processor.
*/
protected function setUsesOutputInterface($commandInfo)
{
$params = $commandInfo->getParameters();
$index = $this->checkUsesInputInterface($params) ? 1 : 0;
$this->usesOutputInterface =
(count($params) > $index) &&
$params[$index]->getClass() &&
$params[$index]->getClass()->implementsInterface(
'\\Symfony\\Component\\Console\\Output\\OutputInterface'
)
;
return $this;
}
protected function setCommandArgumentsFromParameters($commandInfo)
{
$args = $commandInfo->arguments()->getValues();
foreach ($args as $name => $defaultValue) {
$description = $commandInfo->arguments()->getDescription($name);
$hasDefault = $commandInfo->arguments()->hasDefault($name);
$parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
$this->addArgument($name, $parameterMode, $description, $defaultValue);
}
return $this;
}
protected function getCommandArgumentMode($hasDefault, $defaultValue)
{
if (!$hasDefault) {
return InputArgument::REQUIRED;
}
if (is_array($defaultValue)) {
return InputArgument::IS_ARRAY;
}
return InputArgument::OPTIONAL;
}
public function setCommandOptions($commandInfo, $automaticOptions = [])
{
$inputOptions = $commandInfo->inputOptions();
$this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
return $this;
}
public function addOptions($inputOptions, $automaticOptions = [])
{
foreach ($inputOptions as $name => $inputOption) {
$description = $inputOption->getDescription();
if (empty($description) && isset($automaticOptions[$name])) {
$description = $automaticOptions[$name]->getDescription();
$inputOption = static::inputOptionSetDescription($inputOption, $description);
}
$this->getDefinition()->addOption($inputOption);
}
}
protected static function inputOptionSetDescription($inputOption, $description)
{
// Recover the 'mode' value, because Symfony is stubborn
$mode = 0;
if ($inputOption->isValueRequired()) {
$mode |= InputOption::VALUE_REQUIRED;
}
if ($inputOption->isValueOptional()) {
$mode |= InputOption::VALUE_OPTIONAL;
}
if ($inputOption->isArray()) {
$mode |= InputOption::VALUE_IS_ARRAY;
}
if (!$mode) {
$mode = InputOption::VALUE_NONE;
}
$inputOption = new InputOption(
$inputOption->getName(),
$inputOption->getShortcut(),
$mode,
$description,
$inputOption->getDefault()
);
return $inputOption;
}
/**
* Returns all of the hook names that may be called for this command.
*
* @return array
*/
public function getNames()
{
return HookManager::getNames($this, $this->commandCallback);
}
/**
* Add any options to this command that are defined by hook implementations
*/
public function optionsHook()
{
$this->commandProcessor()->optionsHook(
$this,
$this->getNames(),
$this->annotationData
);
}
public function optionsHookForHookAnnotations($commandInfoList)
{
foreach ($commandInfoList as $commandInfo) {
$inputOptions = $commandInfo->inputOptions();
$this->addOptions($inputOptions);
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
if (!in_array($usage, $this->getUsages())) {
$this->addUsageOrExample($usage, $description);
}
}
}
}
/**
* {@inheritdoc}
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$this->commandProcessor()->interact(
$input,
$output,
$this->getNames(),
$this->annotationData
);
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
// Allow the hook manager a chance to provide configuration values,
// if there are any registered hooks to do that.
$this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Validate, run, process, alter, handle results.
return $this->commandProcessor()->process(
$output,
$this->getNames(),
$this->commandCallback,
$this->createCommandData($input, $output)
);
}
/**
* This function is available for use by a class that may
* wish to extend this class rather than use annotations to
* define commands. Using this technique does allow for the
* use of annotations to define hooks.
*/
public function processResults(InputInterface $input, OutputInterface $output, $results)
{
$commandData = $this->createCommandData($input, $output);
$commandProcessor = $this->commandProcessor();
$names = $this->getNames();
$results = $commandProcessor->processResults(
$names,
$results,
$commandData
);
return $commandProcessor->handleResults(
$output,
$names,
$results,
$commandData
);
}
protected function createCommandData(InputInterface $input, OutputInterface $output)
{
$commandData = new CommandData(
$this->annotationData,
$input,
$output
);
$commandData->setUseIOInterfaces(
$this->usesInputInterface,
$this->usesOutputInterface
);
// Allow the commandData to cache the list of options with
// special default values ('null' and 'true'), as these will
// need special handling. @see CommandData::options().
$commandData->cacheSpecialDefaults($this->getDefinition());
return $commandData;
}
}
PK }?VV; ; src/CommandFileDiscovery.phpnu W+A discoverNamespaced($moduleList, '\Drupal');
*
* To discover global commands:
*
* $commandFiles = $discovery->discover($drupalRoot, '\Drupal');
*
* WARNING:
*
* This class is deprecated. Commandfile discovery is complicated, and does
* not work from within phar files. It is recommended to instead use a static
* list of command classes as shown in https://github.com/g1a/starter/blob/master/example
*
* For a better alternative when implementing a plugin mechanism, see
* https://robo.li/extending/#register-command-files-via-psr-4-autoloading
*/
class CommandFileDiscovery
{
/** @var string[] */
protected $excludeList;
/** @var string[] */
protected $searchLocations;
/** @var string */
protected $searchPattern = '*Commands.php';
/** @var boolean */
protected $includeFilesAtBase = true;
/** @var integer */
protected $searchDepth = 2;
/** @var bool */
protected $followLinks = false;
/** @var string[] */
protected $strippedNamespaces;
public function __construct()
{
$this->excludeList = ['Exclude'];
$this->searchLocations = [
'Command',
'CliTools', // TODO: Maybe remove
];
}
/**
* Specify whether to search for files at the base directory
* ($directoryList parameter to discover and discoverNamespaced
* methods), or only in the directories listed in the search paths.
*
* @param boolean $includeFilesAtBase
*/
public function setIncludeFilesAtBase($includeFilesAtBase)
{
$this->includeFilesAtBase = $includeFilesAtBase;
return $this;
}
/**
* Set the list of excludes to add to the finder, replacing
* whatever was there before.
*
* @param array $excludeList The list of directory names to skip when
* searching for command files.
*/
public function setExcludeList($excludeList)
{
$this->excludeList = $excludeList;
return $this;
}
/**
* Add one more location to the exclude list.
*
* @param string $exclude One directory name to skip when searching
* for command files.
*/
public function addExclude($exclude)
{
$this->excludeList[] = $exclude;
return $this;
}
/**
* Set the search depth. By default, fills immediately in the
* base directory are searched, plus all of the search locations
* to this specified depth. If the search locations is set to
* an empty array, then the base directory is searched to this
* depth.
*/
public function setSearchDepth($searchDepth)
{
$this->searchDepth = $searchDepth;
return $this;
}
/**
* Specify that the discovery object should follow symlinks. By
* default, symlinks are not followed.
*/
public function followLinks($followLinks = true)
{
$this->followLinks = $followLinks;
return $this;
}
/**
* Set the list of search locations to examine in each directory where
* command files may be found. This replaces whatever was there before.
*
* @param array $searchLocations The list of locations to search for command files.
*/
public function setSearchLocations($searchLocations)
{
$this->searchLocations = $searchLocations;
return $this;
}
/**
* Set a particular namespace part to ignore. This is useful in plugin
* mechanisms where the plugin is placed by Composer.
*
* For example, Drush extensions are placed in `./drush/Commands`.
* If the Composer installer path is `"drush/Commands/contrib/{$name}": ["type:drupal-drush"]`,
* then Composer will place the command files in `drush/Commands/contrib`.
* The namespace should not be any different in this instance than if
* the extension were placed in `drush/Commands`, though, so Drush therefore
* calls `ignoreNamespacePart('contrib', 'Commands')`. This causes the
* `contrib` component to be removed from the namespace if it follows
* the namespace `Commands`. If the '$base' parameter is not specified, then
* the ignored portion of the namespace may appear anywhere in the path.
*/
public function ignoreNamespacePart($ignore, $base = '')
{
$replacementPart = '\\';
if (!empty($base)) {
$replacementPart .= $base . '\\';
}
$ignoredPart = $replacementPart . $ignore . '\\';
$this->strippedNamespaces[$ignoredPart] = $replacementPart;
return $this;
}
/**
* Add one more location to the search location list.
*
* @param string $location One more relative path to search
* for command files.
*/
public function addSearchLocation($location)
{
$this->searchLocations[] = $location;
return $this;
}
/**
* Specify the pattern / regex used by the finder to search for
* command files.
*/
public function setSearchPattern($searchPattern)
{
$this->searchPattern = $searchPattern;
return $this;
}
/**
* Given a list of directories, e.g. Drupal modules like:
*
* core/modules/block
* core/modules/dblog
* modules/default_content
*
* Discover command files in any of these locations.
*
* @param string|string[] $directoryList Places to search for commands.
*
* @return array
*/
public function discoverNamespaced($directoryList, $baseNamespace = '')
{
return $this->discover($this->convertToNamespacedList((array)$directoryList), $baseNamespace);
}
/**
* Given a simple list containing paths to directories, where
* the last component of the path should appear in the namespace,
* after the base namespace, this function will return an
* associative array mapping the path's basename (e.g. the module
* name) to the directory path.
*
* Module names must be unique.
*
* @param string[] $directoryList A list of module locations
*
* @return array
*/
public function convertToNamespacedList($directoryList)
{
$namespacedArray = [];
foreach ((array)$directoryList as $directory) {
$namespacedArray[basename($directory)] = $directory;
}
return $namespacedArray;
}
/**
* Search for command files in the specified locations. This is the function that
* should be used for all locations that are NOT modules of a framework.
*
* @param string|string[] $directoryList Places to search for commands.
* @return array
*/
public function discover($directoryList, $baseNamespace = '')
{
$commandFiles = [];
foreach ((array)$directoryList as $key => $directory) {
$itemsNamespace = $this->joinNamespace([$baseNamespace, $key]);
$commandFiles = array_merge(
$commandFiles,
$this->discoverCommandFiles($directory, $itemsNamespace),
$this->discoverCommandFiles("$directory/src", $itemsNamespace)
);
}
return $this->fixNamespaces($commandFiles);
}
/**
* fixNamespaces will alter the namespaces in the commandFiles
* result to remove the Composer placement directory, if any.
*/
protected function fixNamespaces($commandFiles)
{
// Do nothing unless the client told us to remove some namespace components.
if (empty($this->strippedNamespaces)) {
return $commandFiles;
}
// Strip out any part of the namespace the client did not want.
// @see CommandFileDiscovery::ignoreNamespacePart
return array_map(
function ($fqcn) {
return str_replace(
array_keys($this->strippedNamespaces),
array_values($this->strippedNamespaces),
$fqcn
);
},
$commandFiles
);
}
/**
* Search for command files in specific locations within a single directory.
*
* In each location, we will accept only a few places where command files
* can be found. This will reduce the need to search through many unrelated
* files.
*
* The default search locations include:
*
* .
* CliTools
* src/CliTools
*
* The pattern we will look for is any file whose name ends in 'Commands.php'.
* A list of paths to found files will be returned.
*/
protected function discoverCommandFiles($directory, $baseNamespace)
{
$commandFiles = [];
// In the search location itself, we will search for command files
// immediately inside the directory only.
if ($this->includeFilesAtBase) {
$commandFiles = $this->discoverCommandFilesInLocation(
$directory,
$this->getBaseDirectorySearchDepth(),
$baseNamespace
);
}
// In the other search locations,
foreach ($this->searchLocations as $location) {
$itemsNamespace = $this->joinNamespace([$baseNamespace, $location]);
$commandFiles = array_merge(
$commandFiles,
$this->discoverCommandFilesInLocation(
"$directory/$location",
$this->getSearchDepth(),
$itemsNamespace
)
);
}
return $commandFiles;
}
/**
* Return a Finder search depth appropriate for our selected search depth.
*
* @return string
*/
protected function getSearchDepth()
{
return $this->searchDepth <= 0 ? '== 0' : '<= ' . $this->searchDepth;
}
/**
* Return a Finder search depth for the base directory. If the
* searchLocations array has been populated, then we will only search
* for files immediately inside the base directory; no traversal into
* deeper directories will be done, as that would conflict with the
* specification provided by the search locations. If there is no
* search location, then we will search to whatever depth was specified
* by the client.
*
* @return string
*/
protected function getBaseDirectorySearchDepth()
{
if (!empty($this->searchLocations)) {
return '== 0';
}
return $this->getSearchDepth();
}
/**
* Search for command files in just one particular location. Returns
* an associative array mapping from the pathname of the file to the
* classname that it contains. The pathname may be ignored if the search
* location is included in the autoloader.
*
* @param string $directory The location to search
* @param string $depth How deep to search (e.g. '== 0' or '< 2')
* @param string $baseNamespace Namespace to prepend to each classname
*
* @return array
*/
protected function discoverCommandFilesInLocation($directory, $depth, $baseNamespace)
{
if (!is_dir($directory)) {
return [];
}
$finder = $this->createFinder($directory, $depth);
$commands = [];
foreach ($finder as $file) {
$relativePathName = $file->getRelativePathname();
$relativeNamespaceAndClassname = str_replace(
['/', '-', '.php'],
['\\', '_', ''],
$relativePathName
);
$classname = $this->joinNamespace([$baseNamespace, $relativeNamespaceAndClassname]);
$commandFilePath = $this->joinPaths([$directory, $relativePathName]);
$commands[$commandFilePath] = $classname;
}
return $commands;
}
/**
* Create a Finder object for use in searching a particular directory
* location.
*
* @param string $directory The location to search
* @param string $depth The depth limitation
*
* @return Finder
*/
protected function createFinder($directory, $depth)
{
$finder = new Finder();
$finder->files()
->name($this->searchPattern)
->in($directory)
->depth($depth);
foreach ($this->excludeList as $item) {
$finder->exclude($item);
}
if ($this->followLinks) {
$finder->followLinks();
}
return $finder;
}
/**
* Combine the items of the provied array into a backslash-separated
* namespace string. Empty and numeric items are omitted.
*
* @param array $namespaceParts List of components of a namespace
*
* @return string
*/
protected function joinNamespace(array $namespaceParts)
{
return $this->joinParts(
'\\',
$namespaceParts,
function ($item) {
return !is_numeric($item) && !empty($item);
}
);
}
/**
* Combine the items of the provied array into a slash-separated
* pathname. Empty items are omitted.
*
* @param array $pathParts List of components of a path
*
* @return string
*/
protected function joinPaths(array $pathParts)
{
$path = $this->joinParts(
'/',
$pathParts,
function ($item) {
return !empty($item);
}
);
return str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
/**
* Simple wrapper around implode and array_filter.
*
* @param string $delimiter
* @param array $parts
* @param callable $filterFunction
*/
protected function joinParts($delimiter, $parts, $filterFunction)
{
$parts = array_map(
function ($item) use ($delimiter) {
return rtrim($item, $delimiter);
},
$parts
);
return implode(
$delimiter,
array_filter($parts, $filterFunction)
);
}
}
PK }?VԍRq src/Options/PrepareFormatter.phpnu W+A application = $application;
}
/**
* @param ConsoleCommandEvent $event
*/
public function alterCommandOptions(ConsoleCommandEvent $event)
{
/* @var Command $command */
$command = $event->getCommand();
$input = $event->getInput();
if ($command->getName() == 'help') {
// Symfony 3.x prepares $input for us; Symfony 2.x, on the other
// hand, passes it in prior to binding with the command definition,
// so we have to go to a little extra work. It may be inadvisable
// to do these steps for commands other than 'help'.
if (!$input->hasArgument('command_name')) {
$command->ignoreValidationErrors();
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
}
// Symfony Console helpfully swaps 'command_name' and 'command'
// depending on whether the user entered `help foo` or `--help foo`.
// One of these is always `help`, and the other is the command we
// are actually interested in.
$nameOfCommandToDescribe = $event->getInput()->getArgument('command_name');
if ($nameOfCommandToDescribe == 'help') {
$nameOfCommandToDescribe = $event->getInput()->getArgument('command');
}
$commandToDescribe = $this->application->find($nameOfCommandToDescribe);
$this->findAndAddHookOptions($commandToDescribe);
} else {
$this->findAndAddHookOptions($command);
}
}
public function findAndAddHookOptions($command)
{
if (!$command instanceof AnnotatedCommand) {
return;
}
$command->optionsHook();
}
/**
* @{@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'alterCommandOptions'];
}
}
PK }?VIfm m 1 src/Options/AutomaticOptionsProviderInterface.phpnu W+A defaultWidth = $defaultWidth;
}
public function setApplication(Application $application)
{
$this->application = $application;
}
public function setTerminal($terminal)
{
$this->terminal = $terminal;
}
public function getTerminal()
{
if (!$this->terminal && class_exists('\Symfony\Component\Console\Terminal')) {
$this->terminal = new \Symfony\Component\Console\Terminal();
}
return $this->terminal;
}
public function enableWrap($shouldWrap)
{
$this->shouldWrap = $shouldWrap;
}
public function prepare(CommandData $commandData, FormatterOptions $options)
{
$width = $this->getTerminalWidth();
if (!$width) {
$width = $this->defaultWidth;
}
// Enforce minimum and maximum widths
$width = min($width, $this->getMaxWidth($commandData));
$width = max($width, $this->getMinWidth($commandData));
$options->setWidth($width);
}
protected function getTerminalWidth()
{
// Don't wrap if wrapping has been disabled.
if (!$this->shouldWrap) {
return 0;
}
$terminal = $this->getTerminal();
if ($terminal) {
return $terminal->getWidth();
}
return $this->getTerminalWidthViaApplication();
}
protected function getTerminalWidthViaApplication()
{
if (!$this->application) {
return 0;
}
$dimensions = $this->application->getTerminalDimensions();
if ($dimensions[0] == null) {
return 0;
}
return $dimensions[0];
}
protected function getMaxWidth(CommandData $commandData)
{
return $this->maxWidth;
}
protected function getMinWidth(CommandData $commandData)
{
return $this->minWidth;
}
}
PK }?V"\4 ( src/CommandCreationListenerInterface.phpnu W+A message = $message;
// Ensure the exit code is non-zero. The exit code may have
// come from an exception, and those often default to zero if
// a specific value is not provided.
$this->exitCode = $exitCode == 0 ? 1 : $exitCode;
}
public function getExitCode()
{
return $this->exitCode;
}
public function getOutputData()
{
return $this->message;
}
}
PK }?V+ src/ExitCodeInterface.phpnu W+A hooks;
}
/**
* Add a hook
*
* @param mixed $callback The callback function to call
* @param string $hook The name of the hook to add
* @param string $name The name of the command to hook
* ('*' for all)
*/
public function add(callable $callback, $hook, $name = '*')
{
if (empty($name)) {
$name = static::getClassNameFromCallback($callback);
}
$this->hooks[$name][$hook][] = $callback;
return $this;
}
public function recordHookOptions($commandInfo, $name)
{
$this->hookOptions[$name][] = $commandInfo;
return $this;
}
public static function getNames($command, $callback)
{
return array_filter(
array_merge(
static::getNamesUsingCommands($command),
[static::getClassNameFromCallback($callback)]
)
);
}
protected static function getNamesUsingCommands($command)
{
return array_merge(
[$command->getName()],
$command->getAliases()
);
}
/**
* If a command hook does not specify any particular command
* name that it should be attached to, then it will be applied
* to every command that is defined in the same class as the hook.
* This is controlled by using the namespace + class name of
* the implementing class of the callback hook.
*/
protected static function getClassNameFromCallback($callback)
{
if (!is_array($callback)) {
return '';
}
$reflectionClass = new \ReflectionClass($callback[0]);
return $reflectionClass->getName();
}
/**
* Add a replace command hook
*
* @param type ReplaceCommandHookInterface $provider
* @param type string $command_name The name of the command to replace
*/
public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name)
{
$this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook;
return $this;
}
public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
{
$this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher;
return $this;
}
public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
{
$this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher;
return $this;
}
public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
{
$this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher;
return $this;
}
public function addCommandEvent(EventSubscriberInterface $eventSubscriber)
{
// Wrap the event subscriber in a dispatcher and add it
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber($eventSubscriber);
return $this->addCommandEventDispatcher($dispatcher);
}
/**
* Add an configuration provider hook
*
* @param type InitializeHookInterface $provider
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
{
$this->hooks[$name][self::INITIALIZE][] = $initializeHook;
return $this;
}
/**
* Add an option hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addOptionHook(OptionHookInterface $interactor, $name = '*')
{
$this->hooks[$name][self::INTERACT][] = $interactor;
return $this;
}
/**
* Add an interact hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addInteractor(InteractorInterface $interactor, $name = '*')
{
$this->hooks[$name][self::INTERACT][] = $interactor;
return $this;
}
/**
* Add a pre-validator hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPreValidator(ValidatorInterface $validator, $name = '*')
{
$this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
return $this;
}
/**
* Add a validator hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addValidator(ValidatorInterface $validator, $name = '*')
{
$this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
return $this;
}
/**
* Add a pre-command hook. This is the same as a validator hook, except
* that it will run after all of the post-validator hooks.
*
* @param type ValidatorInterface $preCommand
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
{
$this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
return $this;
}
/**
* Add a post-command hook. This is the same as a pre-process hook,
* except that it will run before the first pre-process hook.
*
* @param type ProcessResultInterface $postCommand
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
{
$this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
return $this;
}
/**
* Add a result processor.
*
* @param type ProcessResultInterface $resultProcessor
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
{
$this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
return $this;
}
/**
* Add a result alterer. After a result is processed
* by a result processor, an alter hook may be used
* to convert the result from one form to another.
*
* @param type AlterResultInterface $resultAlterer
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
{
$this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
return $this;
}
/**
* Add a status determiner. Usually, a command should return
* an integer on error, or a result object on success (which
* implies a status code of zero). If a result contains the
* status code in some other field, then a status determiner
* can be used to call the appropriate accessor method to
* determine the status code. This is usually not necessary,
* though; a command that fails may return a CommandError
* object, which contains a status code and a result message
* to display.
* @see CommandError::getExitCode()
*
* @param type StatusDeterminerInterface $statusDeterminer
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
{
$this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
return $this;
}
/**
* Add an output extractor. If a command returns an object
* object, by default it is passed directly to the output
* formatter (if in use) for rendering. If the result object
* contains more information than just the data to render, though,
* then an output extractor can be used to call the appopriate
* accessor method of the result object to get the data to
* rendered. This is usually not necessary, though; it is preferable
* to have complex result objects implement the OutputDataInterface.
* @see OutputDataInterface::getOutputData()
*
* @param type ExtractOutputInterface $outputExtractor
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
{
$this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
return $this;
}
public function getHookOptionsForCommand($command)
{
$names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
return $this->getHookOptions($names);
}
/**
* @return CommandInfo[]
*/
public function getHookOptions($names)
{
$result = [];
foreach ($names as $name) {
if (isset($this->hookOptions[$name])) {
$result = array_merge($result, $this->hookOptions[$name]);
}
}
return $result;
}
/**
* Get a set of hooks with the provided name(s). Include the
* pre- and post- hooks, and also include the global hooks ('*')
* in addition to the named hooks provided.
*
* @param string|array $names The name of the function being hooked.
* @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT])
*
* @return callable[]
*/
public function getHooks($names, $hooks, $annotationData = null)
{
return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
}
protected function addWildcardHooksToNames($names, $annotationData = null)
{
$names = array_merge(
(array)$names,
($annotationData == null) ? [] : array_map(function ($item) {
return "@$item";
}, $annotationData->keys())
);
$names[] = '*';
return array_unique($names);
}
/**
* Get a set of hooks with the provided name(s).
*
* @param string|array $names The name of the function being hooked.
* @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT])
*
* @return callable[]
*/
public function get($names, $hooks)
{
$result = [];
foreach ((array)$hooks as $hook) {
foreach ((array)$names as $name) {
$result = array_merge($result, $this->getHook($name, $hook));
}
}
return $result;
}
/**
* Get a single named hook.
*
* @param string $name The name of the hooked method
* @param string $hook The specific hook name (e.g. alter)
*
* @return callable[]
*/
public function getHook($name, $hook)
{
if (isset($this->hooks[$name][$hook])) {
return $this->hooks[$name][$hook];
}
return [];
}
/**
* Call the command event hooks.
*
* TODO: This should be moved to CommandEventHookDispatcher, which
* should become the class that implements EventSubscriberInterface.
* This change would break all clients, though, so postpone until next
* major release.
*
* @param ConsoleCommandEvent $event
*/
public function callCommandEventHooks(ConsoleCommandEvent $event)
{
/* @var Command $command */
$command = $event->getCommand();
$dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
$dispatcher->callCommandEventHooks($event);
}
/**
* @{@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];
}
}
PK }?Vf 1 src/Hooks/Dispatchers/ExtracterHookDispatcher.phpnu W+A getOutputData();
}
$hooks = [
HookManager::EXTRACT_OUTPUT,
];
$extractors = $this->getHooks($hooks);
foreach ($extractors as $extractor) {
$structuredOutput = $this->callExtractor($extractor, $result);
if (isset($structuredOutput)) {
return $structuredOutput;
}
}
return $result;
}
protected function callExtractor($extractor, $result)
{
if ($extractor instanceof ExtractOutputInterface) {
return $extractor->extractOutput($result);
}
if (is_callable($extractor)) {
return $extractor($result);
}
}
}
PK }?V%̀ 2 src/Hooks/Dispatchers/InitializeHookDispatcher.phpnu W+A getHooks($hooks, $annotationData);
foreach ($providers as $provider) {
$this->callInitializeHook($provider, $input, $annotationData);
}
}
protected function callInitializeHook($provider, $input, AnnotationData $annotationData)
{
if ($provider instanceof InitializeHookInterface) {
return $provider->initialize($input, $annotationData);
}
if (is_callable($provider)) {
return $provider($input, $annotationData);
}
}
}
PK }?V. / src/Hooks/Dispatchers/OptionsHookDispatcher.phpnu W+A getHooks($hooks, $annotationData);
foreach ($optionHooks as $optionHook) {
$this->callOptionHook($optionHook, $command, $annotationData);
}
$commandInfoList = $this->hookManager->getHookOptionsForCommand($command);
if ($command instanceof AnnotatedCommand) {
$command->optionsHookForHookAnnotations($commandInfoList);
}
}
protected function callOptionHook($optionHook, $command, AnnotationData $annotationData)
{
if ($optionHook instanceof OptionHookInterface) {
return $optionHook->getOptions($command, $annotationData);
}
if (is_callable($optionHook)) {
return $optionHook($command, $annotationData);
}
}
}
PK }?V 0 src/Hooks/Dispatchers/InteractHookDispatcher.phpnu W+A getHooks($hooks, $annotationData);
foreach ($interactors as $interactor) {
$this->callInteractor($interactor, $input, $output, $annotationData);
}
}
protected function callInteractor($interactor, $input, $output, AnnotationData $annotationData)
{
if ($interactor instanceof InteractorInterface) {
return $interactor->interact($input, $output, $annotationData);
}
if (is_callable($interactor)) {
return $interactor($input, $output, $annotationData);
}
}
}
PK }?V!3 6 src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.phpnu W+A getReplaceCommandHooks());
}
/**
* @return \callable[]
*/
public function getReplaceCommandHooks()
{
$hooks = [
HookManager::REPLACE_COMMAND_HOOK,
];
$replaceCommandHooks = $this->getHooks($hooks);
return $replaceCommandHooks;
}
/**
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
*
* @return callable
*/
public function getReplacementCommand(CommandData $commandData)
{
$replaceCommandHooks = $this->getReplaceCommandHooks();
// We only take the first hook implementation of "replace-command" as the replacement. Commands shouldn't have
// more than one replacement.
$replacementCommand = reset($replaceCommandHooks);
if ($this->logger && count($replaceCommandHooks) > 1) {
$command_name = $commandData->annotationData()->get('command', 'unknown');
$message = "Multiple implementations of the \"replace - command\" hook exist for the \"$command_name\" command.\n";
foreach ($replaceCommandHooks as $replaceCommandHook) {
$class = get_class($replaceCommandHook[0]);
$method = $replaceCommandHook[1];
$hook_name = "$class->$method";
$message .= " - $hook_name\n";
}
$this->logger->warning($message);
}
return $replacementCommand;
}
}
PK }?VGP P 8 src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.phpnu W+A getExitCode();
}
$hooks = [
HookManager::STATUS_DETERMINER,
];
// If the result does not implement ExitCodeInterface,
// then we'll see if there is a determiner that can
// extract a status code from the result.
$determiners = $this->getHooks($hooks);
foreach ($determiners as $determiner) {
$status = $this->callDeterminer($determiner, $result);
if (isset($status)) {
return $status;
}
}
}
protected function callDeterminer($determiner, $result)
{
if ($determiner instanceof StatusDeterminerInterface) {
return $determiner->determineStatusCode($result);
}
if (is_callable($determiner)) {
return $determiner($result);
}
}
}
PK }?V?Ƶ 0 src/Hooks/Dispatchers/ValidateHookDispatcher.phpnu W+A getHooks($hooks, $commandData->annotationData());
foreach ($validators as $validator) {
$validated = $this->callValidator($validator, $commandData);
if ($validated === false) {
return new CommandError();
}
if (is_object($validated)) {
return $validated;
}
}
}
protected function callValidator($validator, CommandData $commandData)
{
if ($validator instanceof ValidatorInterface) {
return $validator->validate($commandData);
}
if (is_callable($validator)) {
return $validator($commandData);
}
}
}
PK }?V} 4 src/Hooks/Dispatchers/CommandEventHookDispatcher.phpnu W+A getHooks($hooks);
foreach ($commandEventHooks as $commandEvent) {
if ($commandEvent instanceof EventDispatcherInterface) {
$commandEvent->dispatch(ConsoleEvents::COMMAND, $event);
}
if (is_callable($commandEvent)) {
$commandEvent($event);
}
}
}
}
PK }?VA 5 src/Hooks/Dispatchers/ProcessResultHookDispatcher.phpnu W+A getHooks($hooks, $commandData->annotationData());
foreach ($processors as $processor) {
$result = $this->callProcessor($processor, $result, $commandData);
}
return $result;
}
protected function callProcessor($processor, $result, CommandData $commandData)
{
$processed = null;
if ($processor instanceof ProcessResultInterface) {
$processed = $processor->process($result, $commandData);
}
if (is_callable($processor)) {
$processed = $processor($result, $commandData);
}
if (isset($processed)) {
return $processed;
}
return $result;
}
}
PK }?V7<0a a ( src/Hooks/Dispatchers/HookDispatcher.phpnu W+A hookManager = $hookManager;
$this->names = $names;
}
public function getHooks($hooks, $annotationData = null)
{
return $this->hookManager->getHooks($this->names, $hooks, $annotationData);
}
}
PK }?V~u# # " src/Hooks/AlterResultInterface.phpnu W+A reflection = new \ReflectionMethod($classNameOrInstance, $methodName);
$this->methodName = $methodName;
$this->arguments = new DefaultsWithDescriptions();
$this->options = new DefaultsWithDescriptions();
// If the cache came from a newer version, ignore it and
// regenerate the cached information.
if (!empty($cache) && CommandInfoDeserializer::isValidSerializedData($cache) && !$this->cachedFileIsModified($cache)) {
$deserializer = new CommandInfoDeserializer();
$deserializer->constructFromCache($this, $cache);
$this->docBlockIsParsed = true;
} else {
$this->constructFromClassAndMethod($classNameOrInstance, $methodName);
}
}
public static function create($classNameOrInstance, $methodName)
{
return new self($classNameOrInstance, $methodName);
}
public static function deserialize($cache)
{
$cache = (array)$cache;
return new self($cache['class'], $cache['method_name'], $cache);
}
public function cachedFileIsModified($cache)
{
$path = $this->reflection->getFileName();
return filemtime($path) != $cache['mtime'];
}
protected function constructFromClassAndMethod($classNameOrInstance, $methodName)
{
$this->otherAnnotations = new AnnotationData();
// Set up a default name for the command from the method name.
// This can be overridden via @command or @name annotations.
$this->name = $this->convertName($methodName);
$this->options = new DefaultsWithDescriptions($this->determineOptionsFromParameters(), false);
$this->arguments = $this->determineAgumentClassifications();
}
/**
* Recover the method name provided to the constructor.
*
* @return string
*/
public function getMethodName()
{
return $this->methodName;
}
/**
* Return the primary name for this command.
*
* @return string
*/
public function getName()
{
$this->parseDocBlock();
return $this->name;
}
/**
* Set the primary name for this command.
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Return whether or not this method represents a valid command
* or hook.
*/
public function valid()
{
return !empty($this->name);
}
/**
* If higher-level code decides that this CommandInfo is not interesting
* or useful (if it is not a command method or a hook method), then
* we will mark it as invalid to prevent it from being created as a command.
* We still cache a placeholder record for invalid methods, so that we
* do not need to re-parse the method again later simply to determine that
* it is invalid.
*/
public function invalidate()
{
$this->name = '';
}
public function getReturnType()
{
$this->parseDocBlock();
return $this->returnType;
}
public function setReturnType($returnType)
{
$this->returnType = $returnType;
return $this;
}
/**
* Get any annotations included in the docblock comment for the
* implementation method of this command that are not already
* handled by the primary methods of this class.
*
* @return AnnotationData
*/
public function getRawAnnotations()
{
$this->parseDocBlock();
return $this->otherAnnotations;
}
/**
* Replace the annotation data.
*/
public function replaceRawAnnotations($annotationData)
{
$this->otherAnnotations = new AnnotationData((array) $annotationData);
return $this;
}
/**
* Get any annotations included in the docblock comment,
* also including default values such as @command. We add
* in the default @command annotation late, and only in a
* copy of the annotation data because we use the existance
* of a @command to indicate that this CommandInfo is
* a command, and not a hook or anything else.
*
* @return AnnotationData
*/
public function getAnnotations()
{
// Also provide the path to the commandfile that these annotations
// were pulled from and the classname of that file.
$path = $this->reflection->getFileName();
$className = $this->reflection->getDeclaringClass()->getName();
return new AnnotationData(
$this->getRawAnnotations()->getArrayCopy() +
[
'command' => $this->getName(),
'_path' => $path,
'_classname' => $className,
]
);
}
/**
* Return a specific named annotation for this command as a list.
*
* @param string $name The name of the annotation.
* @return array|null
*/
public function getAnnotationList($name)
{
// hasAnnotation parses the docblock
if (!$this->hasAnnotation($name)) {
return null;
}
return $this->otherAnnotations->getList($name);
;
}
/**
* Return a specific named annotation for this command as a string.
*
* @param string $name The name of the annotation.
* @return string|null
*/
public function getAnnotation($name)
{
// hasAnnotation parses the docblock
if (!$this->hasAnnotation($name)) {
return null;
}
return $this->otherAnnotations->get($name);
}
/**
* Check to see if the specified annotation exists for this command.
*
* @param string $annotation The name of the annotation.
* @return boolean
*/
public function hasAnnotation($annotation)
{
$this->parseDocBlock();
return isset($this->otherAnnotations[$annotation]);
}
/**
* Save any tag that we do not explicitly recognize in the
* 'otherAnnotations' map.
*/
public function addAnnotation($name, $content)
{
// Convert to an array and merge if there are multiple
// instances of the same annotation defined.
if (isset($this->otherAnnotations[$name])) {
$content = array_merge((array) $this->otherAnnotations[$name], (array)$content);
}
$this->otherAnnotations[$name] = $content;
}
/**
* Remove an annotation that was previoudly set.
*/
public function removeAnnotation($name)
{
unset($this->otherAnnotations[$name]);
}
/**
* Get the synopsis of the command (~first line).
*
* @return string
*/
public function getDescription()
{
$this->parseDocBlock();
return $this->description;
}
/**
* Set the command description.
*
* @param string $description The description to set.
*/
public function setDescription($description)
{
$this->description = str_replace("\n", ' ', $description);
return $this;
}
/**
* Get the help text of the command (the description)
*/
public function getHelp()
{
$this->parseDocBlock();
return $this->help;
}
/**
* Set the help text for this command.
*
* @param string $help The help text.
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Return the list of aliases for this command.
* @return string[]
*/
public function getAliases()
{
$this->parseDocBlock();
return $this->aliases;
}
/**
* Set aliases that can be used in place of the command's primary name.
*
* @param string|string[] $aliases
*/
public function setAliases($aliases)
{
if (is_string($aliases)) {
$aliases = explode(',', static::convertListToCommaSeparated($aliases));
}
$this->aliases = array_filter($aliases);
return $this;
}
/**
* Get hidden status for the command.
* @return bool
*/
public function getHidden()
{
$this->parseDocBlock();
return $this->hasAnnotation('hidden');
}
/**
* Set hidden status. List command omits hidden commands.
*
* @param bool $hidden
*/
public function setHidden($hidden)
{
$this->hidden = $hidden;
return $this;
}
/**
* Return the examples for this command. This is @usage instead of
* @example because the later is defined by the phpdoc standard to
* be example method calls.
*
* @return string[]
*/
public function getExampleUsages()
{
$this->parseDocBlock();
return $this->exampleUsage;
}
/**
* Add an example usage for this command.
*
* @param string $usage An example of the command, including the command
* name and all of its example arguments and options.
* @param string $description An explanation of what the example does.
*/
public function setExampleUsage($usage, $description)
{
$this->exampleUsage[$usage] = $description;
return $this;
}
/**
* Overwrite all example usages
*/
public function replaceExampleUsages($usages)
{
$this->exampleUsage = $usages;
return $this;
}
/**
* Return the topics for this command.
*
* @return string[]
*/
public function getTopics()
{
if (!$this->hasAnnotation('topics')) {
return [];
}
$topics = $this->getAnnotation('topics');
return explode(',', trim($topics));
}
/**
* Return the list of refleaction parameters.
*
* @return ReflectionParameter[]
*/
public function getParameters()
{
return $this->reflection->getParameters();
}
/**
* Descriptions of commandline arguements for this command.
*
* @return DefaultsWithDescriptions
*/
public function arguments()
{
return $this->arguments;
}
/**
* Descriptions of commandline options for this command.
*
* @return DefaultsWithDescriptions
*/
public function options()
{
return $this->options;
}
/**
* Get the inputOptions for the options associated with this CommandInfo
* object, e.g. via @option annotations, or from
* $options = ['someoption' => 'defaultvalue'] in the command method
* parameter list.
*
* @return InputOption[]
*/
public function inputOptions()
{
if (!isset($this->inputOptions)) {
$this->inputOptions = $this->createInputOptions();
}
return $this->inputOptions;
}
protected function addImplicitNoOptions()
{
$opts = $this->options()->getValues();
foreach ($opts as $name => $defaultValue) {
if ($defaultValue === true) {
$key = 'no-' . $name;
if (!array_key_exists($key, $opts)) {
$description = "Negate --$name option.";
$this->options()->add($key, $description, false);
}
}
}
}
protected function createInputOptions()
{
$explicitOptions = [];
$this->addImplicitNoOptions();
$opts = $this->options()->getValues();
foreach ($opts as $name => $defaultValue) {
$description = $this->options()->getDescription($name);
$fullName = $name;
$shortcut = '';
if (strpos($name, '|')) {
list($fullName, $shortcut) = explode('|', $name, 2);
}
// Treat the following two cases identically:
// - 'foo' => InputOption::VALUE_OPTIONAL
// - 'foo' => null
// The first form is preferred, but we will convert the value
// to 'null' for storage as the option default value.
if ($defaultValue === InputOption::VALUE_OPTIONAL) {
$defaultValue = null;
}
if ($defaultValue === false) {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_NONE, $description);
} elseif ($defaultValue === InputOption::VALUE_REQUIRED) {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_REQUIRED, $description);
} elseif (is_array($defaultValue)) {
$optionality = count($defaultValue) ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
$explicitOptions[$fullName] = new InputOption(
$fullName,
$shortcut,
InputOption::VALUE_IS_ARRAY | $optionality,
$description,
count($defaultValue) ? $defaultValue : null
);
} else {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $defaultValue);
}
}
return $explicitOptions;
}
/**
* An option might have a name such as 'silent|s'. In this
* instance, we will allow the @option or @default tag to
* reference the option only by name (e.g. 'silent' or 's'
* instead of 'silent|s').
*
* @param string $optionName
* @return string
*/
public function findMatchingOption($optionName)
{
// Exit fast if there's an exact match
if ($this->options->exists($optionName)) {
return $optionName;
}
$existingOptionName = $this->findExistingOption($optionName);
if (isset($existingOptionName)) {
return $existingOptionName;
}
return $this->findOptionAmongAlternatives($optionName);
}
/**
* @param string $optionName
* @return string
*/
protected function findOptionAmongAlternatives($optionName)
{
// Check the other direction: if the annotation contains @silent|s
// and the options array has 'silent|s'.
$checkMatching = explode('|', $optionName);
if (count($checkMatching) > 1) {
foreach ($checkMatching as $checkName) {
if ($this->options->exists($checkName)) {
$this->options->rename($checkName, $optionName);
return $optionName;
}
}
}
return $optionName;
}
/**
* @param string $optionName
* @return string|null
*/
protected function findExistingOption($optionName)
{
// Check to see if we can find the option name in an existing option,
// e.g. if the options array has 'silent|s' => false, and the annotation
// is @silent.
foreach ($this->options()->getValues() as $name => $default) {
if (in_array($optionName, explode('|', $name))) {
return $name;
}
}
}
/**
* Examine the parameters of the method for this command, and
* build a list of commandline arguements for them.
*
* @return array
*/
protected function determineAgumentClassifications()
{
$result = new DefaultsWithDescriptions();
$params = $this->reflection->getParameters();
$optionsFromParameters = $this->determineOptionsFromParameters();
if ($this->lastParameterIsOptionsArray()) {
array_pop($params);
}
foreach ($params as $param) {
$this->addParameterToResult($result, $param);
}
return $result;
}
/**
* Examine the provided parameter, and determine whether it
* is a parameter that will be filled in with a positional
* commandline argument.
*/
protected function addParameterToResult($result, $param)
{
// Commandline arguments must be strings, so ignore any
// parameter that is typehinted to any non-primative class.
if ($param->getClass() != null) {
return;
}
$result->add($param->name);
if ($param->isDefaultValueAvailable()) {
$defaultValue = $param->getDefaultValue();
if (!$this->isAssoc($defaultValue)) {
$result->setDefaultValue($param->name, $defaultValue);
}
} elseif ($param->isArray()) {
$result->setDefaultValue($param->name, []);
}
}
/**
* Examine the parameters of the method for this command, and determine
* the disposition of the options from them.
*
* @return array
*/
protected function determineOptionsFromParameters()
{
$params = $this->reflection->getParameters();
if (empty($params)) {
return [];
}
$param = end($params);
if (!$param->isDefaultValueAvailable()) {
return [];
}
if (!$this->isAssoc($param->getDefaultValue())) {
return [];
}
return $param->getDefaultValue();
}
/**
* Determine if the last argument contains $options.
*
* Two forms indicate options:
* - $options = []
* - $options = ['flag' => 'default-value']
*
* Any other form, including `array $foo`, is not options.
*/
protected function lastParameterIsOptionsArray()
{
$params = $this->reflection->getParameters();
if (empty($params)) {
return [];
}
$param = end($params);
if (!$param->isDefaultValueAvailable()) {
return [];
}
return is_array($param->getDefaultValue());
}
/**
* Helper; determine if an array is associative or not. An array
* is not associative if its keys are numeric, and numbered sequentially
* from zero. All other arrays are considered to be associative.
*
* @param array $arr The array
* @return boolean
*/
protected function isAssoc($arr)
{
if (!is_array($arr)) {
return false;
}
return array_keys($arr) !== range(0, count($arr) - 1);
}
/**
* Convert from a method name to the corresponding command name. A
* method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will
* become 'foo:bar-baz-boz'.
*
* @param string $camel method name.
* @return string
*/
protected function convertName($camel)
{
$splitter="-";
$camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
$camel = preg_replace("/$splitter/", ':', $camel, 1);
return strtolower($camel);
}
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
protected function parseDocBlock()
{
if (!$this->docBlockIsParsed) {
// The parse function will insert data from the provided method
// into this object, using our accessors.
CommandDocBlockParserFactory::parse($this, $this->reflection);
$this->docBlockIsParsed = true;
}
}
/**
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
* convert the data into the last of these forms.
*/
protected static function convertListToCommaSeparated($text)
{
return preg_replace('#[ \t\n\r,]+#', ',', $text);
}
}
PK }?Vdm m ' src/Parser/DefaultsWithDescriptions.phpnu W+A values = $values;
$this->hasDefault = array_filter($this->values, function ($value) {
return isset($value);
});
$this->descriptions = [];
$this->defaultDefault = $defaultDefault;
}
/**
* Return just the key : default values mapping
*
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Return true if this set of options is empty
*
* @return
*/
public function isEmpty()
{
return empty($this->values);
}
/**
* Check to see whether the speicifed key exists in the collection.
*
* @param string $key
* @return boolean
*/
public function exists($key)
{
return array_key_exists($key, $this->values);
}
/**
* Get the value of one entry.
*
* @param string $key The key of the item.
* @return string
*/
public function get($key)
{
if (array_key_exists($key, $this->values)) {
return $this->values[$key];
}
return $this->defaultDefault;
}
/**
* Get the description of one entry.
*
* @param string $key The key of the item.
* @return string
*/
public function getDescription($key)
{
if (array_key_exists($key, $this->descriptions)) {
return $this->descriptions[$key];
}
return '';
}
/**
* Add another argument to this command.
*
* @param string $key Name of the argument.
* @param string $description Help text for the argument.
* @param mixed $defaultValue The default value for the argument.
*/
public function add($key, $description = '', $defaultValue = null)
{
if (!$this->exists($key) || isset($defaultValue)) {
$this->values[$key] = isset($defaultValue) ? $defaultValue : $this->defaultDefault;
}
unset($this->descriptions[$key]);
if (!empty($description)) {
$this->descriptions[$key] = $description;
}
}
/**
* Change the default value of an entry.
*
* @param string $key
* @param mixed $defaultValue
*/
public function setDefaultValue($key, $defaultValue)
{
$this->values[$key] = $defaultValue;
$this->hasDefault[$key] = true;
return $this;
}
/**
* Check to see if the named argument definitively has a default value.
*
* @param string $key
* @return bool
*/
public function hasDefault($key)
{
return array_key_exists($key, $this->hasDefault);
}
/**
* Remove an entry
*
* @param string $key The entry to remove
*/
public function clear($key)
{
unset($this->values[$key]);
unset($this->descriptions[$key]);
}
/**
* Rename an existing option to something else.
*/
public function rename($oldName, $newName)
{
$this->add($newName, $this->getDescription($oldName), $this->get($oldName));
$this->clear($oldName);
}
}
PK }?VXY $ src/Parser/CommandInfoSerializer.phpnu W+A getAnnotations();
$path = $allAnnotations['_path'];
$className = $allAnnotations['_classname'];
// Include the minimum information for command info (including placeholder records)
$info = [
'schema' => CommandInfo::SERIALIZATION_SCHEMA_VERSION,
'class' => $className,
'method_name' => $commandInfo->getMethodName(),
'mtime' => filemtime($path),
];
// If this is a valid method / hook, then add more information.
if ($commandInfo->valid()) {
$info += [
'name' => $commandInfo->getName(),
'description' => $commandInfo->getDescription(),
'help' => $commandInfo->getHelp(),
'aliases' => $commandInfo->getAliases(),
'annotations' => $commandInfo->getRawAnnotations()->getArrayCopy(),
'example_usages' => $commandInfo->getExampleUsages(),
'return_type' => $commandInfo->getReturnType(),
];
$info['arguments'] = $this->serializeDefaultsWithDescriptions($commandInfo->arguments());
$info['options'] = $this->serializeDefaultsWithDescriptions($commandInfo->options());
}
return $info;
}
protected function serializeDefaultsWithDescriptions(DefaultsWithDescriptions $defaults)
{
$result = [];
foreach ($defaults->getValues() as $key => $val) {
$result[$key] = [
'description' => $defaults->getDescription($key),
];
if ($defaults->hasDefault($key)) {
$result[$key]['default'] = $val;
}
}
return $result;
}
}
PK }?V1V!
0 src/Parser/Internal/FullyQualifiedClassCache.phpnu W+A primeCache($filename, $className);
return $this->cached($filename, $className);
}
protected function cached($filename, $className)
{
return isset($this->classCache[$filename][$className]) ? $this->classCache[$filename][$className] : $className;
}
protected function primeCache($filename, $className)
{
// If the cache has already been primed, do no further work
if (isset($this->namespaceCache[$filename])) {
return false;
}
$handle = fopen($filename, "r");
if (!$handle) {
return false;
}
$namespaceName = $this->primeNamespaceCache($filename, $handle);
$this->primeUseCache($filename, $handle);
// If there is no 'use' statement for the className, then
// generate an effective classname from the namespace
if (!isset($this->classCache[$filename][$className])) {
$this->classCache[$filename][$className] = $namespaceName . '\\' . $className;
}
fclose($handle);
}
protected function primeNamespaceCache($filename, $handle)
{
$namespaceName = $this->readNamespace($handle);
if (!$namespaceName) {
return false;
}
$this->namespaceCache[$filename] = $namespaceName;
return $namespaceName;
}
protected function primeUseCache($filename, $handle)
{
$usedClasses = $this->readUseStatements($handle);
if (empty($usedClasses)) {
return false;
}
$this->classCache[$filename] = $usedClasses;
}
protected function readNamespace($handle)
{
$namespaceRegex = '#^\s*namespace\s+#';
$line = $this->readNextRelevantLine($handle);
if (!$line || !preg_match($namespaceRegex, $line)) {
return false;
}
$namespaceName = preg_replace($namespaceRegex, '', $line);
$namespaceName = rtrim($namespaceName, ';');
return $namespaceName;
}
protected function readUseStatements($handle)
{
$useRegex = '#^\s*use\s+#';
$result = [];
while (true) {
$line = $this->readNextRelevantLine($handle);
if (!$line || !preg_match($useRegex, $line)) {
return $result;
}
$usedClass = preg_replace($useRegex, '', $line);
$usedClass = rtrim($usedClass, ';');
$unqualifiedClass = preg_replace('#.*\\\\#', '', $usedClass);
// If this is an aliased class, 'use \Foo\Bar as Baz', then adjust
if (strpos($usedClass, ' as ')) {
$unqualifiedClass = preg_replace('#.*\sas\s+#', '', $usedClass);
$usedClass = preg_replace('#\s+as\s+#', '', $usedClass);
}
$result[$unqualifiedClass] = $usedClass;
}
}
protected function readNextRelevantLine($handle)
{
while (($line = fgets($handle)) !== false) {
if (preg_match('#^\s*\w#', $line)) {
return trim($line);
}
}
return false;
}
}
PK }?Vѝ/ / 4 src/Parser/Internal/CommandDocBlockParserFactory.phpnu W+A parse();
}
private static function create(CommandInfo $commandInfo, \ReflectionMethod $reflection)
{
return new BespokeDocBlockParser($commandInfo, $reflection);
}
}
PK }?VM7 " src/Parser/Internal/TagFactory.phpnu W+A current = null;
$this->tags = [];
}
public function parseLine($line)
{
if (DocblockTag::isTag($line)) {
return $this->createTag($line);
}
if (empty($line)) {
return $this->storeCurrentTag();
}
return $this->accumulateContent($line);
}
public function getTags()
{
$this->storeCurrentTag();
return $this->tags;
}
protected function createTag($line)
{
DocblockTag::splitTagAndContent($line, $matches);
$this->storeCurrentTag();
$this->current = new DocblockTag($matches['tag'], $matches['description']);
return true;
}
protected function storeCurrentTag()
{
if (!$this->current) {
return false;
}
$this->tags[] = $this->current;
$this->current = false;
return true;
}
protected function accumulateContent($line)
{
if (!$this->current) {
return false;
}
$this->current->appendContent($line);
return true;
}
}
PK }?V
D( ( - src/Parser/Internal/BespokeDocBlockParser.phpnu W+A 'processCommandTag',
'name' => 'processCommandTag',
'arg' => 'processArgumentTag',
'param' => 'processArgumentTag',
'return' => 'processReturnTag',
'option' => 'processOptionTag',
'default' => 'processDefaultTag',
'aliases' => 'processAliases',
'usage' => 'processUsageTag',
'description' => 'processAlternateDescriptionTag',
'desc' => 'processAlternateDescriptionTag',
];
public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection, $fqcnCache = null)
{
$this->commandInfo = $commandInfo;
$this->reflection = $reflection;
$this->fqcnCache = $fqcnCache ?: new FullyQualifiedClassCache();
}
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
public function parse()
{
$doc = $this->reflection->getDocComment();
$this->parseDocBlock($doc);
}
/**
* Save any tag that we do not explicitly recognize in the
* 'otherAnnotations' map.
*/
protected function processGenericTag($tag)
{
$this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent());
}
/**
* Set the name of the command from a @command or @name annotation.
*/
protected function processCommandTag($tag)
{
if (!$tag->hasWordAndDescription($matches)) {
throw new \Exception('Could not determine command name from tag ' . (string)$tag);
}
$commandName = $matches['word'];
$this->commandInfo->setName($commandName);
// We also store the name in the 'other annotations' so that is is
// possible to determine if the method had a @command annotation.
$this->commandInfo->addAnnotation($tag->getTag(), $commandName);
}
/**
* The @description and @desc annotations may be used in
* place of the synopsis (which we call 'description').
* This is discouraged.
*
* @deprecated
*/
protected function processAlternateDescriptionTag($tag)
{
$this->commandInfo->setDescription($tag->getContent());
}
/**
* Store the data from a @arg annotation in our argument descriptions.
*/
protected function processArgumentTag($tag)
{
if (!$tag->hasVariable($matches)) {
throw new \Exception('Could not determine argument name from tag ' . (string)$tag);
}
if ($matches['variable'] == $this->optionParamName()) {
return;
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $matches['variable'], $matches['description']);
}
/**
* Store the data from an @option annotation in our option descriptions.
*/
protected function processOptionTag($tag)
{
if (!$tag->hasVariable($matches)) {
throw new \Exception('Could not determine option name from tag ' . (string)$tag);
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $matches['variable'], $matches['description']);
}
protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $name, $description)
{
$variableName = $this->commandInfo->findMatchingOption($name);
$description = static::removeLineBreaks($description);
$set->add($variableName, $description);
}
/**
* Store the data from a @default annotation in our argument or option store,
* as appropriate.
*/
protected function processDefaultTag($tag)
{
if (!$tag->hasVariable($matches)) {
throw new \Exception('Could not determine parameter name for default value from tag ' . (string)$tag);
}
$variableName = $matches['variable'];
$defaultValue = $this->interpretDefaultValue($matches['description']);
if ($this->commandInfo->arguments()->exists($variableName)) {
$this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue);
return;
}
$variableName = $this->commandInfo->findMatchingOption($variableName);
if ($this->commandInfo->options()->exists($variableName)) {
$this->commandInfo->options()->setDefaultValue($variableName, $defaultValue);
}
}
/**
* Store the data from a @usage annotation in our example usage list.
*/
protected function processUsageTag($tag)
{
$lines = explode("\n", $tag->getContent());
$usage = trim(array_shift($lines));
$description = static::removeLineBreaks(implode("\n", array_map(function ($line) {
return trim($line);
}, $lines)));
$this->commandInfo->setExampleUsage($usage, $description);
}
/**
* Process the comma-separated list of aliases
*/
protected function processAliases($tag)
{
$this->commandInfo->setAliases((string)$tag->getContent());
}
/**
* Store the data from a @return annotation in our argument descriptions.
*/
protected function processReturnTag($tag)
{
// The return type might be a variable -- '$this'. It will
// usually be a type, like RowsOfFields, or \Namespace\RowsOfFields.
if (!$tag->hasVariableAndDescription($matches)) {
throw new \Exception('Could not determine return type from tag ' . (string)$tag);
}
// Look at namespace and `use` statments to make returnType a fqdn
$returnType = $matches['variable'];
$returnType = $this->findFullyQualifiedClass($returnType);
$this->commandInfo->setReturnType($returnType);
}
protected function findFullyQualifiedClass($className)
{
if (strpos($className, '\\') !== false) {
return $className;
}
return $this->fqcnCache->qualify($this->reflection->getFileName(), $className);
}
private function parseDocBlock($doc)
{
// Remove the leading /** and the trailing */
$doc = preg_replace('#^\s*/\*+\s*#', '', $doc);
$doc = preg_replace('#\s*\*+/\s*#', '', $doc);
// Nothing left? Exit.
if (empty($doc)) {
return;
}
$tagFactory = new TagFactory();
$lines = [];
foreach (explode("\n", $doc) as $row) {
// Remove trailing whitespace and leading space + '*'s
$row = rtrim($row);
$row = preg_replace('#^[ \t]*\**#', '', $row);
if (!$tagFactory->parseLine($row)) {
$lines[] = $row;
}
}
$this->processDescriptionAndHelp($lines);
$this->processAllTags($tagFactory->getTags());
}
protected function processDescriptionAndHelp($lines)
{
// Trim all of the lines individually.
$lines =
array_map(
function ($line) {
return trim($line);
},
$lines
);
// Everything up to the first blank line goes in the description.
$description = array_shift($lines);
while ($this->nextLineIsNotEmpty($lines)) {
$description .= ' ' . array_shift($lines);
}
// Everything else goes in the help.
$help = trim(implode("\n", $lines));
$this->commandInfo->setDescription($description);
$this->commandInfo->setHelp($help);
}
protected function nextLineIsNotEmpty($lines)
{
if (empty($lines)) {
return false;
}
$nextLine = trim($lines[0]);
return !empty($nextLine);
}
protected function processAllTags($tags)
{
// Iterate over all of the tags, and process them as necessary.
foreach ($tags as $tag) {
$processFn = [$this, 'processGenericTag'];
if (array_key_exists($tag->getTag(), $this->tagProcessors)) {
$processFn = [$this, $this->tagProcessors[$tag->getTag()]];
}
$processFn($tag);
}
}
protected function lastParameterName()
{
$params = $this->commandInfo->getParameters();
$param = end($params);
if (!$param) {
return '';
}
return $param->name;
}
/**
* Return the name of the last parameter if it holds the options.
*/
public function optionParamName()
{
// Remember the name of the last parameter, if it holds the options.
// We will use this information to ignore @param annotations for the options.
if (!isset($this->optionParamName)) {
$this->optionParamName = '';
$options = $this->commandInfo->options();
if (!$options->isEmpty()) {
$this->optionParamName = $this->lastParameterName();
}
}
return $this->optionParamName;
}
protected function interpretDefaultValue($defaultValue)
{
$defaults = [
'null' => null,
'true' => true,
'false' => false,
"''" => '',
'[]' => [],
];
foreach ($defaults as $defaultName => $defaultTypedValue) {
if ($defaultValue == $defaultName) {
return $defaultTypedValue;
}
}
return $defaultValue;
}
/**
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
* convert the data into the last of these forms.
*/
protected static function convertListToCommaSeparated($text)
{
return preg_replace('#[ \t\n\r,]+#', ',', $text);
}
/**
* Take a multiline description and convert it into a single
* long unbroken line.
*/
protected static function removeLineBreaks($text)
{
return trim(preg_replace('#[ \t\n\r]+#', ' ', $text));
}
}
PK }?V,& # src/Parser/Internal/DocblockTag.phpnu W+A [^\s$]+)[\s]*';
const VARIABLE_REGEX = '\\$(?P[^\s$]+)[\s]*';
const VARIABLE_OR_WORD_REGEX = '\\$?(?P[^\s$]+)[\s]*';
const TYPE_REGEX = '(?P[^\s$]+)[\s]*';
const WORD_REGEX = '(?P[^\s$]+)[\s]*';
const DESCRIPTION_REGEX = '(?P.*)';
const IS_TAG_REGEX = '/^[*\s]*@/';
/**
* Check if the provided string begins with a tag
* @param string $subject
* @return bool
*/
public static function isTag($subject)
{
return preg_match(self::IS_TAG_REGEX, $subject);
}
/**
* Use a regular expression to separate the tag from the content.
*
* @param string $subject
* @param string[] &$matches Sets $matches['tag'] and $matches['description']
* @return bool
*/
public static function splitTagAndContent($subject, &$matches)
{
$regex = '/' . self::TAG_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $subject, $matches);
}
/**
* DockblockTag constructor
*/
public function __construct($tag, $content = null)
{
$this->tag = $tag;
$this->content = $content;
}
/**
* Add more content onto a tag during parsing.
*/
public function appendContent($line)
{
$this->content .= "\n$line";
}
/**
* Return the tag - e.g. "@foo description" returns 'foo'
*
* @return string
*/
public function getTag()
{
return $this->tag;
}
/**
* Return the content portion of the tag - e.g. "@foo bar baz boz" returns
* "bar baz boz"
*
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* Convert tag back into a string.
*/
public function __toString()
{
return '@' . $this->getTag() . ' ' . $this->getContent();
}
/**
* Determine if tag is one of:
* - "@tag variable description"
* - "@tag $variable description"
* - "@tag type $variable description"
*
* @param string $subject
* @param string[] &$matches Sets $matches['variable'] and
* $matches['description']; might set $matches['type'].
* @return bool
*/
public function hasVariable(&$matches)
{
return
$this->hasTypeVariableAndDescription($matches) ||
$this->hasVariableAndDescription($matches);
}
/**
* Determine if tag is "@tag $variable description"
* @param string $subject
* @param string[] &$matches Sets $matches['variable'] and
* $matches['description']
* @return bool
*/
public function hasVariableAndDescription(&$matches)
{
$regex = '/^\s*' . self::VARIABLE_OR_WORD_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $this->getContent(), $matches);
}
/**
* Determine if tag is "@tag type $variable description"
*
* @param string $subject
* @param string[] &$matches Sets $matches['variable'],
* $matches['description'] and $matches['type'].
* @return bool
*/
public function hasTypeVariableAndDescription(&$matches)
{
$regex = '/^\s*' . self::TYPE_REGEX . self::VARIABLE_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $this->getContent(), $matches);
}
/**
* Determine if tag is "@tag word description"
* @param string $subject
* @param string[] &$matches Sets $matches['word'] and
* $matches['description']
* @return bool
*/
public function hasWordAndDescription(&$matches)
{
$regex = '/^\s*' . self::WORD_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $this->getContent(), $matches);
}
}
PK }?V