diff --git a/composer.json b/composer.json index c7c11f86..cc5bcf79 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,6 @@ "license": "MIT", "require": { "php": "^7.3 || ^8.0", - "my127/my127": "dev-master", "symfony/config": "^4.4", "symfony/console": "^4.4", "symfony/dependency-injection": "^4.4", @@ -22,11 +21,14 @@ "phpstan/phpstan": "^1.2" }, "autoload": { - "psr-4": {"my127\\Workspace\\": "src/"} + "psr-4": { + "my127\\Workspace\\": "src/" + } }, "autoload-dev": { "psr-4": { - "my127\\Workspace\\Tests\\": "tests/" + "my127\\Workspace\\Tests\\": "tests/", + "my127\\Console\\Tests\\": "console/tests/" } }, "scripts": { diff --git a/composer.lock b/composer.lock index 35f7609c..c12e30e7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,76 +4,24 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2a0b4c353feaaca1bc9e37fff4914df5", + "content-hash": "ed1ddfbf0a6976118c2ac3aa81768847", "packages": [ - { - "name": "my127/my127", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/my127/php-library.git", - "reference": "0f662a526cacef2e5f20dd51092e640a1f5ca8aa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/my127/php-library/zipball/0f662a526cacef2e5f20dd51092e640a1f5ca8aa", - "reference": "0f662a526cacef2e5f20dd51092e640a1f5ca8aa", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "symfony/event-dispatcher": "^4.0" - }, - "replace": { - "my127/console": "self.version", - "my127/fsm": "self.version" - }, - "require-dev": { - "phpspec/phpspec": "^7.0", - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.5" - }, - "default-branch": true, - "type": "library", - "autoload": { - "psr-4": { - "my127\\Console\\": "src/Console/src/", - "my127\\FSM\\": "src/FSM/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David Cole", - "email": "dcole@my127.io" - } - ], - "description": "my127 PHP Library", - "support": { - "issues": "https://github.com/my127/php-library/issues", - "source": "https://github.com/my127/php-library/tree/master" - }, - "time": "2021-11-03T09:11:24+00:00" - }, { "name": "psr/cache", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "213f9dbc5b9bfbc4f8db86d2838dc968752ce13b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/213f9dbc5b9bfbc4f8db86d2838dc968752ce13b", + "reference": "213f9dbc5b9bfbc4f8db86d2838dc968752ce13b", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -93,7 +41,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -103,26 +51,26 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/2.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:23:37+00:00" }, { "name": "psr/container", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", "autoload": { @@ -151,36 +99,36 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/log", - "version": "1.1.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -201,22 +149,22 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/2.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:41:46+00:00" }, { "name": "symfony/cache", - "version": "v5.3.12", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "fe05bcb21c1287401d96d066ada7ed881418c6a1" + "reference": "c0718d0e01ac14251a45cc9c8b93716ec41ae64b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/fe05bcb21c1287401d96d066ada7ed881418c6a1", - "reference": "fe05bcb21c1287401d96d066ada7ed881418c6a1", + "url": "https://api.github.com/repos/symfony/cache/zipball/c0718d0e01ac14251a45cc9c8b93716ec41ae64b", + "reference": "c0718d0e01ac14251a45cc9c8b93716ec41ae64b", "shasum": "" }, "require": { @@ -224,14 +172,14 @@ "psr/cache": "^1.0|^2.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0" + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "conflict": { - "doctrine/dbal": "<2.10", + "doctrine/dbal": "<2.13.1", "symfony/dependency-injection": "<4.4", "symfony/http-kernel": "<4.4", "symfony/var-dumper": "<4.4" @@ -244,15 +192,15 @@ "require-dev": { "cache/integration-tests": "dev-master", "doctrine/cache": "^1.6|^2.0", - "doctrine/dbal": "^2.10|^3.0", + "doctrine/dbal": "^2.13.1|^3.0", "predis/predis": "^1.1", "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/filesystem": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/messenger": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0" + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { @@ -284,7 +232,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.3.12" + "source": "https://github.com/symfony/cache/tree/v5.4.6" }, "funding": [ { @@ -300,7 +248,7 @@ "type": "tidelift" } ], - "time": "2021-11-23T18:33:50+00:00" + "time": "2022-03-02T12:56:28+00:00" }, { "name": "symfony/cache-contracts", @@ -383,16 +331,16 @@ }, { "name": "symfony/config", - "version": "v4.4.34", + "version": "v4.4.37", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "e99b65a18faa34fde57078095c39a1bc91a22492" + "reference": "e8c2d2c951ddedecb6d28954d336cb7d2e852d0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/e99b65a18faa34fde57078095c39a1bc91a22492", - "reference": "e99b65a18faa34fde57078095c39a1bc91a22492", + "url": "https://api.github.com/repos/symfony/config/zipball/e8c2d2c951ddedecb6d28954d336cb7d2e852d0e", + "reference": "e8c2d2c951ddedecb6d28954d336cb7d2e852d0e", "shasum": "" }, "require": { @@ -441,7 +389,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v4.4.34" + "source": "https://github.com/symfony/config/tree/v4.4.37" }, "funding": [ { @@ -457,20 +405,20 @@ "type": "tidelift" } ], - "time": "2021-10-29T15:43:26+00:00" + "time": "2022-01-03T09:46:22+00:00" }, { "name": "symfony/console", - "version": "v4.4.34", + "version": "v4.4.38", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "329b3a75cc6b16d435ba1b1a41df54a53382a3f0" + "reference": "5a50085bf5460f0c0d60a50b58388c1249826b8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/329b3a75cc6b16d435ba1b1a41df54a53382a3f0", - "reference": "329b3a75cc6b16d435ba1b1a41df54a53382a3f0", + "url": "https://api.github.com/repos/symfony/console/zipball/5a50085bf5460f0c0d60a50b58388c1249826b8a", + "reference": "5a50085bf5460f0c0d60a50b58388c1249826b8a", "shasum": "" }, "require": { @@ -531,7 +479,7 @@ "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/console/tree/v4.4.34" + "source": "https://github.com/symfony/console/tree/v4.4.38" }, "funding": [ { @@ -547,20 +495,20 @@ "type": "tidelift" } ], - "time": "2021-11-04T12:23:33+00:00" + "time": "2022-01-30T21:23:57+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.4.34", + "version": "v4.4.39", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "117d7f132ed7efbd535ec947709d49bec1b9d24b" + "reference": "5d0fbcdb9317864b2bd9e49d570d88ae512cadf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/117d7f132ed7efbd535ec947709d49bec1b9d24b", - "reference": "117d7f132ed7efbd535ec947709d49bec1b9d24b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/5d0fbcdb9317864b2bd9e49d570d88ae512cadf3", + "reference": "5d0fbcdb9317864b2bd9e49d570d88ae512cadf3", "shasum": "" }, "require": { @@ -573,7 +521,7 @@ "symfony/config": "<4.3|>=5.0", "symfony/finder": "<3.4", "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" + "symfony/yaml": "<4.4.26" }, "provide": { "psr/container-implementation": "1.0", @@ -582,7 +530,7 @@ "require-dev": { "symfony/config": "^4.3", "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/yaml": "^4.4|^5.0" + "symfony/yaml": "^4.4.26|^5.0" }, "suggest": { "symfony/config": "", @@ -617,7 +565,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v4.4.34" + "source": "https://github.com/symfony/dependency-injection/tree/v4.4.39" }, "funding": [ { @@ -633,7 +581,7 @@ "type": "tidelift" } ], - "time": "2021-11-15T14:42:25+00:00" + "time": "2022-03-02T12:36:39+00:00" }, { "name": "symfony/deprecation-contracts", @@ -702,181 +650,18 @@ ], "time": "2021-07-12T14:48:14+00:00" }, - { - "name": "symfony/event-dispatcher", - "version": "v4.4.34", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1a024b45369c9d55d76b6b8a241bd20c9ea1cbd8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1a024b45369c9d55d76b6b8a241bd20c9ea1cbd8", - "reference": "1a024b45369c9d55d76b6b8a241bd20c9ea1cbd8", - "shasum": "" - }, - "require": { - "php": ">=7.1.3", - "symfony/event-dispatcher-contracts": "^1.1", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "symfony/dependency-injection": "<3.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/error-handler": "~3.4|~4.4", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" - }, - "type": "library", - "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.34" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-11-15T14:42:25+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.11", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "01e9a4efac0ee33a05dfdf93b346f62e7d0e998c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/01e9a4efac0ee33a05dfdf93b346f62e7d0e998c", - "reference": "01e9a4efac0ee33a05dfdf93b346f62e7d0e998c", - "shasum": "" - }, - "require": { - "php": ">=7.1.3" - }, - "suggest": { - "psr/event-dispatcher": "", - "symfony/event-dispatcher-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.1-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "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": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.11" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-03-23T15:25:38+00:00" - }, { "name": "symfony/expression-language", - "version": "v4.4.34", + "version": "v4.4.37", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "6331d834d364cce857e5a83368ce19141d5147bd" + "reference": "b25f28d2bca785d0715426bdd4af873288fe1d89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/6331d834d364cce857e5a83368ce19141d5147bd", - "reference": "6331d834d364cce857e5a83368ce19141d5147bd", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/b25f28d2bca785d0715426bdd4af873288fe1d89", + "reference": "b25f28d2bca785d0715426bdd4af873288fe1d89", "shasum": "" }, "require": { @@ -910,7 +695,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v4.4.34" + "source": "https://github.com/symfony/expression-language/tree/v4.4.37" }, "funding": [ { @@ -926,25 +711,26 @@ "type": "tidelift" } ], - "time": "2021-11-16T18:00:05+00:00" + "time": "2022-01-02T09:41:36+00:00" }, { "name": "symfony/filesystem", - "version": "v5.3.4", + "version": "v5.4.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "343f4fe324383ca46792cae728a3b6e2f708fb32" + "reference": "d53a45039974952af7f7ebc461ccdd4295e29440" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/343f4fe324383ca46792cae728a3b6e2f708fb32", - "reference": "343f4fe324383ca46792cae728a3b6e2f708fb32", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d53a45039974952af7f7ebc461ccdd4295e29440", + "reference": "d53a45039974952af7f7ebc461ccdd4295e29440", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", "symfony/polyfill-php80": "^1.16" }, "type": "library", @@ -973,7 +759,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.3.4" + "source": "https://github.com/symfony/filesystem/tree/v5.4.6" }, "funding": [ { @@ -989,20 +775,20 @@ "type": "tidelift" } ], - "time": "2021-07-21T12:40:44+00:00" + "time": "2022-03-02T12:42:23+00:00" }, { "name": "symfony/finder", - "version": "v4.4.30", + "version": "v4.4.37", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "70362f1e112280d75b30087c7598b837c1b468b6" + "reference": "b17d76d7ed179f017aad646e858c90a2771af15d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/70362f1e112280d75b30087c7598b837c1b468b6", - "reference": "70362f1e112280d75b30087c7598b837c1b468b6", + "url": "https://api.github.com/repos/symfony/finder/zipball/b17d76d7ed179f017aad646e858c90a2771af15d", + "reference": "b17d76d7ed179f017aad646e858c90a2771af15d", "shasum": "" }, "require": { @@ -1035,7 +821,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v4.4.30" + "source": "https://github.com/symfony/finder/tree/v4.4.37" }, "funding": [ { @@ -1051,11 +837,11 @@ "type": "tidelift" } ], - "time": "2021-08-04T20:31:23+00:00" + "time": "2022-01-02T09:41:36+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1117,7 +903,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" }, "funding": [ { @@ -1137,7 +923,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -1200,7 +986,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" }, "funding": [ { @@ -1220,7 +1006,7 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.24.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", @@ -1276,7 +1062,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0" }, "funding": [ { @@ -1296,16 +1082,16 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", "shasum": "" }, "require": { @@ -1322,12 +1108,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -1355,7 +1141,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0" }, "funding": [ { @@ -1371,20 +1157,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-06-05T21:20:04+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.23.1", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c", + "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c", "shasum": "" }, "require": { @@ -1401,12 +1187,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -1438,7 +1224,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0" }, "funding": [ { @@ -1454,20 +1240,20 @@ "type": "tidelift" } ], - "time": "2021-07-28T13:41:28+00:00" + "time": "2022-03-04T08:16:47+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "e66119f3de95efc359483f810c4c3e6436279436" + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", - "reference": "e66119f3de95efc359483f810c4c3e6436279436", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", "shasum": "" }, "require": { @@ -1484,12 +1270,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -1517,7 +1303,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0" }, "funding": [ { @@ -1533,7 +1319,7 @@ "type": "tidelift" } ], - "time": "2021-05-21T13:25:03+00:00" + "time": "2021-09-13T13:58:11+00:00" }, { "name": "symfony/service-contracts", @@ -1620,24 +1406,23 @@ }, { "name": "symfony/var-exporter", - "version": "v5.3.11", + "version": "v6.0.6", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "b16fcf355b810bcbccc2c6eac1d016725dbf9002" + "reference": "130229a482abf17635a685590958894dfb4b4360" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b16fcf355b810bcbccc2c6eac1d016725dbf9002", - "reference": "b16fcf355b810bcbccc2c6eac1d016725dbf9002", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/130229a482abf17635a685590958894dfb4b4360", + "reference": "130229a482abf17635a685590958894dfb4b4360", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2" }, "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9" + "symfony/var-dumper": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -1673,7 +1458,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.3.11" + "source": "https://github.com/symfony/var-exporter/tree/v6.0.6" }, "funding": [ { @@ -1689,20 +1474,20 @@ "type": "tidelift" } ], - "time": "2021-11-22T10:43:59+00:00" + "time": "2022-03-02T12:58:14+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.34", + "version": "v4.4.37", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "2c309e258adeb9970229042be39b360d34986fad" + "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/2c309e258adeb9970229042be39b360d34986fad", - "reference": "2c309e258adeb9970229042be39b360d34986fad", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d7f637cc0f0cc14beb0984f2bb50da560b271311", + "reference": "d7f637cc0f0cc14beb0984f2bb50da560b271311", "shasum": "" }, "require": { @@ -1744,7 +1529,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v4.4.34" + "source": "https://github.com/symfony/yaml/tree/v4.4.37" }, "funding": [ { @@ -1760,20 +1545,20 @@ "type": "tidelift" } ], - "time": "2021-11-18T18:49:23+00:00" + "time": "2022-01-24T20:11:01+00:00" }, { "name": "twig/twig", - "version": "v2.14.11", + "version": "v2.14.12", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "66baa66f29ee30e487e05f1679903e36eb01d727" + "reference": "19c898bda30c5edea573bbb9ee1235d8cf6956ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/66baa66f29ee30e487e05f1679903e36eb01d727", - "reference": "66baa66f29ee30e487e05f1679903e36eb01d727", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/19c898bda30c5edea573bbb9ee1235d8cf6956ed", + "reference": "19c898bda30c5edea573bbb9ee1235d8cf6956ed", "shasum": "" }, "require": { @@ -1828,7 +1613,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v2.14.11" + "source": "https://github.com/twigphp/Twig/tree/v2.14.12" }, "funding": [ { @@ -1840,22 +1625,22 @@ "type": "tidelift" } ], - "time": "2022-02-04T06:57:25+00:00" + "time": "2022-03-25T09:34:52+00:00" } ], "packages-dev": [ { "name": "bamarni/composer-bin-plugin", - "version": "1.4.1", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/bamarni/composer-bin-plugin.git", - "reference": "9329fb0fbe29e0e1b2db8f4639a193e4f5406225" + "reference": "49934ffea764864788334c1485fbb08a4b852031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/9329fb0fbe29e0e1b2db8f4639a193e4f5406225", - "reference": "9329fb0fbe29e0e1b2db8f4639a193e4f5406225", + "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/49934ffea764864788334c1485fbb08a4b852031", + "reference": "49934ffea764864788334c1485fbb08a4b852031", "shasum": "" }, "require": { @@ -1890,29 +1675,100 @@ ], "support": { "issues": "https://github.com/bamarni/composer-bin-plugin/issues", - "source": "https://github.com/bamarni/composer-bin-plugin/tree/master" + "source": "https://github.com/bamarni/composer-bin-plugin/tree/v1.5.0" }, - "time": "2020-05-03T08:27:20+00:00" + "time": "2022-02-22T21:01:25+00:00" + }, + { + "name": "composer/pcre", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/1.0.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-21T20:24:37+00:00" }, { "name": "composer/semver", - "version": "3.2.6", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "83e511e247de329283478496f7a1e114c9517506" + "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/83e511e247de329283478496f7a1e114c9517506", - "reference": "83e511e247de329283478496f7a1e114c9517506", + "url": "https://api.github.com/repos/composer/semver/zipball/5d8e574bb0e69188786b8ef77d43341222a41a71", + "reference": "5d8e574bb0e69188786b8ef77d43341222a41a71", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.54", + "phpstan/phpstan": "^1.4", "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", @@ -1957,7 +1813,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.2.6" + "source": "https://github.com/composer/semver/tree/3.3.1" }, "funding": [ { @@ -1973,29 +1829,31 @@ "type": "tidelift" } ], - "time": "2021-10-25T11:34:17+00:00" + "time": "2022-03-16T11:22:07+00:00" }, { "name": "composer/xdebug-handler", - "version": "2.0.2", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339" + "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/84674dd3a7575ba617f5a76d7e9e29a7d3891339", - "reference": "84674dd3a7575ba617f5a76d7e9e29a7d3891339", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/9e36aeed4616366d2b690bdce11f71e9178c579a", + "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a", "shasum": "" }, "require": { + "composer/pcre": "^1", "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" }, "type": "library", "autoload": { @@ -2021,7 +1879,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.2" + "source": "https://github.com/composer/xdebug-handler/tree/2.0.5" }, "funding": [ { @@ -2037,7 +1895,7 @@ "type": "tidelift" } ], - "time": "2021-07-31T17:03:58+00:00" + "time": "2022-02-24T20:20:32+00:00" }, { "name": "doctrine/annotations", @@ -2113,29 +1971,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -2162,7 +2021,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -2178,36 +2037,32 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", - "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "^8.2" + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" @@ -2242,7 +2097,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.1" + "source": "https://github.com/doctrine/lexer/tree/1.2.3" }, "funding": [ { @@ -2258,20 +2113,20 @@ "type": "tidelift" } ], - "time": "2020-05-25T17:44:05+00:00" + "time": "2022-02-28T11:07:21+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.3.1", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "b37bf90405cec3f7a83c18e645ef748bcb87ac11" + "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b37bf90405cec3f7a83c18e645ef748bcb87ac11", - "reference": "b37bf90405cec3f7a83c18e645ef748bcb87ac11", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", + "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", "shasum": "" }, "require": { @@ -2282,33 +2137,32 @@ "ext-tokenizer": "*", "php": "^7.2.5 || ^8.0", "php-cs-fixer/diff": "^2.0", - "symfony/console": "^4.4.20 || ^5.1.3", - "symfony/event-dispatcher": "^4.4.20 || ^5.0", - "symfony/filesystem": "^4.4.20 || ^5.0", - "symfony/finder": "^4.4.20 || ^5.0", - "symfony/options-resolver": "^4.4.20 || ^5.0", + "symfony/console": "^4.4.20 || ^5.1.3 || ^6.0", + "symfony/event-dispatcher": "^4.4.20 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.4.20 || ^5.0 || ^6.0", + "symfony/finder": "^4.4.20 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.4.20 || ^5.0 || ^6.0", "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php72": "^1.23", "symfony/polyfill-php80": "^1.23", "symfony/polyfill-php81": "^1.23", - "symfony/process": "^4.4.20 || ^5.0", - "symfony/stopwatch": "^4.4.20 || ^5.0" + "symfony/process": "^4.4.20 || ^5.0 || ^6.0", + "symfony/stopwatch": "^4.4.20 || ^5.0 || ^6.0" }, "require-dev": { "justinrainbow/json-schema": "^5.2", "keradus/cli-executor": "^1.5", "mikey179/vfsstream": "^1.6.8", - "php-coveralls/php-coveralls": "^2.4.3", + "php-coveralls/php-coveralls": "^2.5.2", "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.10.3", + "phpspec/prophecy": "^1.15", "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5", + "phpunit/phpunit": "^8.5.21 || ^9.5", "phpunitgoodpractices/polyfill": "^1.5", "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^5.2.4", - "symfony/yaml": "^4.4.20 || ^5.0" + "symfony/phpunit-bridge": "^5.2.4 || ^6.0", + "symfony/yaml": "^4.4.20 || ^5.0 || ^6.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -2340,7 +2194,7 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.3.1" + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.4.0" }, "funding": [ { @@ -2348,41 +2202,42 @@ "type": "github" } ], - "time": "2021-11-15T18:01:18+00:00" + "time": "2021-12-11T16:25:08+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2398,7 +2253,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -2406,20 +2261,20 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nikic/php-parser", - "version": "v4.13.1", + "version": "v4.13.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd" + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/63a79e8daa781cac14e5195e63ed8ae231dd10fd", - "reference": "63a79e8daa781cac14e5195e63ed8ae231dd10fd", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", "shasum": "" }, "require": { @@ -2460,9 +2315,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" }, - "time": "2021-11-03T20:52:16+00:00" + "time": "2021-11-30T19:35:32+00:00" }, { "name": "phar-io/manifest", @@ -2526,16 +2381,16 @@ }, { "name": "phar-io/version", - "version": "3.1.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "bae7c545bef187884426f042434e561ab1ddb182" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", - "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -2571,9 +2426,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.0" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2021-02-23T14:00:09+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "php-cs-fixer/diff", @@ -2739,16 +2594,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.5.1", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae" + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae", - "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", "shasum": "" }, "require": { @@ -2783,22 +2638,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0" }, - "time": "2021-10-02T14:08:47+00:00" + "time": "2022-01-04T19:58:01+00:00" }, { "name": "phpspec/prophecy", - "version": "1.14.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", - "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { @@ -2850,26 +2705,26 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.14.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" }, - "time": "2021-09-10T09:02:12+00:00" + "time": "2021-12-08T12:19:24+00:00" }, { "name": "phpstan/phpstan", - "version": "1.2.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee" + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cbe085f9fdead5b6d62e4c022ca52dc9427a10ee", - "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", + "reference": "2be8dd6dfa09ab1a21c49956ff591979cd5ab29e", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "php": "^7.2|^8.0" }, "conflict": { "phpstan/phpstan-shim": "*" @@ -2879,11 +2734,6 @@ "phpstan.phar" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2-dev" - } - }, "autoload": { "files": [ "bootstrap.php" @@ -2896,7 +2746,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.2.0" + "source": "https://github.com/phpstan/phpstan/tree/1.5.0" }, "funding": [ { @@ -2916,20 +2766,20 @@ "type": "tidelift" } ], - "time": "2021-11-18T14:09:01+00:00" + "time": "2022-03-24T18:18:00+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.9", + "version": "9.2.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b" + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b", - "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { @@ -2985,7 +2835,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -2993,20 +2843,20 @@ "type": "github" } ], - "time": "2021-11-19T15:21:02+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", - "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -3045,7 +2895,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -3053,7 +2903,7 @@ "type": "github" } ], - "time": "2020-09-28T05:57:25+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -3238,16 +3088,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.10", + "version": "9.5.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" + "reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", - "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35ea4b7f3acabb26f4bb640f8c30866c401da807", + "reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807", "shasum": "" }, "require": { @@ -3263,7 +3113,7 @@ "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -3277,7 +3127,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -3298,11 +3148,11 @@ } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3325,11 +3175,11 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.19" }, "funding": [ { - "url": "https://phpunit.de/donate.html", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { @@ -3337,7 +3187,7 @@ "type": "github" } ], - "time": "2021-09-25T07:38:51+00:00" + "time": "2022-03-15T09:57:31+00:00" }, { "name": "sebastian/cli-parser", @@ -3845,16 +3695,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -3897,7 +3747,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { @@ -3905,7 +3755,7 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", @@ -4196,28 +4046,28 @@ }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -4240,7 +4090,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -4248,7 +4098,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -4303,25 +4153,186 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "symfony/event-dispatcher", + "version": "v4.4.37", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "3ccfcfb96ecce1217d7b0875a0736976bc6e63dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3ccfcfb96ecce1217d7b0875a0736976bc6e63dc", + "reference": "3ccfcfb96ecce1217d7b0875a0736976bc6e63dc", + "shasum": "" + }, + "require": { + "php": ">=7.1.3", + "symfony/event-dispatcher-contracts": "^1.1", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "~3.4|~4.4", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "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": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.37" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:41:36+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "01e9a4efac0ee33a05dfdf93b346f62e7d0e998c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/01e9a4efac0ee33a05dfdf93b346f62e7d0e998c", + "reference": "01e9a4efac0ee33a05dfdf93b346f62e7d0e998c", + "shasum": "" + }, + "require": { + "php": ">=7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T15:25:38+00:00" + }, { "name": "symfony/options-resolver", - "version": "v5.3.7", + "version": "v6.0.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "4b78e55b179003a42523a362cc0e8327f7a69b5e" + "reference": "51f7006670febe4cbcbae177cbffe93ff833250d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4b78e55b179003a42523a362cc0e8327f7a69b5e", - "reference": "4b78e55b179003a42523a362cc0e8327f7a69b5e", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/51f7006670febe4cbcbae177cbffe93ff833250d", + "reference": "51f7006670febe4cbcbae177cbffe93ff833250d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.1|^3" }, "type": "library", "autoload": { @@ -4354,7 +4365,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.3.7" + "source": "https://github.com/symfony/options-resolver/tree/v6.0.3" }, "funding": [ { @@ -4370,20 +4381,20 @@ "type": "tidelift" } ], - "time": "2021-08-04T21:20:46+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/process", - "version": "v5.3.12", + "version": "v5.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e498803a6e95ede78e9d5646ad32a2255c033a6a" + "reference": "95440409896f90a5f85db07a32b517ecec17fa4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e498803a6e95ede78e9d5646ad32a2255c033a6a", - "reference": "e498803a6e95ede78e9d5646ad32a2255c033a6a", + "url": "https://api.github.com/repos/symfony/process/zipball/95440409896f90a5f85db07a32b517ecec17fa4c", + "reference": "95440409896f90a5f85db07a32b517ecec17fa4c", "shasum": "" }, "require": { @@ -4416,7 +4427,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.3.12" + "source": "https://github.com/symfony/process/tree/v5.4.5" }, "funding": [ { @@ -4432,25 +4443,25 @@ "type": "tidelift" } ], - "time": "2021-11-22T22:39:13+00:00" + "time": "2022-01-30T18:16:22+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.3.4", + "version": "v6.0.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "b24c6a92c6db316fee69e38c80591e080e41536c" + "reference": "f2c1780607ec6502f2121d9729fd8150a655d337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b24c6a92c6db316fee69e38c80591e080e41536c", - "reference": "b24c6a92c6db316fee69e38c80591e080e41536c", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f2c1780607ec6502f2121d9729fd8150a655d337", + "reference": "f2c1780607ec6502f2121d9729fd8150a655d337", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1.0|^2" + "php": ">=8.0.2", + "symfony/service-contracts": "^1|^2|^3" }, "type": "library", "autoload": { @@ -4478,7 +4489,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.3.4" + "source": "https://github.com/symfony/stopwatch/tree/v6.0.5" }, "funding": [ { @@ -4494,7 +4505,7 @@ "type": "tidelift" } ], - "time": "2021-07-10T08:58:57+00:00" + "time": "2022-02-21T17:15:17+00:00" }, { "name": "theseer/tokenizer", @@ -4607,14 +4618,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "my127/my127": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { "php": "^7.3 || ^8.0" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.1.0" } diff --git a/src/Application.php b/src/Application.php index ef697b98..f971eae6 100644 --- a/src/Application.php +++ b/src/Application.php @@ -2,8 +2,8 @@ namespace my127\Workspace; -use my127\Console\Application\Application as ConsoleApplication; -use my127\Console\Application\Executor; +use my127\Workspace\Console\Application\Application as ConsoleApplication; +use my127\Workspace\Console\Application\Executor; use my127\Workspace\Environment\Environment; use Symfony\Component\EventDispatcher\EventDispatcher; diff --git a/src/Console/Application/Action/Action.php b/src/Console/Application/Action/Action.php new file mode 100644 index 00000000..ec1679f5 --- /dev/null +++ b/src/Console/Application/Action/Action.php @@ -0,0 +1,11 @@ +actions[($action instanceof Action)?$action::getName():$name] = $action; + } + + public function get(string $name): callable + { + return $this->actions[$name]; + } +} diff --git a/src/Console/Application/Application.php b/src/Console/Application/Application.php new file mode 100644 index 00000000..d976b640 --- /dev/null +++ b/src/Console/Application/Application.php @@ -0,0 +1,119 @@ +version = $version; + $this->executor = $executor; + $this->dispatcher = $dispatcher; + $this->registeredActions = $executor->getActionCollection(); + + $this->root = (new Section($name)) + ->description($description); + } + + public function getExecutor(): Executor + { + return $this->executor; + } + + public function getEventDispatcher(): EventDispatcher + { + return $this->dispatcher; + } + + public function getActionCollection(): ActionCollection + { + return $this->executor->getActionCollection(); + } + + public function register(callable $action, string $name = ''): Application + { + $this->registeredActions->add($action, $name); + return $this; + } + + public function description(string $description): Application + { + $this->root->setDescription($description); + return $this; + } + + public function usage(string $usage): Application + { + $this->root->addUsageDefinition($usage); + return $this; + } + + public function option(string $option): Application + { + $this->root->addOption($option); + return $this; + } + + public function action($action): Application + { + $this->root->setAction($action); + return $this; + } + + public function plugin(Plugin $plugin): Application + { + $this->plugins[] = $plugin; + $plugin->setup($this); + return $this; + } + + public function on(string $event, callable $listener): Application + { + $this->dispatcher->addListener($event, $listener); + return $this; + } + + public function section($name): Section + { + return $this->root->get($this->root->getName().' '.$name); + } + + public function run(?array $argv = null): void + { + if ($argv === null) { + global $argv; + $argv[0] = $this->root->getName(); + } + + $this->executor->run($this->root, $argv); + } + + public function getRootSection(): Section + { + return $this->root; + } +} diff --git a/src/Console/Application/Event/BeforeActionEvent.php b/src/Console/Application/Event/BeforeActionEvent.php new file mode 100644 index 00000000..decc4181 --- /dev/null +++ b/src/Console/Application/Event/BeforeActionEvent.php @@ -0,0 +1,40 @@ +input = $input; + $this->section = $section; + } + + public function getSection(): Section + { + return $this->section; + } + + public function getInput(): Input + { + return $this->input; + } + + public function isActionPrevented(): bool + { + return $this->preventAction; + } + + public function preventAction(): void + { + $this->preventAction = true; + } +} diff --git a/src/Console/Application/Event/InvalidUsageEvent.php b/src/Console/Application/Event/InvalidUsageEvent.php new file mode 100644 index 00000000..624b94de --- /dev/null +++ b/src/Console/Application/Event/InvalidUsageEvent.php @@ -0,0 +1,23 @@ +input = (new InputSequenceFactory())->createFrom($args, $options); + } + + public function getInputSequence(): InputSequence + { + return clone $this->input; + } +} diff --git a/src/Console/Application/Executor.php b/src/Console/Application/Executor.php new file mode 100644 index 00000000..29afd1fe --- /dev/null +++ b/src/Console/Application/Executor.php @@ -0,0 +1,174 @@ +dispatcher = $dispatcher; + $this->optionParser = $optionParser; + $this->usageParserBuilder = $usageParserBuilder; + $this->actions = $actions; + } + + public function getActionCollection(): ActionCollection + { + return $this->actions; + } + + public function run(Section $section, $argv = []): void + { + $this->argv = $argv; + + $this->root = $section; + $this->root->accept($this); + + if ($this->matchedSection === null || $this->matchedInput === null) { + $this->invalidUsage($argv); + return; + } + + if ($this->beforeAction()->isActionPrevented()) { + return; + } + + if (($action = $this->matchedSection->getAction()) === null) { + return; + } + + $this->invokeAction($action); + } + + public function visit(Section $section): bool + { + $options = $this->buildOptionCollection($this->root->getOptions()) + ->merge($this->buildOptionCollection($section->getOptions())); + + + $usageDefinitions = $section->getUsageDefinitions(); + + if (empty($usageDefinitions) && $section->getAction() !== null) { + $usageDefinitions = [$section->getName().' '.'[options]']; + } + + foreach ($usageDefinitions as $usageDefinition) { + + if ($usageDefinition[-1] == '%') { + + $compare = explode(' ', substr($usageDefinition, 0, -2)); + $against = array_slice($this->argv, 0, count($compare)); + + if ($compare != $against) { + continue; + } + + $options = new OptionDefinitionCollection(); + $args = []; + + foreach ($compare as $command) { + $args[] = new Command($command); + } + + $args[] = new Argument('%', implode(' ', array_slice($this->argv, count($compare)))); + + $this->matchedInput = new Input($args, $options); + $this->matchedSection = $section; + + return false; + + } else { + + $parser = $this->usageParserBuilder->createUsageParser($usageDefinition, $options); + + if (($input = $parser->parse($this->argv)) !== false) { + $this->matchedInput = $input; + $this->matchedSection = $section; + return false; + } + } + } + + return true; + } + + private function buildOptionCollection($options = []): OptionDefinitionCollection + { + $collection = new OptionDefinitionCollection(); + + foreach ($options as $option) { + $collection->add($this->optionParser->parse($option)); + } + + return $collection; + } + + private function invalidUsage($argv): InvalidUsageEvent + { + $this->dispatcher->dispatch(self::EVENT_INVALID_USAGE, $event = new InvalidUsageEvent( + $argv, + $this->buildOptionCollection($this->root->getOptions()) + )); + + return $event; + } + + private function beforeAction(): BeforeActionEvent + { + $this->dispatcher->dispatch(self::EVENT_BEFORE_ACTION, $event = new BeforeActionEvent( + $this->matchedInput, + $this->matchedSection + )); + + return $event; + } + + private function invokeAction($action) + { + if (!is_callable($action)) { + $action = $this->actions->get($action); + } + + $action($this->matchedInput); + } +} diff --git a/src/Console/Application/Plugin/ContextualHelpPlugin.php b/src/Console/Application/Plugin/ContextualHelpPlugin.php new file mode 100644 index 00000000..684ef315 --- /dev/null +++ b/src/Console/Application/Plugin/ContextualHelpPlugin.php @@ -0,0 +1,168 @@ +root = $application->getRootSection(); + + $application + ->option('-h, --help Show help message') + ->on(Executor::EVENT_BEFORE_ACTION, function (BeforeActionEvent $e) + { + if (($input = $e->getInput())->getOption('help') == true) { + $this->displayHelpPage($this->root->get(implode(' ', $input->getCommand()))); + $e->preventAction(); + } + }) + ->on(Executor::EVENT_INVALID_USAGE, function (InvalidUsageEvent $e) + { + $argv = $e->getInputSequence(); + $parts = []; + + while ($positional = $argv->pop()) { + $parts[] = $positional; + } + + $name = implode(' ', $parts); + $section = $this->root->contains($name)?$this->root->get($name):$this->root; + + $this->displayHelpPage($section); + }); + } + + private function displayHelpPage(Section $section): void + { + // Description + echo "\n\033[1m".($section->getDescription()?:$section->getName())."\033[0m\n\n"; + + // Usage + if (count($section->getUsageDefinitions()) > 0) { + + echo "\033[33mUsage:\033[0m\n"; + foreach ($section->getUsageDefinitions() as $usageDefinition) { + echo " {$usageDefinition}\n"; + } + echo "\n\n"; + + } else if ($section->getAction() !== null) { + + echo "\033[33mUsage:\033[0m\n"; + echo " {$section->getName()} [options]"; + echo "\n\n"; + } + + // Command Options + if (!$this->isRoot($section)) { + $this->displayOptionsHelp("\033[33mCommand Options:\033[0m", $section->getOptions()); + } + + // Sub Commands + $this->displaySubCommandHelp($section); + + // Global Options + $this->displayOptionsHelp("\033[33mGlobal Options:\033[0m", $this->root->getOptions()); + } + + private function displaySubCommandHelp(Section $section) + { + if (empty($children = $section->getChildren())) { + return; + } + + echo "\033[33mSub Commands:\033[0m\n"; + + $lines = []; + $padding = 0; + + /** @var Section $child */ + foreach ($children as $child) { + + $name = $child->getName(); + $line = [ + 'name' => substr($name, strrpos($name, ' ')), + 'description' => $child->getDescription() + ]; + + if (($length = strlen($name)) > $padding) { + $padding = $length; + } + + $lines[] = $line; + } + + $padding += 4; + + foreach ($lines as $line) { + echo ' '."\033[32m".str_pad($line['name'], $padding) . "\033[0m" . $line['description'] . "\n"; + } + + echo "\n"; + } + + private function displayOptionsHelp(string $heading, array $options): void + { + $padding = 0; + $lines = []; + + /** @var OptionDefinition $option */ + foreach ($this->getOptionCollection($options) as $option) { + + $description = $option->getDescription(); + + $definition = ' '; + $definition .= $option->getShortName()?'-'.$option->getShortName().', ':' '; + $definition .= $option->getLongName()?'--'.$option->getLongName():''; + $definition .= $option->getType() == OptionDefinition::TYPE_VALUE ? '=<'.$option->getArgument().'>':''; + + if (($length = strlen($definition)) > $padding) { + $padding = $length; + } + + $lines[] = ['definition' => $definition, 'description' => $description]; + } + + $padding += 4; + + if (!empty($lines)) { + echo $heading."\n"; + + foreach ($lines as $line) { + echo "\033[32m".str_pad($line['definition'], $padding) ."\033[0m". $line['description'] . "\n"; + } + + echo "\n"; + } + } + + private function isRoot($section): bool + { + return $section === $this->root; + } + + private function getOptionCollection(array $options): OptionDefinitionCollection + { + $parser = new OptionDefinitionParser(); + $collection = new OptionDefinitionCollection(); + + foreach ($options as $option) { + $collection->add($parser->parse($option)); + } + + return $collection; + } +} diff --git a/src/Console/Application/Plugin/Plugin.php b/src/Console/Application/Plugin/Plugin.php new file mode 100644 index 00000000..cde4a89e --- /dev/null +++ b/src/Console/Application/Plugin/Plugin.php @@ -0,0 +1,10 @@ +name = $name; + } + + public function getName(): string + { + return $this->name; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(string $description): void + { + $this->description = $description; + } + + public function description(string $description): Section + { + $this->setDescription($description); + return $this; + } + + public function getUsageDefinitions(): iterable + { + return $this->usageDefinitions; + } + + public function addUsageDefinition(string $usage): void + { + $this->usageDefinitions[] = strtok($this->name, ' ').' '.((strpos($usage, '[options]') === false && $usage[-1] != '%') ? $usage.' [options]' : $usage); + } + + public function usage(string $usage): Section + { + $this->addUsageDefinition($usage); + + return $this; + } + + public function getOptions(): iterable + { + return $this->options; + } + + public function addOption(string $option): void + { + $this->options[] = $option; + } + + public function option(string $option): Section + { + $this->addOption($option); + + return $this; + } + + public function getAction() + { + return $this->action; + } + + public function setAction($action): void + { + $this->action = $action; + } + + public function action($action): Section + { + $this->setAction($action); + + return $this; + } + + public function add(Section $child): void + { + $this->children[] = $child; + } + + public function get(string $name): Section + { + if ($name === $this->name) { + return $this; + } + + foreach ($this->children as $child) { + if (strpos($name, $child->getName()) === 0) { + return $child->get($name); + } + } + + $childSectionName = substr($name, 0, strpos($name, ' ', strlen($this->name) + 1)?:strlen($name)); + $childSection = new self($childSectionName); + + $this->add($childSection); + + return $childSection->get($name); + } + + public function getChildren(): array + { + return $this->children; + } + + public function contains(string $name): bool + { + if ($name === $this->name) { + return true; + } + + foreach ($this->children as $child) { + if ($child->contains($name)) { + return true; + } + } + + return false; + } + + public function accept(SectionVisitor $visitor): bool + { + if ($visitor->visit($this) === false) { + return false; + } + + foreach ($this->children as $child) { + if ($child->accept($visitor) === false) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/Console/Application/Section/SectionVisitor.php b/src/Console/Application/Section/SectionVisitor.php new file mode 100644 index 00000000..23c00b0e --- /dev/null +++ b/src/Console/Application/Section/SectionVisitor.php @@ -0,0 +1,15 @@ +plugin(new ContextualHelpPlugin()); + $application->plugin(new VersionInfoPlugin()); + + return $application; + } + + public static function usage($definition, $cmd = null, OptionDefinitionCollection $optionRepository = null) + { + $cmd = empty($cmd) ? [] : preg_split('/\s+/', $cmd); + $usageParser = (new UsageParserBuilder())->createUsageParser($definition, $optionRepository); + + return $usageParser->parse($cmd); + } +} diff --git a/src/Console/Factory/OptionValueFactory.php b/src/Console/Factory/OptionValueFactory.php new file mode 100644 index 00000000..b22fc6f2 --- /dev/null +++ b/src/Console/Factory/OptionValueFactory.php @@ -0,0 +1,63 @@ +createInvalidTypeException($type); + } + } + + public function createFromTypeAndValue(string $type, string $value): OptionValue + { + switch ($type) { + case OptionDefinition::TYPE_BOOL: + return BooleanOptionValue::create($this->stringToBoolean($value)); + case OptionDefinition::TYPE_VALUE: + return StringOptionValue::create($value); + default: + throw $this->createInvalidTypeException($type); + } + } + + private function createInvalidTypeException(string $type): InvalidArgumentException + { + return new InvalidArgumentException(sprintf( + 'Option type "%s" is invalid. Valid arguments are "%s"', + $type, + implode('","', OptionDefinition::TYPES) + )); + } + + private function createInvalidBooleanValueException(string $value): InvalidArgumentException + { + return new InvalidArgumentException(sprintf( + 'The provided value "%s" not a boolean representation. Valid arguments are "%s"', + $value, + implode('","', BooleanOptionValue::VALUES) + )); + } + + private function stringToBoolean(string $value): bool + { + if (!in_array($value, BooleanOptionValue::VALUES)) { + throw $this->createInvalidBooleanValueException($value); + } + + return in_array($value, BooleanOptionValue::TRUE_VALUES, true); + } +} diff --git a/src/Console/Usage/Exception/NoSuchOptionException.php b/src/Console/Usage/Exception/NoSuchOptionException.php new file mode 100644 index 00000000..9bf1e78d --- /dev/null +++ b/src/Console/Usage/Exception/NoSuchOptionException.php @@ -0,0 +1,13 @@ +args = $args; + + $this->processArgs($optionRepository); + } + + public function command($offset = 0, $length = null) + { + return implode(' ', array_slice($this->command, $offset, $length)); + } + + public function getCommand() + { + return $this->command; + } + + public function argument($argument) + { + return $this->getArgument($argument); + } + + public function getArgument($argument) + { + if (!isset($this->arguments[$argument])) { + return null; + } + + $values = $this->arguments[$argument]; + return (count($values) == 1) ? $values[0] : $values; + } + + public function option($option) + { + return $this->getOption($option); + } + + public function getOption($option) + { + if (!isset($this->options[$option])) { + return null; + } + + $values = $this->options[$option]; + return (count($values) == 1) ? $values[0] : $values; + } + + /** + * @inheritDoc + */ + public function offsetExists($offset) + { + return $this->args[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetGet($offset) + { + return $this->args[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value) + { + $this->args[$offset] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset) + { + unset($this->args[$offset]); + } + + /** + * @inheritDoc + */ + public function count() + { + return count($this->args); + } + + /** + * @inheritDoc + */ + public function getIterator() + { + return new ArrayIterator($this->args); + } + + /** + * @inheritDoc + */ + public function __toString() + { + return implode("\n", array_map(function ($arg) { + return (string) $arg; + + }, $this->args)); + } + + public function toJSON() + { + $data = + [ + 'argv' => array_map(function ($arg) { + return (string) $arg; + }, $this->args), + 'command' => $this->command, + 'arguments' => array_map(function ($values) { + return count($values) == 1 ? $values[0] : $values; + }, $this->arguments), + 'options' => array_map(function ($values) { + return count($values) == 1 ? $values[0] : $values; + }, $this->options) + ]; + + return json_encode($data, JSON_PRETTY_PRINT); + } + + private function processArgs(OptionDefinitionCollection $optionRepository) + { + /** @var OptionDefinition $optionDefinition */ + foreach ($optionRepository as $optionDefinition) { + $this->options[$optionDefinition->getLongName() ?: $optionDefinition->getShortName()] = null; + } + + foreach ($this->args as $arg) { + switch (true) { + case ($arg instanceof Command): + $this->command[] = $arg->getName(); + break; + + case ($arg instanceof Argument): + $this->arguments[$arg->getName()][] = $arg->getValue(); + break; + + case ($arg instanceof Option): + $this->options[$this->getOptionName($arg)][] = $arg->getValue(); + break; + } + } + + foreach ($this->options as $key => $value) { + if ($value === null) { + $this->options[$key] = [$optionRepository->find($key)->getDefault()]; + } + } + } + + private function getOptionName(Option $option) + { + $definition = $option->getDefinition(); + + return $definition->getLongName()?:$definition->getShortName(); + } +} diff --git a/src/Console/Usage/Model/Argument.php b/src/Console/Usage/Model/Argument.php new file mode 100644 index 00000000..e012a33d --- /dev/null +++ b/src/Console/Usage/Model/Argument.php @@ -0,0 +1,30 @@ +name = $name; + $this->value = $value; + } + + public function getName() + { + return $this->name; + } + + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return 'argument(\''.$this->name.'\', \''.$this->value.'\')'; + } +} diff --git a/src/Console/Usage/Model/BooleanOptionValue.php b/src/Console/Usage/Model/BooleanOptionValue.php new file mode 100644 index 00000000..b395c654 --- /dev/null +++ b/src/Console/Usage/Model/BooleanOptionValue.php @@ -0,0 +1,34 @@ +value = $value; + } + + public static function create(bool $value): self + { + return new self($value); + } + + public function equals(OptionValue $value): bool + { + return $value->value() === $this->value; + } + + public function value(): bool + { + return $this->value; + } +} diff --git a/src/Console/Usage/Model/Command.php b/src/Console/Usage/Model/Command.php new file mode 100644 index 00000000..14c52bf5 --- /dev/null +++ b/src/Console/Usage/Model/Command.php @@ -0,0 +1,23 @@ +name = $name; + } + + public function getName() + { + return $this->name; + } + + public function __toString() + { + return 'command(\''.$this->name.'\')'; + } +} diff --git a/src/Console/Usage/Model/Option.php b/src/Console/Usage/Model/Option.php new file mode 100644 index 00000000..64a755cb --- /dev/null +++ b/src/Console/Usage/Model/Option.php @@ -0,0 +1,43 @@ +name = $name; + $this->definition = $definition; + $this->value = $value; + } + + public function getDefinition() + { + return $this->definition; + } + + public function getName() + { + return $this->name; + } + + public function getValue() + { + return $this->value; + } + + public function __toString() + { + $value = $this->value; + + if (is_bool($value)) { + $value = ($value) ? 'true' : 'false'; + } + + return 'option(\''.$this->name.'\', \''.$value.'\')'; + } +} diff --git a/src/Console/Usage/Model/OptionDefinition.php b/src/Console/Usage/Model/OptionDefinition.php new file mode 100644 index 00000000..97d8a2b8 --- /dev/null +++ b/src/Console/Usage/Model/OptionDefinition.php @@ -0,0 +1,183 @@ +shortName = $shortName; + $this->longName = $longName; + $this->description = $description; + $this->type = $type; + $this->default = $default; + $this->argument = $argument; + } + + public function getLabel() + { + $names = []; + + if ($this->longName !== null) { + $names[] = '--'.$this->longName; + } + + if ($this->shortName !== null) { + $names[] = '-'.$this->shortName; + } + + return implode('|', $names).' (type:'.$this->type.')'; + } + + public function getDefault() + { + return $this->default; + } + + /** + * Get Short Name + * + * @return string + */ + public function getShortName() + { + return $this->shortName; + } + + /** + * Set Short Name + * + * @param string $shortName + * + * @return void + */ + public function setShortName($shortName) + { + $this->shortName = $shortName; + } + + /** + * Get Long Name + * + * @return string + */ + public function getLongName() + { + return $this->longName; + } + + /** + * Set Long Name + * + * @param string $longName + * + * @return void + */ + public function setLongName($longName) + { + $this->longName = $longName; + } + + /** + * Get Description + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set Description + * + * @param string $description + * + * @return void + */ + public function setDescription($description) + { + $this->description = $description; + } + + /** + * Get Type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set Type + * + * @param string $type + * + * @return void + */ + public function setType($type) + { + $this->type = $type; + } + + public function getArgument() + { + return $this->argument; + } + + public function __toString() + { + return $this->getLabel(); + } +} diff --git a/src/Console/Usage/Model/OptionDefinitionCollection.php b/src/Console/Usage/Model/OptionDefinitionCollection.php new file mode 100644 index 00000000..00f4e72a --- /dev/null +++ b/src/Console/Usage/Model/OptionDefinitionCollection.php @@ -0,0 +1,54 @@ +options[$optionDefinition->getLabel()] = $optionDefinition; + + if (($shortName = $optionDefinition->getShortName()) !== null) { + $this->map[$shortName] = $optionDefinition; + } + + if (($longName = $optionDefinition->getLongName()) !== null) { + $this->map[$longName] = $optionDefinition; + } + } + + public function find($optionName) + { + return (isset($this->map[$optionName])) ? $this->map[$optionName] : null; + } + + public function getIterator() + { + return new ArrayIterator($this->options); + } + + public function merge(OptionDefinitionCollection $toMerge) + { + $merged = clone $this; + + foreach ($toMerge->options as $option) { + $merged->add($option); + } + + return $merged; + } + + public function count() + { + return count($this->options); + } +} diff --git a/src/Console/Usage/Model/OptionValue.php b/src/Console/Usage/Model/OptionValue.php new file mode 100644 index 00000000..6154926d --- /dev/null +++ b/src/Console/Usage/Model/OptionValue.php @@ -0,0 +1,11 @@ +value = $value; + } + + public static function create(string $value): self + { + return new self($value); + } + + public function equals(OptionValue $value): bool + { + return $value->value() == $this->value; + } + + public function value(): string + { + return $this->value; + } +} diff --git a/src/Console/Usage/Parser/InputSequence.php b/src/Console/Usage/Parser/InputSequence.php new file mode 100644 index 00000000..ce98f8f1 --- /dev/null +++ b/src/Console/Usage/Parser/InputSequence.php @@ -0,0 +1,60 @@ +options = $options; + $this->positional = $positional; + } + + public function peek() + { + return end($this->positional); + } + + public function pop() + { + return array_pop($this->positional); + } + + public function getOption(OptionDefinition $definition) + { + $key = $definition->getLabel(); + + if (!isset($this->options[$key])) { + return null; + } + + $option = array_pop($this->options[$key]); + + if (empty($this->options[$key])) { + unset($this->options[$key]); + } + + return $option; + } + + public function hasPositional() + { + return !empty($this->positional); + } + + public function hasOption(OptionDefinition $definition) + { + return isset($this->options[$definition->getLabel()]); + } + + public function count() + { + return count($this->positional) + count($this->options); + } +} diff --git a/src/Console/Usage/Parser/InputSequenceFactory.php b/src/Console/Usage/Parser/InputSequenceFactory.php new file mode 100644 index 00000000..c6c962c2 --- /dev/null +++ b/src/Console/Usage/Parser/InputSequenceFactory.php @@ -0,0 +1,117 @@ +find($name); + + if (!$definition) { + return null; + } + + switch ($definition->getType()) { + case OptionDefinition::TYPE_BOOL: + $options[$definition->getLabel()][] = new Option($name, $definition, true); + break; + + case OptionDefinition::TYPE_VALUE: + $value = (isset($parts[1])) ? $parts[1] : $symbols[++$i]; + $options[$definition->getLabel()][] = new Option($name, $definition, $value); + break; + } + + continue; + } + + // Short Options + + $shortOptions = substr($symbol, 1); + + while (!empty($shortOptions)) { + $name = $shortOptions[0]; + $shortOptions = substr($shortOptions, 1); + $definition = $definitionRepository->find($name); + + if (!$definition) { + return null; + } + + switch ($definition->getType()) { + case OptionDefinition::TYPE_BOOL: + $options[$definition->getLabel()][] = new Option($name, $definition, true); + break; + + case OptionDefinition::TYPE_VALUE: + $value = null; + + if (!empty($shortOptions)) { + $value = ($shortOptions[0] == '=') ? substr($shortOptions, 1) : $shortOptions; + $shortOptions = ''; + } + + if ($value === null) { + $value = $symbols[++$i]; + } + + $options[$definition->getLabel()][] = new Option($name, $definition, $value); + break; + } + } + } + + $positional = array_reverse($positional); + + foreach ($options as &$optionGroup) { + $optionGroup = array_reverse($optionGroup); + } + + return new InputSequence($options, $positional); + } +} diff --git a/src/Console/Usage/Parser/OptionDefinitionParser.php b/src/Console/Usage/Parser/OptionDefinitionParser.php new file mode 100644 index 00000000..53a9a190 --- /dev/null +++ b/src/Console/Usage/Parser/OptionDefinitionParser.php @@ -0,0 +1,171 @@ += 'A') && ($t <= 'Z') )) { + goto parseArgument; + } + + goto modeSelect; + } + + parseArgument: + { + ++$i; + + $argument = ''; + + while ($i < $length && ($option[$i] != '>' && $option[$i] != ',' && $option[$i] != ' ')) { + $argument .= $option[$i++]; + } + + ++$i; + + $type = OptionDefinition::TYPE_VALUE; + + goto modeSelect; + } + + parseDescription: + { + while ($i < $length) { + $description .= $t = $option[$i++]; + + if ($t == '[') { + goto hasDefault; + } + } + + goto buildOptionDefinition; + } + + hasDefault: + { + $hasDefault = ''; + + while ($i < $length) { + $hasDefault .= $t = $option[$i++]; + + if ($t == ':') { + $description .= $hasDefault; + + if ($hasDefault == 'default:') { + goto parseDefaultValue; + } + + goto parseDescription; + } + } + + goto buildOptionDefinition; + } + + parseDefaultValue: + { + $description .= ' '; + $i += 1; + $default = ''; + + while ($i < $length) { + $t = $option[$i++]; + + if ($t != ']') { + $default .= $t; + } else { + $description .= $default . ']'; + goto parseDescription; + } + } + + goto buildOptionDefinition; + } + + buildOptionDefinition: + { + if ($type == OptionDefinition::TYPE_BOOL && $default === null) { + $default = false; + } + + return new OptionDefinition($shortName, $longName, $description, $type, $default, $argument); + } + } +} diff --git a/src/Console/Usage/Parser/Transition/ArgumentTransition.php b/src/Console/Usage/Parser/Transition/ArgumentTransition.php new file mode 100644 index 00000000..05a0ed11 --- /dev/null +++ b/src/Console/Usage/Parser/Transition/ArgumentTransition.php @@ -0,0 +1,105 @@ +argument = $argument; + $this->to = $to; + } + + /** + * @inheritDoc + */ + public function getTo() + { + return $this->to; + } + + /** + * @inheritDoc + */ + public function setTo(State $state) + { + $this->to = $state; + } + + /** + * @inheritDoc + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + $this->to->accept($visitor, $visited); + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function can($input, Stateful $context, Runner $runner) + { + return $input->hasPositional(); + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function apply($input, Stateful $context, Runner $runner) + { + $context->setCurrentState($this->to); + + return new Argument($this->argument, $input->pop()); + } + + /** + * @inheritDoc + */ + public function copy(&$visited = []) + { + return new self($this->argument, $this->to->copy($visited)); + } + + /** + * @inheritDoc + */ + public function __toString() + { + return '<'.$this->argument.'>'; + } +} diff --git a/src/Console/Usage/Parser/Transition/CommandTransition.php b/src/Console/Usage/Parser/Transition/CommandTransition.php new file mode 100644 index 00000000..375a5f6a --- /dev/null +++ b/src/Console/Usage/Parser/Transition/CommandTransition.php @@ -0,0 +1,105 @@ +command = $command; + $this->to = $to; + } + + /** + * @inheritDoc + */ + public function getTo() + { + return $this->to; + } + + /** + * @inheritDoc + */ + public function setTo(State $state) + { + $this->to = $state; + } + + /** + * @inheritDoc + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + $this->to->accept($visitor, $visited); + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function can($input, Stateful $context, Runner $runner) + { + return $input->peek() == $this->command; + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function apply($input, Stateful $context, Runner $runner) + { + $context->setCurrentState($this->to); + + return new Command($input->pop()); + } + + /** + * @inheritDoc + */ + public function copy(&$visited = []) + { + return new self($this->command, $this->to->copy($visited)); + } + + /** + * @inheritDoc + */ + public function __toString() + { + return $this->command; + } +} diff --git a/src/Console/Usage/Parser/Transition/LoopTransition.php b/src/Console/Usage/Parser/Transition/LoopTransition.php new file mode 100644 index 00000000..e44feb9b --- /dev/null +++ b/src/Console/Usage/Parser/Transition/LoopTransition.php @@ -0,0 +1,104 @@ +to = $to; + } + + /** + * @inheritDoc + */ + public function getTo() + { + return $this->to; + } + + /** + * @inheritDoc + */ + public function setTo(State $state) + { + $this->to = $state; + } + + /** + * @inheritDoc + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + $this->to->accept($visitor, $visited); + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function can($input, Stateful $context, Runner $runner) + { + $last = $context->getData($this); + $current = $runner->getOutput(); + + if ($last == $current) { + return false; + } + + $context->setData($this, $current); + return true; + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function apply($input, Stateful $context, Runner $runner) + { + $context->setCurrentState($this->to); + return null; + } + + /** + * @inheritDoc + */ + public function copy(&$visited = []) + { + return new self($this->to->copy($visited)); + } + + /** + * @inheritDoc + */ + public function __toString() + { + return 'loop'; + } +} diff --git a/src/Console/Usage/Parser/Transition/OptionTransition.php b/src/Console/Usage/Parser/Transition/OptionTransition.php new file mode 100644 index 00000000..eaabec24 --- /dev/null +++ b/src/Console/Usage/Parser/Transition/OptionTransition.php @@ -0,0 +1,117 @@ +to = $to; + $this->optionDefinition = $optionDefinition; + } + + /** + * @inheritDoc + */ + public function getTo() + { + return $this->to; + } + + /** + * @inheritDoc + */ + public function setTo(State $state) + { + $this->to = $state; + } + + /** + * @inheritDoc + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + $this->to->accept($visitor, $visited); + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function can($input, Stateful $context, Runner $runner) + { + return $input->hasOption($this->optionDefinition); + } + + /** + * @inheritDoc + * + * @var InputSequence $input + * @var UsageParserContext $context + * @var BacktrackingRunner $runner + */ + public function apply($input, Stateful $context, Runner $runner) + { + $context->setCurrentState($this->to); + + return $input->getOption($this->optionDefinition); + } + + /** + * @inheritDoc + */ + public function copy(&$visited = []) + { + return new self($this->optionDefinition, $this->to->copy($visited)); + } + + /** + * @inheritDoc + */ + public function __toString() + { + $names = []; + + if (($longName = $this->optionDefinition->getLongName()) !== null) { + $names[] = $longName; + } + + if (($shortName = $this->optionDefinition->getShortName()) !== null) { + $names[] = $shortName; + } + + return 'option('.implode(' | ', $names).')::'.$this->optionDefinition->getType(); + } +} diff --git a/src/Console/Usage/Parser/Transition/ShortcutTransition.php b/src/Console/Usage/Parser/Transition/ShortcutTransition.php new file mode 100644 index 00000000..ea6966d4 --- /dev/null +++ b/src/Console/Usage/Parser/Transition/ShortcutTransition.php @@ -0,0 +1,82 @@ +to = $to; + } + + /** + * @inheritDoc + */ + public function getTo() + { + return $this->to; + } + + /** + * @inheritDoc + */ + public function setTo(State $state) + { + $this->to = $state; + } + + /** + * @inheritDoc + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + $this->to->accept($visitor, $visited); + } + + /** + * @inheritDoc + */ + public function can($input, Stateful $context, Runner $runner) + { + return true; + } + + /** + * @inheritDoc + */ + public function apply($input, Stateful $context, Runner $runner) + { + $context->setCurrentState($this->to); + return null; + } + + /** + * @inheritDoc + */ + public function copy(&$visited = []) + { + return new self($this->to->copy($visited)); + } + + /** + * @inheritDoc + */ + public function __toString() + { + return '*'; + } +} diff --git a/src/Console/Usage/Parser/UsageParser.php b/src/Console/Usage/Parser/UsageParser.php new file mode 100644 index 00000000..9e6feb4a --- /dev/null +++ b/src/Console/Usage/Parser/UsageParser.php @@ -0,0 +1,71 @@ +usageDefinition = $usageDefinition; + $this->optionRepository = $optionRepository; + } + + public function getDefinition() + { + return $this->usageDefinition; + } + + public function getOptionDefinitions() + { + return $this->optionRepository; + } + + /** + * @param array $args + * + * @return Input|false + */ + public function parse($args = null) + { + $fsm = new BacktrackingRunner($this->usageDefinition->getInitialState(), new UsageParserContext()); + $symbols = $this->getInputSequence($args); + + if (!$symbols) { + return false; + } + + $result = $fsm->input($symbols); + + return $result === false ? false : new Input($result, $this->optionRepository); + } + + private function getInputSequence($args = null) + { + if ($args instanceof InputSequence) { + return $args; + } + + return (new InputSequenceFactory())->createFrom($args, $this->optionRepository); + } +} diff --git a/src/Console/Usage/Parser/UsageParserBuilder.php b/src/Console/Usage/Parser/UsageParserBuilder.php new file mode 100644 index 00000000..a66ada85 --- /dev/null +++ b/src/Console/Usage/Parser/UsageParserBuilder.php @@ -0,0 +1,321 @@ +stack = []; + $this->sequences = []; + $this->sequence = []; + $this->mode = self::MODE_REQUIRED; + $this->globalDefinitionRepository = (!$definitionRepository) ? new OptionDefinitionCollection() : $definitionRepository; + $this->usageDefinitionRepository = new OptionDefinitionCollection(); + $this->tokens = new Scanner($definition); + + return new UsageParser($this->parse(), $this->usageDefinitionRepository); + } + + private function parse() + { + while (($type = $this->tokens->peek()->getType()) != Token::T_EOL) { + switch ($type) { + case Token::T_SHORT_OPTION: + case Token::T_LONG_OPTION: + $this->parseOption(); + break; + + case Token::T_SINGLE_DASH: + $this->parseSingleDash(); + break; + + case Token::T_OPTION_SEQUENCE: + $this->parseOptionSequence(); + break; + + case Token::T_ARGUMENT_START: + $this->parseArgument(); + break; + + case Token::T_STRING: + $this->parseCommand(); + break; + + case Token::T_REQUIRED_START: + $this->parseRequired(); + break; + + case Token::T_OPTIONAL_START: + $this->parseOptional(); + break; + + case Token::T_DOUBLE_DASH: + $this->parseDoubleDash(); + break; + + case Token::T_MUTEX: + $this->parseMutex(); + break; + + case Token::T_ELLIPSIS: + $this->parseEllipsis(); + break; + + case Token::T_OPTIONS: + $this->parseOptions(); + break; + + default: + return null; + } + } + + $usage = $this->groupSequences(); + $usage->getState('start')->setType(State::TYPE_INITIAL); + $usage->getState('end')->setType(State::TYPE_TERMINAL); + + return $usage; + } + + private function addOption(OptionDefinition $optionDefinition) + { + $this->usageDefinitionRepository->add($optionDefinition); + $option = new Definition('Short Option'); + $option->addTransition(new OptionTransition($optionDefinition, $option->getState('end')), 'start'); + $this->append($option); + } + + private function parseOptions() + { + $this->expect(Token::T_OPTIONS); + + foreach ($this->globalDefinitionRepository as $optionDefinition) { + $this->addOption($optionDefinition); + } + } + + private function parseMutex() + { + $this->expect(Token::T_MUTEX); + $this->sequences[] = $this->sequence; + $this->sequence = []; + } + + private function parseEllipsis() + { + $this->expect(Token::T_ELLIPSIS); + + if (!($atom = end($this->sequence))) { + throw new Exception('Unable to loop empty atom'); + } + + $atom->getState('end')->addTransition(new LoopTransition($atom->getState('start'))); + } + + private function parseArgument() + { + $token = $this->expect([Token::T_ARGUMENT_START, Token::T_STRING, Token::T_ARGUMENT_STOP])[1]; + $argument = new Definition('Argument'); + $argument->addTransition(new ArgumentTransition($token->getValue(), $argument->getState('end')), 'start'); + $this->append($argument); + } + + private function parseCommand() + { + $token = $this->expect([Token::T_STRING])[0]; + $command = new Definition('Command'); + $command->addTransition(new CommandTransition($token->getValue(), $command->getState('end')), 'start'); + $this->append($command); + } + + private function parseRequired() + { + $this->expect(Token::T_REQUIRED_START); + $this->push(self::MODE_REQUIRED); + $this->parse(); + $this->pop(); + $this->expect(Token::T_REQUIRED_STOP); + } + + private function parseOptional() + { + $this->expect(Token::T_OPTIONAL_START); + $this->push(self::MODE_OPTIONAL); + $this->parse(); + $this->pop(); + $this->expect(Token::T_OPTIONAL_STOP); + } + + private function parseOption() + { + $token = $this->tokens->pop(); + $definition = $this->globalDefinitionRepository->find($token->getValue()); + $value = $this->is(Token::T_EQUALS); + + if (!$definition) { + $definition = ($token->getType() == Token::T_LONG_OPTION) ? + new OptionDefinition(null, $token->getValue(), null, ($value ? 'value' : 'bool')): + new OptionDefinition($token->getValue(), null, null, ($value ? 'value' : 'bool')); + } + + if ($value) { + $this->expect([Token::T_ARGUMENT_START, Token::T_STRING, Token::T_ARGUMENT_STOP]); + } + + $this->addOption($definition); + } + + private function parseOptionSequence() + { + $tokens = $this->tokens->pop()->getValue(); + + for ($i = 0; $i < strlen($tokens); ++$i) { + if (!($definition = $this->globalDefinitionRepository->find($tokens[$i]))) { + $definition = new OptionDefinition($tokens[$i], null, null, 'bool'); + } + + $this->addOption($definition); + } + } + + private function parseDoubleDash() + { + } + + private function parseSingleDash() + { + $this->expect(Token::T_SINGLE_DASH); + $command = new Definition('Command'); + $command->addTransition(new CommandTransition('-', $command->getState('end')), 'start'); + $this->append($command); + } + + private function expect($types) + { + $passed = []; + $types = (is_array($types)) ? $types : [$types]; + + foreach ($types as $type) { + $passed[] = $token = $this->tokens->pop(); + + if ($token->getType() != $type) { + throw new Exception('Expected Token ['.(new Token($type)).'] but found ['.(new Token($token->getType())).'].'); + } + } + + return $passed; + } + + private function is($type) + { + return ($this->tokens->peek()->getType() == $type) ? $this->tokens->pop() : false; + } + + private function append(Definition $partial) + { + if (count($this->sequence) > 0) { + end($this->sequence)->getState('end')->addTransition(new ShortcutTransition($partial->getState('start'))); + } + + if ($this->mode == self::MODE_OPTIONAL) { + $partial->getState('start')->addTransition(new ShortcutTransition($partial->getState('end'))); + } + + $this->sequence[] = $partial; + } + + private function groupSequences() + { + if (empty($this->sequences) && empty($this->sequence)) { + return null; + } + + $group = new Definition('Group'); + $choices = array_merge($this->sequences, [$this->sequence]); + + foreach ($choices as $choice) { + if (count($choice) == 0) { + continue; + } + + $group->getState('start')->addTransition(new ShortcutTransition($choice[0]->getState('start'))); + end($choice)->getState('end')->addTransition(new ShortcutTransition($group->getState('end'))); + } + + return $group; + } + + private function push($mode) + { + $this->stack[] = [$this->mode, $this->sequence, $this->sequences]; + + $this->mode = $mode; + $this->sequence = []; + $this->sequences = []; + } + + private function pop() + { + $group = $this->groupSequences(); + list ($this->mode, $this->sequence, $this->sequences) = array_pop($this->stack); + + if ($group !== null) { + $this->append($group); + } + } +} diff --git a/src/Console/Usage/Parser/UsageParserContext.php b/src/Console/Usage/Parser/UsageParserContext.php new file mode 100644 index 00000000..f6e095f0 --- /dev/null +++ b/src/Console/Usage/Parser/UsageParserContext.php @@ -0,0 +1,72 @@ +data = new SplObjectStorage(); + } + + /** + * Get Current State + * + * @return State + */ + public function getCurrentState() + { + return $this->currentState; + } + + /** + * Set Current State + * + * @param State $state + * + * @return void + */ + public function setCurrentState(State $state) + { + $this->currentState = $state; + } + + /** + * @param Transition $transition + * + * @return mixed + */ + public function getData(Transition $transition) + { + return isset($this->data[$transition]) ? $this->data[$transition] : null; + } + + /** + * @param Transition $transition + * @param mixed $data + */ + public function setData(Transition $transition, $data) + { + $this->data[$transition] = $data; + } + + public function __clone() + { + $data = $this->data; + + $this->data = new SplObjectStorage(); + $this->data->addAll($data); + } +} diff --git a/src/Console/Usage/Scanner/Scanner.php b/src/Console/Usage/Scanner/Scanner.php new file mode 100644 index 00000000..efaa6404 --- /dev/null +++ b/src/Console/Usage/Scanner/Scanner.php @@ -0,0 +1,307 @@ + true, + ')' => true, + '[' => true, + ']' => true, + '|' => true, + '=' => true, + '<' => true, + '>' => true, + '.' => true, + ' ' => true + ]; + + /** + * @var \Generator + */ + private $generator; + + /** + * Peeked tokens + * + * @var Token + */ + private $peeked = null; + + /** + * Command & Usage Scanner + * + * @param string $text + */ + public function __construct($text) + { + $this->text = $text; + $this->length = strlen($text); + $this->generator = $this->getGenerator(); + } + + /** + * Peek Next Token + * + * @return Token + */ + public function peek() + { + return $this->peeked?:$this->peeked = $this->pop(); + } + + /** + * Fetch Next Token + * + * @return Token + */ + public function pop() + { + if ($token = $this->peeked) { + $this->peeked = null; + return $token; + } + + $token = $this->generator->current(); + $this->generator->next(); + return $token; + } + + public function current() + { + return $this->generator->current(); + } + + private function getGenerator() + { + for ($this->i = 0; $this->i < $this->length; ++$this->i) { + $token = null; + + switch ($this->text[$this->i]) { + case ' ': + $this->only(' '); + break; + + case '(': + $token = new Token(Token::T_REQUIRED_START, '('); + break; + + case ')': + $token = new Token(Token::T_REQUIRED_STOP, ')'); + break; + + case '[': + $token = new Token(Token::T_OPTIONAL_START, '['); + break; + + case ']': + $token = new Token(Token::T_OPTIONAL_STOP, ']'); + break; + + case '<': + $token = new Token(Token::T_ARGUMENT_START, '<'); + break; + + case '>': + $token = new Token(Token::T_ARGUMENT_STOP, '>'); + break; + + case '|': + $token = new Token(Token::T_MUTEX, '|'); + break; + + case '=': + $token = new Token(Token::T_EQUALS, '='); + break; + + case '-': + ++$this->i; + + if ($this->i == $this->length) { + $token = new Token(Token::T_SINGLE_DASH, '-'); + break; + } + + switch (true) { + case $this->is('-'): + // long option or double dash + + if (($this->i >= $this->length) || $this->is(' ')) { + $token = new Token(Token::T_DOUBLE_DASH, '--'); + break; + } + + $optionName = ''; + + while (($this->i < $this->length) && (($c = $this->isAlphanumeric()) || ($c = $this->is('-')) || ($c = $this->is('_')))) { + $optionName .= $c; + } + + if (strlen($optionName) == 0 || $optionName[0] == '-') { + throw new Exception('Expecting long option name'); + } + + $token = new Token(Token::T_LONG_OPTION, $optionName); + + --$this->i; + + break; + + case $this->isLetter(): + // short option or sequence of options + + --$this->i; + + $sequence = ''; + + while (($this->i < $this->length) && ($letter = $this->isLetter()) !== false) { + $sequence .= $letter; + } + + $token = (strlen($sequence) == 1) ? + new Token(Token::T_SHORT_OPTION, $sequence) : + new Token(Token::T_OPTION_SEQUENCE, $sequence); + + --$this->i; + + break; + + default: + --$this->i; + $token = new Token(Token::T_SINGLE_DASH, '-'); + } + + break; + + case '.': + if (strlen($this->only('.')) != 3) { + throw new Exception("Unexpected Character"); + } + + $token = new Token(Token::T_ELLIPSIS, '...'); + + break; + + default: + $text = $this->until($this->reserved); + $token = new Token(strtoupper($text) == 'OPTIONS' ? Token::T_OPTIONS : TOKEN::T_STRING, $text); + } + + if (!$token) { + continue; + } + + yield $token; + } + + yield new Token(Token::T_EOL); + } + + /** + * isLetter + * + * Test and consume if current position is a letter, otherwise + * return false. + * + * @return string|false Current character if letter, otherwise false + */ + private function isLetter() + { + if (!ctype_alpha($letter = $this->text[$this->i])) { + return false; + } + + ++$this->i; + + return $letter; + } + + /** + * @return bool + */ + private function isAlphanumeric() + { + if (!ctype_alnum($char = $this->text[$this->i])) { + return false; + } + + ++$this->i; + + return $char; + } + + /** + * is + * + * Test and consume if current position is a match, otherwise + * return false. + * + * @param string $match Single character to match against + * + * @return string|false Current character if match, otherwise false + */ + private function is($match) + { + if (($letter = $this->text[$this->i]) != $match) { + return false; + } + + ++$this->i; + + return $letter; + } + + /** + * Consume characters only while they match + * + * @param $seek + * + * @return string + */ + private function only($seek) + { + $seek = (is_array($seek))?$seek:[$seek => true]; + $value = ''; + + while (($this->i < $this->length) && (isset($seek[$char = $this->text[$this->i]]))) { + $value .= $char; + ++$this->i; + } + + --$this->i; + + return $value; + } + + /** + * Consume characters until we find a match + * + * @param $seek + * + * @return string + */ + private function until($seek) + { + $seek = (is_array($seek))?$seek:[$seek => true]; + $value = ''; + + while (($this->i < $this->length) && (!isset($seek[$char = $this->text[$this->i]]))) { + $value .= $char; + ++$this->i; + } + + --$this->i; + + return $value; + } +} diff --git a/src/Console/Usage/Scanner/Token.php b/src/Console/Usage/Scanner/Token.php new file mode 100644 index 00000000..e61cf80c --- /dev/null +++ b/src/Console/Usage/Scanner/Token.php @@ -0,0 +1,103 @@ + + const T_REQUIRED_START = 3; // ( + const T_REQUIRED_STOP = 4; // ) + const T_OPTIONAL_START = 5; // [ + const T_OPTIONAL_STOP = 6; // ] + const T_ELLIPSIS = 7; // ... + const T_MUTEX = 8; // | + const T_MINUS = 9; // - + const T_EQUALS = 10; // = + const T_WS = 11; // any amount of whitespace + const T_STRING = 12; // all other characters that are not reserved + const T_EOL = 13; // end of input + const T_SHORT_OPTION = 14; // -h + const T_OPTION_SEQUENCE = 15; // -iou + const T_DOUBLE_DASH = 16; // -- + const T_SINGLE_DASH = 17; // - + const T_LONG_OPTION = 18; // --option-name + const T_OPTIONS = 19; // options + + private $tokenToText = [ + self::T_ARGUMENT_START => '<', + self::T_ARGUMENT_STOP => '>', + self::T_REQUIRED_START => '(', + self::T_REQUIRED_STOP => ')', + self::T_OPTIONAL_START => '[', + self::T_OPTIONAL_STOP => ']', + self::T_MUTEX => '|', + self::T_MINUS => '-', + self::T_EQUALS => '=', + self::T_WS => 'WS', + self::T_STRING => 'STRING', + self::T_EOL => 'EOL', + self::T_SHORT_OPTION => 'SHORT_OPTION', + self::T_ELLIPSIS => '...', + self::T_DOUBLE_DASH => '--', + self::T_SINGLE_DASH => '-', + self::T_LONG_OPTION => 'LONG_OPTION' + ]; + + /** + * Token Type + * + * @var int + */ + + private $type; + + /** + * Token Value + * + * @var string + */ + private $value; + + /** + * Token + * + * @param int $type One of the predefined token constants + * @param string $value Optional value associated with the token + */ + public function __construct($type, $value = null) + { + $this->type = $type; + $this->value = is_null($value) ? $this->tokenToText[$type] : $value; + } + + /** + * Token Type + * + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * Token Value + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Human readable token + * + * @return string + */ + public function __toString() + { + return sprintf("Token('%s', '%s')", $this->tokenToText[$this->type], $this->value); + } +} diff --git a/src/FSM/Context.php b/src/FSM/Context.php new file mode 100644 index 00000000..7c0fafa5 --- /dev/null +++ b/src/FSM/Context.php @@ -0,0 +1,32 @@ +currentState; + } + + /** + * Set Current State + * + * @param State $state + * + * @return void + */ + public function setCurrentState(State $state) + { + $this->currentState = $state; + } +} diff --git a/src/FSM/Definition.php b/src/FSM/Definition.php new file mode 100644 index 00000000..14ba419b --- /dev/null +++ b/src/FSM/Definition.php @@ -0,0 +1,251 @@ +label = $label; + $this->runnerFactory = $runnerFactory; + } + + /** + * Add State + * + * @param State|string $state + * @param string $type + * + * @throws Exception + */ + public function addState($state, $type = State::TYPE_NORMAL) + { + if (!($state instanceof State)) { + $state = new DefaultState($state, $type); + } + + $label = (string) $state; + + if (isset($this->states[$label])) { + throw new Exception(sprintf("State %s already exists.", $label)); + } + + if (is_null($this->initialState)) { + $this->initialState = $state; + } + + $this->states[$label] = $state; + } + + /** + * Get State + * + * @param string $state + * @param string $type + * + * @return State + */ + public function getState($state, $type = State::TYPE_NORMAL) + { + if (!isset($this->states[$state])) { + $this->addState($state, $type); + } + + return $this->states[$state]; + } + + /** + * Add Transition + * + * Usage :- + * + * addTransition(Transition, From) + * addTransition(Label, From, To [,Guard [,Action]]) + * + * @param string|Transition $transition + * @param string|State $from + * @param string|State $to + * @param callable $guard + * @param string|callable $action + * + * @return void + */ + public function addTransition($transition, $from, $to = null, callable $guard = null, $action = null) + { + if (!($from instanceof State)) { + $from = $this->getState($from); + } + + if (!($transition instanceof Transition)) { + $transition = new DefaultTransition( + $transition, + ($to instanceof State) ? $to : $this->getState($to), + $guard, + $action + ); + } + + $from->addTransition($transition); + } + + /** + * Create Transition + * + * Fluid counterpart to addTransition + * + * @param string|State $state + * @param string $type + * + * @return DefinitionTransitionBuilder + */ + public function from($state, $type = State::TYPE_NORMAL) + { + return (new DefinitionTransitionBuilder($this))->from($state, $type); + } + + /** + * Accept Visitor + * + * @param StateVisitor $visitor + * + * @return void + */ + public function accept(StateVisitor $visitor) + { + $visited = []; + + foreach ($this->states as $state) { + $state->accept($visitor, $visited); + } + } + + /** + * Set Initial State + * + * @param State $initial + * + * @return void + * + * TODO: update to allow strings as well + */ + public function setInitialState(State $initial) + { + $this->initialState = $initial; + } + + /** + * Get Initial State + * + * @return State + * @throws Exception + */ + public function getInitialState() + { + if (is_null($this->initialState)) { + throw new Exception(); + } + + return $this->initialState; + } + + /** + * Build as FSM + * + * @param Stateful $context + * + * @return Runner + * @throws Exception + */ + public function toFSM(Stateful $context = null) + { + return $this->getRunnerFactory()->buildFSM($this->getInitialState(), $context); + } + + /** + * Build as Lambda + * + * @param Stateful $context + * + * @return callable + */ + public function toLambda(Stateful $context = null) + { + return $this->toFSM($context)->getLambda(); + } + + /** + * Perform a deep clone + */ + public function __clone() + { + $visited = []; + + foreach ($this->states as $k => $state) { + $this->states[$k] = $state->copy($visited); + } + + if ($this->initialState instanceof State) { + $this->setInitialState($visited[spl_object_hash($this->initialState)]); + } + } + + /** + * Get StepRunner Factory + * + * @return RunnerFactory + */ + private function getRunnerFactory() + { + if (is_null($this->runnerFactory)) { + $this->runnerFactory = new RunnerFactory(); + } + + return $this->runnerFactory; + } +} diff --git a/src/FSM/Runner/BacktrackingRunner.php b/src/FSM/Runner/BacktrackingRunner.php new file mode 100644 index 00000000..06920830 --- /dev/null +++ b/src/FSM/Runner/BacktrackingRunner.php @@ -0,0 +1,237 @@ +context = $context ? : new Context(); + $this->context->setCurrentState($initialState); + $this->graph = new GraphBuilder($initialState); + } + + /** + * Input + * + * @param mixed $input An array of symbols to pass into the FSM + * + * @return mixed[]|false + */ + public function input($input) + { + $this->input = is_array($input) ? new InputSequence($input) : $input; + $solution = $this->backtrack(); + + return $solution !== null ? $solution : false; + } + + /** + * Get Lambda + * + * @return callable + */ + public function getLambda() + { + return [$this, 'input']; + } + + /** + * Current Output + * + * @return mixed[] + */ + public function getOutput() + { + return $this->output; + } + + /** + * Backtrack + */ + private function backtrack() + { + if ($this->accept()) { + return $this->output; + } + + foreach ($this->candidates() as $candidate) { + if (!$candidate->can($this->input, $this->context, $this)) { + continue; + } + + $this->push(); + + $this->bt[] = $this->graph->transitions[$candidate]; + //echo implode(' ', $this->bt)."\n"; + + $solved = $this->apply($candidate); + $this->pop(); + + if ($solved !== null) { + return $solved; + } + } + + return null; + } + + /** + * Push + * + * @return void + */ + private function push() + { + $this->stack[] = [ + clone $this->input, + $this->output, + clone $this->context + ]; + } + + /** + * Pop + * + * @return void + */ + private function pop() + { + array_pop($this->bt); + //echo implode(' ', $this->bt)."\n"; + list ($this->input, $this->output, $this->context) + = array_pop($this->stack); + } + + /** + * Apply Transition + * + * @param Transition $candidate + * + * @return mixed[]|null + */ + private function apply($candidate) + { + if (($output = $candidate->apply($this->input, $this->context, $this)) !== null) { + $this->output[] = $output; + } + + return $this->backtrack(); + } + + /** + * Candidates + * + * @return Transition[] + */ + private function candidates() + { + return $this->context->getCurrentState()->getTransitions(); + } + + /** + * Accept + * + * Has the FSM reached a terminal state with all input consumed? + * + * @return bool + */ + private function accept() + { + if ($this->context->getCurrentState()->isTerminal() && count($this->input) == 0) { + return true; + } + + return false; + } + + /** + * Alias of input + * + * @param $input + * + * @return mixed + */ + public function __invoke($input) + { + return $this->input($input); + } + + /** + * Get Current State + * + * @return State + */ + public function getCurrentState() + { + return $this->context->getCurrentState(); + } +} diff --git a/src/FSM/Runner/InputSequence.php b/src/FSM/Runner/InputSequence.php new file mode 100644 index 00000000..349a1527 --- /dev/null +++ b/src/FSM/Runner/InputSequence.php @@ -0,0 +1,40 @@ +input = array_reverse($input); + } + + public function peek() + { + return end($this->input); + } + + public function pop() + { + return array_pop($this->input); + } + + public function push($symbol) + { + $this->input[] = $symbol; + } + + public function size() + { + return count($this->input); + } + + public function count() + { + return count($this->input); + } +} diff --git a/src/FSM/Runner/Runner.php b/src/FSM/Runner/Runner.php new file mode 100644 index 00000000..cc2c41ff --- /dev/null +++ b/src/FSM/Runner/Runner.php @@ -0,0 +1,39 @@ +defaultRunner($initialState, $context); + } +} diff --git a/src/FSM/Runner/SequenceRunner.php b/src/FSM/Runner/SequenceRunner.php new file mode 100644 index 00000000..67fd0991 --- /dev/null +++ b/src/FSM/Runner/SequenceRunner.php @@ -0,0 +1,201 @@ +setCurrentState($initialState); + + $this->originalContext = clone ($this->context = $context); + } + + /** + * Get Context + * + * @return Stateful + */ + public function getContext() + { + return $this->context; + } + + /** + * Set Context + * + * @param Stateful $context + * + * @return void + */ + public function setContext(Stateful $context) + { + $this->context = $context; + } + + /** + * Transitions available from current state + * + * @return Transition[] + */ + public function getTransitions() + { + return $this->context->getCurrentState()->getTransitions(); + } + + + /** + * Input + + * @param array|Traversable $input + * + * @return mixed[]|false + */ + public function input($input) + { + $this->context = clone $this->originalContext; + + $output = []; + + foreach ($input as $symbol) { + if (!($transition = $this->can($symbol))) { + return false; + } + + $output[] = $this->apply($symbol, $transition); + } + + if (!$this->context->getCurrentState()->isTerminal()) { + return false; + } + + return $output; + } + + /** + * @return callable + */ + public function getLambda() + { + return [$this, 'input']; + } + + /** + * Advance + * + * Attempt to advance the machine with the given input, optionally try + * and follow the exact path as given by the transition. + * + * @param mixed $input + * @param Transition $transition Take this path, otherwise try all paths + * + * @return mixed + * @throws Exception + */ + private function apply($input = null, Transition $transition = null) + { + $state = $this->context->getCurrentState(); + + if ($transition === null && !($transition = $this->can($input))) { + throw new StateException( + sprintf( + 'No transition from %s accepted %s', + (string)$state, + $input + ) + ); + } + + return $transition->apply($input, $this->context, $this); + } + + /** + * Can Advance + * + * Determine if the machine can be advanced, if so return the + * valid transition. + * + * Usage :- + * + * can(Input) + * can(Input, Transition) + * + * @param mixed $input Input against which transition(s) will be tested + * @param Transition $transition If specified only this transition will be tested + * + * @return Transition|false + * @throws Exception + */ + private function can($input, $transition = null) + { + $state = $this->context->getCurrentState(); + + if (!($transition instanceof Transition)) { + foreach ($state->getTransitions() as $candidate) { + if (!$candidate->can($input, $this->context, $this)) { + continue; + } + + return $candidate; + } + + return false; + } + + return $transition->can($input, $this->context, $this) ? $transition : false; + } + + /** + * Alias of input + * + * @param $input + * + * @return mixed + */ + public function __invoke($input) + { + return $this->input($input); + } + + /** + * Get Current State + * + * @return State + */ + public function getCurrentState() + { + return $this->context->getCurrentState(); + } +} diff --git a/src/FSM/Runner/StepRunner.php b/src/FSM/Runner/StepRunner.php new file mode 100644 index 00000000..2e9d6947 --- /dev/null +++ b/src/FSM/Runner/StepRunner.php @@ -0,0 +1,178 @@ +context = $context ? : new Context(); + $this->context->setCurrentState($initialState); + } + + /** + * Get Context + * + * @return Stateful + */ + public function getContext() + { + return $this->context; + } + + /** + * Set Context + * + * @param Stateful $context + * + * @return void + */ + public function setContext(Stateful $context) + { + $this->context = $context; + } + + /** + * Transitions available from current state + * + * @return Transition[] + */ + public function getTransitions() + { + return $this->context->getCurrentState()->getTransitions(); + } + + /** + * Advance + * + * Attempt to advance the machine with the given input, optionally try + * and follow the exact path as given by the transition. + * + * @param mixed $input + * @param Transition $transition Take this path, otherwise try all paths + * + * @return mixed + * @throws Exception + */ + public function apply($input = null, Transition $transition = null) + { + $state = $this->context->getCurrentState(); + + if ($transition === null && !($transition = $this->can($input))) { + throw new StateException( + sprintf( + 'No transition from %s accepted %s', + (string)$state, + $input + ) + ); + } + + return $transition->apply($input, $this->context, $this); + } + + /** + * Can Advance + * + * Determine if the machine can be advanced, if so return the + * valid transition. + * + * Usage :- + * + * can(Input) + * can(Input, Transition) + * + * @param mixed $input Input against which transition(s) will be tested + * @param Transition $transition If specified only this transition will be tested + * + * @return Transition|false + * @throws Exception + */ + public function can($input, $transition = null) + { + $state = $this->context->getCurrentState(); + + if (!($transition instanceof Transition)) { + foreach ($state->getTransitions() as $candidate) { + if (!$candidate->can($input, $this->context, $this)) { + continue; + } + + return $candidate; + } + + return false; + } + + return $transition->can($input, $this->context, $this) ? $transition : false; + } + + /** + * Get Lambda + * + * @return callable + */ + public function getLambda() + { + return [$this, 'apply']; + } + + /** + * Input + * + * @param $input + * + * @return mixed + */ + public function input($input) + { + return $this->apply($input); + } + + /** + * Alias of Input + * + * @param $input + * + * @return mixed + */ + public function __invoke($input) + { + return $this->apply($input); + } + + + /** + * Get Current State + * + * @return State + */ + public function getCurrentState() + { + return $this->context->getCurrentState(); + } +} diff --git a/src/FSM/State/DefaultState.php b/src/FSM/State/DefaultState.php new file mode 100644 index 00000000..eef320d8 --- /dev/null +++ b/src/FSM/State/DefaultState.php @@ -0,0 +1,187 @@ +label = $label; + $this->type = $type; + } + + /** + * Add Transition + * + * Usage :- + * + * addTransition(Transition) + * addTransition(Label, To [,Guard [,Action]]) + * + * @param string|Transition $transition + * @param State $to + * @param callable $guard + * @param callable $action + * + * @return void + */ + public function addTransition($transition, State $to = null, $guard = null, $action = null) + { + if (!($transition instanceof Transition)) { + $transition = new DefaultTransition($transition, $to, $guard, $action); + } + + $this->transitions[] = $transition; + } + + /** + * Set Type + * + * @param string $type + * + * @return void + */ + public function setType($type) + { + $this->type = $type; + } + + /** + * Is Initial + * + * @return bool + */ + public function isInitial() + { + return $this->type == self::TYPE_INITIAL; + } + + /** + * Is Normal + * + * @return bool + */ + public function isNormal() + { + return $this->type == self::TYPE_NORMAL; + } + + /** + * Is Terminal + * + * @return bool + */ + public function isTerminal() + { + return $this->type == self::TYPE_TERMINAL; + } + + /** + * Get Transitions + * + * @return Transition[] + */ + public function getTransitions() + { + return $this->transitions; + } + + /** + * Create Transition + * + * Fluid counterpart to addTransition + * + * @param string $label + * @param callable $guard + * + * @return StateTransitionBuilder + */ + public function when($label, callable $guard = null) + { + return (new StateTransitionBuilder($this))->when($label, $guard); + } + + /** + * Copy State + * + * Perform a deep clone + * + * @param State[] $visited + * + * @return State + */ + public function copy(&$visited = []) + { + $hash = spl_object_hash($this); + + if (isset($visited[$hash])) { + return $visited[$hash]; + } + + $copy = $visited[$hash] = new DefaultState($this->label, $this->type); + + foreach ($this->transitions as $transition) { + $copy->addTransition($transition->copy($visited)); + } + + return $copy; + } + + /** + * Accept Visitor + * + * @param StateVisitor $visitor + * @param State[] $visited + * + * @return void + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + if (!isset($visited[$hash = spl_object_hash($this)])) { + $visitor->visit($visited[$hash] = $this); + + foreach ($this->transitions as $transition) { + $transition->accept($visitor, $visited); + } + } + } + + /** + * Get Label + * + * @return string + */ + public function __toString() + { + return $this->label; + } +} diff --git a/src/FSM/State/State.php b/src/FSM/State/State.php new file mode 100644 index 00000000..34f7ebdb --- /dev/null +++ b/src/FSM/State/State.php @@ -0,0 +1,111 @@ +from = $from; + } + + /** + * To + * + * @param State $state + * + * @return $this + */ + public function to($state) + { + $this->to = $state; + + return $this; + } + + /** + * When + * + * @param string $label + * @param callable $guard + * + * @return $this + */ + public function when($label, callable $guard = null) + { + $this->label = $label; + $this->guard = $guard; + + return $this; + } + + /** + * Then + * + * @param string $action + * + * @return $this + */ + public function then($action) + { + $this->action = $action; + + return $this; + } + + /** + * Done + * + * @return Definition + * @throws Exception + */ + public function done() + { + if ($this->to === null) { + throw new Exception('Missing To State'); + } + + if ($this->label == null) { + throw new Exception('Missing Label'); + } + + $this->from->addTransition(new DefaultTransition($this->label, $this->to, $this->guard, $this->action)); + + return $this->from; + } +} diff --git a/src/FSM/State/StateVisitor.php b/src/FSM/State/StateVisitor.php new file mode 100644 index 00000000..d1e43c4d --- /dev/null +++ b/src/FSM/State/StateVisitor.php @@ -0,0 +1,17 @@ +label = $label; + $this->to = $to; + $this->guard = $guard; + $this->action = $action; + } + + /** + * Get To + * + * @return State + */ + public function getTo() + { + return $this->to; + } + + /** + * Set To + * + * @param State $state + * + * @return void + */ + public function setTo(State $state) + { + $this->to = $state; + } + + /** + * Accept Visitor + * + * @param StateVisitor $visitor + * @param State[] $visited + * + * @return void + */ + public function accept(StateVisitor $visitor, &$visited = []) + { + $this->to->accept($visitor, $visited); + } + + /** + * Can Accept Input + * + * @param mixed $input + * @param Stateful $context + * @param Runner $runner + * + * @return bool + */ + public function can($input, Stateful $context, Runner $runner) + { + if (!is_null($this->guard)) { + return call_user_func($this->guard, $input, $context, $runner, $this); + } + + if ($input instanceof InputSequence) { + if ($input->size() == 0) { + return false; + } + + if ($input->peek() == $this->label) { + return true; + } + } + + return $this->label == $input; + } + + /** + * Apply Transition + * + * @param mixed $input + * @param Stateful $context + * @param Runner $runner + * + * @return mixed + */ + public function apply($input, Stateful $context, Runner $runner) + { + $context->setCurrentState($this->to); + + if ($this->action !== null && is_callable($this->action)) { + return call_user_func($this->action, $input, $context, $runner, $this); + } + + if ($input instanceof InputSequence) { + $input->pop(); + } + + return $this->action?:(string)$this->to; + } + + /** + * Copy + * + * Perform deep clone + * + * @param State[] $visited + * + * @return Transition + */ + public function copy(&$visited = []) + { + return new DefaultTransition($this->label, $this->to->copy($visited), $this->guard, $this->action); + } + + /** + * Get Label + * + * @return string + */ + public function __toString() + { + return $this->label; + } +} diff --git a/src/FSM/Transition/DefinitionTransitionBuilder.php b/src/FSM/Transition/DefinitionTransitionBuilder.php new file mode 100644 index 00000000..9d931d1c --- /dev/null +++ b/src/FSM/Transition/DefinitionTransitionBuilder.php @@ -0,0 +1,154 @@ +definition = $definition; + } + + /** + * From + * + * @param string|State $state + * @param string $type + * + * @return $this + */ + public function from($state, $type = State::TYPE_NORMAL) + { + if (!($state instanceof State)) { + $state = $this->definition->getState($state, $type); + } + + $this->from = $state; + + return $this; + } + + /** + * To + * + * @param string|State $state + * @param string $type + * + * @return $this + */ + public function to($state, $type = State::TYPE_NORMAL) + { + if (!($state instanceof State)) { + $state = $this->definition->getState($state, $type); + } + + $this->to = $state; + + return $this; + } + + /** + * When + * + * @param string $label + * @param callable $guard + * + * @return $this + */ + public function when($label, callable $guard = null) + { + $this->label = $label; + $this->guard = $guard; + + return $this; + } + + /** + * Then + * + * @param string $action + * + * @return $this + */ + public function then($action) + { + $this->action = $action; + + return $this; + } + + /** + * Done + * + * @return Definition + * @throws Exception + */ + public function done() + { + if ($this->from === null) { + throw new Exception('Missing From State'); + } + + if ($this->to === null) { + throw new Exception('Missing To State'); + } + + if ($this->label == null) { + throw new Exception('Missing Label'); + } + + $this->from->addTransition(new DefaultTransition($this->label, $this->to, $this->guard, $this->action)); + + return $this->definition; + } +} diff --git a/src/FSM/Transition/Transition.php b/src/FSM/Transition/Transition.php new file mode 100644 index 00000000..c2d08772 --- /dev/null +++ b/src/FSM/Transition/Transition.php @@ -0,0 +1,78 @@ +reset(); + + $this->write('digraph machine {'); + $graph->accept($this); + $this->write('}'); + + return $this->output; + } + + /** + * Visit State + * + * @param State $state + * + * @return void + */ + public function visit(State $state) + { + $stateId = $this->getStateId($state); + + $attributes = []; + $attributes['label'] = $stateId; + $attributes['shape'] = 'circle'; + + if ($state->isTerminal()) { + $attributes['shape'] = 'doublecircle'; + } + + $this->write( + sprintf( + '%s ['.$this->formatAttributes($attributes).'];', + $stateId, + $stateId + ) + ); + + foreach ($state->getTransitions() as $transition) { + $this->write( + sprintf( + '%s -> %s [label="%s"];', + $stateId, + $this->getStateId($transition->getTo()), + $this->getTransitionId($transition).'('.(string)$transition.')' + ) + ); + } + } + + private function formatAttributes($attributes) + { + $formatted = []; + + foreach ($attributes as $k => $v) { + $formatted[] = $k.'="'.$v.'"'; + } + + return implode(' ', $formatted); + } + + /** + * Append output + * + * @param string $output + */ + private function write($output) + { + $this->output .= $output."\n"; + } + + /** + * Prepare for outputting a new graph + */ + private function reset() + { + $this->states = []; + $this->lastStateId = 0; + $this->lastTransitionId = 0; + $this->output = ''; + } + + /** + * Get State ID + * + * @param State $state + * + * @return string + */ + private function getStateId(State $state) + { + $hash = spl_object_hash($state); + + if (!isset($this->states[$hash])) { + $this->states[$hash] = 'S'.(++$this->lastStateId); + } + + return $this->states[$hash]; + } + + /** + * Get Transition ID + * + * @param Transition $transition + * + * @return string + */ + private function getTransitionId(Transition $transition) + { + return 'T'.(++$this->lastTransitionId); + } +} diff --git a/src/FSM/Utility/GraphBuilder.php b/src/FSM/Utility/GraphBuilder.php new file mode 100644 index 00000000..04b55e1b --- /dev/null +++ b/src/FSM/Utility/GraphBuilder.php @@ -0,0 +1,38 @@ +transitions = new SplObjectStorage(); + $state->accept($this); + } + + /** + * Visit State + * + * @param State $state + * + * @return void + */ + public function visit(State $state) + { + foreach ($state->getTransitions() as $transition) { + $this->transitions[$transition] = 'T'.(++$this->id); + } + } +} diff --git a/src/Types/Crypt/Builder.php b/src/Types/Crypt/Builder.php index 5fa669a1..242d91d9 100644 --- a/src/Types/Crypt/Builder.php +++ b/src/Types/Crypt/Builder.php @@ -3,7 +3,7 @@ namespace my127\Workspace\Types\Crypt; use Exception; -use my127\Console\Usage\Input; +use my127\Workspace\Console\Usage\Input; use my127\Workspace\Application; use my127\Workspace\Definition\Collection as DefinitionCollection; use my127\Workspace\Environment\Builder as EnvironmentBuilder; diff --git a/src/Types/Workspace/Builder.php b/src/Types/Workspace/Builder.php index 7cf8614f..28d0ebe9 100644 --- a/src/Types/Workspace/Builder.php +++ b/src/Types/Workspace/Builder.php @@ -2,9 +2,9 @@ namespace my127\Workspace\Types\Workspace; -use my127\Console\Application\Event\BeforeActionEvent; -use my127\Console\Application\Executor; -use my127\Console\Usage\Input; +use my127\Workspace\Console\Application\Event\BeforeActionEvent; +use my127\Workspace\Console\Application\Executor; +use my127\Workspace\Console\Usage\Input; use my127\Workspace\Application; use my127\Workspace\Definition\Collection as DefinitionCollection; use my127\Workspace\Definition\Definition as WorkspaceDefinition; diff --git a/src/Types/Workspace/Workspace.php b/src/Types/Workspace/Workspace.php index 4a2028be..5af75914 100644 --- a/src/Types/Workspace/Workspace.php +++ b/src/Types/Workspace/Workspace.php @@ -3,9 +3,9 @@ namespace my127\Workspace\Types\Workspace; use ArrayAccess; -use my127\Console\Usage\Input; -use my127\Console\Usage\Model\BooleanOptionValue; -use my127\Console\Usage\Model\StringOptionValue; +use my127\Workspace\Console\Usage\Input; +use my127\Workspace\Console\Usage\Model\BooleanOptionValue; +use my127\Workspace\Console\Usage\Model\StringOptionValue; use my127\Workspace\Path\Path; use my127\Workspace\Terminal\Terminal; use my127\Workspace\Types\Attribute\Collection as AttributeCollection;