chore(dependency): add conventional changelog

This commit is contained in:
grandeljay 2023-12-01 15:08:54 +01:00
parent 1ae6f74b44
commit 960e972940
257 changed files with 49780 additions and 13 deletions

View file

@ -11,11 +11,20 @@
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"wp-coding-standards/wpcs": "^3.0",
"phpunit/phpunit": "10"
"phpunit/phpunit": "10",
"marcocesarato/php-conventional-changelog": "^1.17"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
},
"scripts": {
"changelog": "conventional-changelog",
"release": "conventional-changelog --commit",
"release:patch": "conventional-changelog --patch --commit",
"release:minor": "conventional-changelog --minor --commit",
"release:major": "conventional-changelog --major --commit"
},
"version": "1.1.2"
}

791
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "415b91125309894f3be47a7378723764",
"content-hash": "fd3daacfaa5b239d111c85b34b8721f4",
"packages": [
{
"name": "composer/ca-bundle",
@ -1192,6 +1192,83 @@
},
"time": "2023-01-05T11:28:13+00:00"
},
{
"name": "marcocesarato/php-conventional-changelog",
"version": "1.17.0",
"source": {
"type": "git",
"url": "https://github.com/marcocesarato/php-conventional-changelog.git",
"reference": "9269b0a3198d2107322f9f9a0fca399719825f67"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/marcocesarato/php-conventional-changelog/zipball/9269b0a3198d2107322f9f9a0fca399719825f67",
"reference": "9269b0a3198d2107322f9f9a0fca399719825f67",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=7.1.3",
"symfony/console": "^4 || ^5 || ^6"
},
"require-dev": {
"brainmaestro/composer-git-hooks": "^2.8",
"friendsofphp/php-cs-fixer": "^3.8",
"php-mock/php-mock": "^2.3",
"php-mock/php-mock-phpunit": "^2.6",
"phpunit/phpunit": "^9.6"
},
"bin": [
"conventional-changelog"
],
"type": "library",
"extra": {
"hooks": {
"pre-commit": "composer fix-cs",
"pre-push": "composer check-cs",
"post-merge": "composer install"
}
},
"autoload": {
"psr-4": {
"ConventionalChangelog\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"authors": [
{
"name": "Marco Cesarato",
"email": "cesarato.developer@gmail.com"
}
],
"description": "Generate changelogs and release notes from a project's commit messages and metadata and automate versioning with semver.org and conventionalcommits.org",
"keywords": [
"changelog",
"commit",
"commits",
"convention",
"conventional",
"conventional-changelog",
"conventional-changelog-preset",
"conventional-commit",
"conventional-commits",
"conventionalcommits",
"generation",
"git",
"history",
"php",
"readme",
"tag"
],
"support": {
"issues": "https://github.com/marcocesarato/php-conventional-changelog/issues",
"source": "https://github.com/marcocesarato/php-conventional-changelog/tree/v1.17.0"
},
"time": "2023-03-26T16:59:30+00:00"
},
{
"name": "myclabs/deep-copy",
"version": "1.11.1",
@ -1975,6 +2052,59 @@
],
"time": "2023-02-03T07:16:15+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/2.0.2"
},
"time": "2021-11-05T16:47:00+00:00"
},
{
"name": "sebastian/cli-parser",
"version": "2.0.0",
@ -2947,6 +3077,665 @@
},
"time": "2023-02-22T23:07:41+00:00"
},
{
"name": "symfony/console",
"version": "v6.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/cd9864b47c367450e14ab32f78fdbf98c44c26b6",
"reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0|^7.0"
},
"conflict": {
"symfony/dependency-injection": "<5.4",
"symfony/dotenv": "<5.4",
"symfony/event-dispatcher": "<5.4",
"symfony/lock": "<5.4",
"symfony/process": "<5.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/lock": "^5.4|^6.0|^7.0",
"symfony/messenger": "^5.4|^6.0|^7.0",
"symfony/process": "^5.4|^6.0|^7.0",
"symfony/stopwatch": "^5.4|^6.0|^7.0",
"symfony/var-dumper": "^5.4|^6.0|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command-line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.4.0"
},
"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": "2023-11-20T16:41:16+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
},
"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": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"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": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "875e90aeea2777b6f135677f618529449334a612"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
"reference": "875e90aeea2777b6f135677f618529449334a612",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
},
"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": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
},
"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": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"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": "2023-07-28T09:04:16+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
"reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/container": "^2.0"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
},
"exclude-from-classmap": [
"/Test/"
]
},
"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 writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.4.0"
},
"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": "2023-07-30T20:28:31+00:00"
},
{
"name": "symfony/string",
"version": "v7.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
"reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.0.0"
},
"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": "2023-11-29T08:40:23+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",

View file

@ -4,5 +4,6 @@
"@fontsource/raleway": "^4.5.10",
"fomantic-ui": "^2.9.0",
"html2canvas": "^1.4.1"
}
},
"version": "1.1.2"
}

View file

@ -8,6 +8,7 @@ $baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php',
'PHPCSUtils\\BackCompat\\BCFile' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php',
'PHPCSUtils\\BackCompat\\BCTokens' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php',

View file

@ -6,6 +6,12 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',

View file

@ -7,10 +7,18 @@ $baseDir = dirname($vendorDir);
return array(
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Slim\\Psr7\\' => array($vendorDir . '/slim/psr7/src'),
'Qferrer\\Mjml\\' => array($vendorDir . '/qferr/mjml-php/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'),
'ML\\JsonLD\\' => array($vendorDir . '/ml/json-ld'),
@ -22,5 +30,6 @@ return array(
'Fig\\Http\\Message\\' => array($vendorDir . '/fig/http-message-util/src'),
'Embed\\' => array($vendorDir . '/embed/embed/src'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
'ConventionalChangelog\\' => array($vendorDir . '/marcocesarato/php-conventional-changelog/src'),
'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
);

View file

@ -7,6 +7,12 @@ namespace Composer\Autoload;
class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
@ -18,6 +24,13 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Component\\String\\' => 25,
'Symfony\\Component\\Console\\' => 26,
'Slim\\Psr7\\' => 10,
),
'Q' =>
@ -28,6 +41,7 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
array (
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'Psr\\Container\\' => 14,
'PhpParser\\' => 10,
'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 57,
),
@ -63,6 +77,7 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
),
'C' =>
array (
'ConventionalChangelog\\' => 22,
'Composer\\CaBundle\\' => 18,
),
);
@ -72,6 +87,34 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
),
'Symfony\\Polyfill\\Intl\\Grapheme\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Slim\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/slim/psr7/src',
@ -89,6 +132,10 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'PhpParser\\' =>
array (
0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
@ -134,6 +181,10 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
array (
0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy',
),
'ConventionalChangelog\\' =>
array (
0 => __DIR__ . '/..' . '/marcocesarato/php-conventional-changelog/src',
),
'Composer\\CaBundle\\' =>
array (
0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
@ -153,6 +204,7 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php',
'PHPCSUtils\\BackCompat\\BCFile' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php',
'PHPCSUtils\\BackCompat\\BCTokens' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php',

View file

@ -640,6 +640,86 @@
},
"install-path": "../jaybizzle/crawler-detect"
},
{
"name": "marcocesarato/php-conventional-changelog",
"version": "1.17.0",
"version_normalized": "1.17.0.0",
"source": {
"type": "git",
"url": "https://github.com/marcocesarato/php-conventional-changelog.git",
"reference": "9269b0a3198d2107322f9f9a0fca399719825f67"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/marcocesarato/php-conventional-changelog/zipball/9269b0a3198d2107322f9f9a0fca399719825f67",
"reference": "9269b0a3198d2107322f9f9a0fca399719825f67",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=7.1.3",
"symfony/console": "^4 || ^5 || ^6"
},
"require-dev": {
"brainmaestro/composer-git-hooks": "^2.8",
"friendsofphp/php-cs-fixer": "^3.8",
"php-mock/php-mock": "^2.3",
"php-mock/php-mock-phpunit": "^2.6",
"phpunit/phpunit": "^9.6"
},
"time": "2023-03-26T16:59:30+00:00",
"bin": [
"conventional-changelog"
],
"type": "library",
"extra": {
"hooks": {
"pre-commit": "composer fix-cs",
"pre-push": "composer check-cs",
"post-merge": "composer install"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"ConventionalChangelog\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-3.0-or-later"
],
"authors": [
{
"name": "Marco Cesarato",
"email": "cesarato.developer@gmail.com"
}
],
"description": "Generate changelogs and release notes from a project's commit messages and metadata and automate versioning with semver.org and conventionalcommits.org",
"keywords": [
"changelog",
"commit",
"commits",
"convention",
"conventional",
"conventional-changelog",
"conventional-changelog-preset",
"conventional-commit",
"conventional-commits",
"conventionalcommits",
"generation",
"git",
"history",
"php",
"readme",
"tag"
],
"support": {
"issues": "https://github.com/marcocesarato/php-conventional-changelog/issues",
"source": "https://github.com/marcocesarato/php-conventional-changelog/tree/v1.17.0"
},
"install-path": "../marcocesarato/php-conventional-changelog"
},
{
"name": "ml/iri",
"version": "1.1.4",
@ -1625,6 +1705,62 @@
],
"install-path": "../phpunit/phpunit"
},
{
"name": "psr/container",
"version": "2.0.2",
"version_normalized": "2.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"time": "2021-11-05T16:47:00+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/2.0.2"
},
"install-path": "../psr/container"
},
{
"name": "psr/http-client",
"version": "1.0.2",
@ -2994,6 +3130,515 @@
},
"install-path": "../squizlabs/php_codesniffer"
},
{
"name": "symfony/console",
"version": "v6.4.0",
"version_normalized": "6.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/cd9864b47c367450e14ab32f78fdbf98c44c26b6",
"reference": "cd9864b47c367450e14ab32f78fdbf98c44c26b6",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^5.4|^6.0|^7.0"
},
"conflict": {
"symfony/dependency-injection": "<5.4",
"symfony/dotenv": "<5.4",
"symfony/event-dispatcher": "<5.4",
"symfony/lock": "<5.4",
"symfony/process": "<5.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/lock": "^5.4|^6.0|^7.0",
"symfony/messenger": "^5.4|^6.0|^7.0",
"symfony/process": "^5.4|^6.0|^7.0",
"symfony/stopwatch": "^5.4|^6.0|^7.0",
"symfony/var-dumper": "^5.4|^6.0|^7.0"
},
"time": "2023-11-20T16:41:16+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command-line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v6.4.0"
},
"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"
}
],
"install-path": "../symfony/console"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.4.0",
"version_normalized": "3.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"time": "2023-05-23T14:45:45+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
},
"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"
}
],
"install-path": "../symfony/deprecation-contracts"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.28.0",
"version_normalized": "1.28.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"time": "2023-01-26T09:26:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"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"
}
],
"install-path": "../symfony/polyfill-ctype"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.28.0",
"version_normalized": "1.28.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "875e90aeea2777b6f135677f618529449334a612"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
"reference": "875e90aeea2777b6f135677f618529449334a612",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"time": "2023-01-26T09:26:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
},
"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"
}
],
"install-path": "../symfony/polyfill-intl-grapheme"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.28.0",
"version_normalized": "1.28.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"time": "2023-01-26T09:26:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
},
"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"
}
],
"install-path": "../symfony/polyfill-intl-normalizer"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.28.0",
"version_normalized": "1.28.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"time": "2023-07-28T09:04:16+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"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"
}
],
"install-path": "../symfony/polyfill-mbstring"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.28.0",
@ -3080,6 +3725,180 @@
],
"install-path": "../symfony/polyfill-php80"
},
{
"name": "symfony/service-contracts",
"version": "v3.4.0",
"version_normalized": "3.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
"reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/container": "^2.0"
},
"conflict": {
"ext-psr": "<1.1|>=2"
},
"time": "2023-07-30T20:28:31+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
},
"exclude-from-classmap": [
"/Test/"
]
},
"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 writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.4.0"
},
"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"
}
],
"install-path": "../symfony/service-contracts"
},
{
"name": "symfony/string",
"version": "v7.0.0",
"version_normalized": "7.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
"reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^6.4|^7.0"
},
"time": "2023-11-29T08:40:23+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.0.0"
},
"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"
}
],
"install-path": "../symfony/string"
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",
@ -3206,6 +4025,7 @@
"dev": true,
"dev-package-names": [
"dealerdirect/phpcodesniffer-composer-installer",
"marcocesarato/php-conventional-changelog",
"myclabs/deep-copy",
"nikic/php-parser",
"phar-io/manifest",
@ -3218,6 +4038,7 @@
"phpunit/php-text-template",
"phpunit/php-timer",
"phpunit/phpunit",
"psr/container",
"sebastian/cli-parser",
"sebastian/code-unit",
"sebastian/code-unit-reverse-lookup",
@ -3234,6 +4055,14 @@
"sebastian/type",
"sebastian/version",
"squizlabs/php_codesniffer",
"symfony/console",
"symfony/deprecation-contracts",
"symfony/polyfill-ctype",
"symfony/polyfill-intl-grapheme",
"symfony/polyfill-intl-normalizer",
"symfony/polyfill-mbstring",
"symfony/service-contracts",
"symfony/string",
"theseer/tokenizer",
"wp-coding-standards/wpcs"
]

View file

@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => 'e01b7e170146c4ec435ab28dada918ff171996e4',
'reference' => '1ae6f74b44e2f24e28b1e895bbcc73c0773fdaad',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-develop',
'version' => 'dev-develop',
'reference' => 'e01b7e170146c4ec435ab28dada918ff171996e4',
'reference' => '1ae6f74b44e2f24e28b1e895bbcc73c0773fdaad',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -100,6 +100,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'marcocesarato/php-conventional-changelog' => array(
'pretty_version' => '1.17.0',
'version' => '1.17.0.0',
'reference' => '9269b0a3198d2107322f9f9a0fca399719825f67',
'type' => 'library',
'install_path' => __DIR__ . '/../marcocesarato/php-conventional-changelog',
'aliases' => array(),
'dev_requirement' => true,
),
'ml/iri' => array(
'pretty_version' => '1.1.4',
'version' => '1.1.4.0',
@ -235,6 +244,15 @@
'aliases' => array(),
'dev_requirement' => true,
),
'psr/container' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'dev_requirement' => true,
),
'psr/http-client' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
@ -274,6 +292,12 @@
0 => '1.0',
),
),
'psr/log-implementation' => array(
'dev_requirement' => true,
'provided' => array(
0 => '1.0|2.0|3.0',
),
),
'qferr/mjml-php' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
@ -445,6 +469,60 @@
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/console' => array(
'pretty_version' => 'v6.4.0',
'version' => '6.4.0.0',
'reference' => 'cd9864b47c367450e14ab32f78fdbf98c44c26b6',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/console',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.4.0',
'version' => '3.4.0.0',
'reference' => '7c3aff79d10325257a001fcf92d991f24fc967cf',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => 'ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/polyfill-intl-grapheme' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => '875e90aeea2777b6f135677f618529449334a612',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => '8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
'reference' => '42292d99c55abe617799667f454222c54c60e229',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.28.0',
'version' => '1.28.0.0',
@ -454,6 +532,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/service-contracts' => array(
'pretty_version' => 'v3.4.0',
'version' => '3.4.0.0',
'reference' => 'b3313c2dbffaf71c8de2934e2ea56ed2291a3838',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/service-contracts',
'aliases' => array(),
'dev_requirement' => true,
),
'symfony/string' => array(
'pretty_version' => 'v7.0.0',
'version' => '7.0.0.0',
'reference' => '92bd2bfbba476d4a1838e5e12168bef2fd1e6620',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/string',
'aliases' => array(),
'dev_requirement' => true,
),
'theseer/tokenizer' => array(
'pretty_version' => '1.2.1',
'version' => '1.2.1.0',

View file

@ -0,0 +1,52 @@
<?php
namespace ConventionalChangelog;
use ConventionalChangelog\Git\Repository;
use ConventionalChangelog\Helper\SemanticVersion;
$releaseMessagePrefix = 'chore(release): ';
return [
'types' => ['feat', 'fix', 'perf', 'docs', 'chore'],
// Ignore changelogs
'ignorePatterns' => [
'/' . preg_quote($releaseMessagePrefix, '/') . '.*/i',
'/chore\(changelog\)[:].*/i',
],
'releaseCommitMessageFormat' => "{$releaseMessagePrefix}{{currentTag}}",
'postRun' => function () use ($releaseMessagePrefix) {
$lastTag = Repository::getLastTagRefname();
$lastTagCommit = Repository::getLastTagRefnameCommit();
$lastCommit = Repository::getLastCommit();
if ($lastTagCommit !== $lastCommit) {
return;
}
$binFile = __DIR__ . '/conventional-changelog';
$readmeFile = __DIR__ . '/README.md';
$semverRegex = SemanticVersion::PATTERN;
// Get version
$version = new SemanticVersion($lastTag);
// Update version on readme
$readme = file_get_contents($readmeFile);
$readme = preg_replace("/(https:\/\/img\.shields\.io\/badge\/version)-({$semverRegex})-/m", '$1-' . $version->getVersion() . '-', $readme);
file_put_contents($readmeFile, $readme);
// Update version on bin
$bin = file_get_contents($binFile);
$bin = preg_replace("/(\('conventional-changelog', )'({$semverRegex})'/m", '$1\'' . $version->getVersion() . '\'', $bin);
file_put_contents($binFile, $bin);
// Delete tag
Repository::deleteTag($lastTag);
// Commit and tag
$message = $releaseMessagePrefix . $version->getVersion();
Repository::commit($message, [$binFile, $readmeFile], true, true, true);
Repository::tag($lastTag);
},
];

View file

@ -0,0 +1,15 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.195.0/containers/php/.devcontainer/base.Dockerfile
# [Choice] PHP version (use -bullseye variants on local arm64/Apple Silicon): 8, 8.0, 7, 7.4, 7.3, 8-bullseye, 8.0-bullseye, 7-bullseye, 7.4-bullseye, 7.3-bullseye, 8-buster, 8.0-buster, 7-buster, 7.4-buster, 7.3-buster
ARG VARIANT=8-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View file

@ -0,0 +1,55 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.195.0/containers/php
{
"name": "PHP",
"build": {
"dockerfile": "Dockerfile",
"args": {
// Update VARIANT to pick a PHP version: 8, 8.0, 7, 7.4, 7.3
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "8-bullseye",
"NODE_VERSION": "lts/*"
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"php.validate.executablePath": "/usr/local/bin/php"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"felixfbecker.php-debug",
"bmewburn.vscode-intelephense-client",
"mrmlnc.vscode-apache"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [8080],
// Use 'portsAttributes' to set default properties for specific forwarded ports. More info: https://code.visualstudio.com/docs/remote/devcontainerjson-reference.
"portsAttributes": {
"8000": {
"label": "Hello Remote World",
"onAutoForward": "notify"
}
},
// Use 'otherPortsAttributes' to configure any ports that aren't configured using 'portsAttributes'.
// "otherPortsAttributes": {
// "onAutoForward": "silent"
// },
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html"
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View file

@ -0,0 +1 @@
eol=lf

View file

@ -0,0 +1,42 @@
name: Code Checker
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Update composer
run: composer update
- name: Validate composer.json and composer.lock
run: composer validate
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: composer install --prefer-dist --no-progress --no-suggest
- name: PHP Lint
uses: davidlienhard/php-simple-lint@1
with:
folder: './'
ignore: './vendor/\*'
- uses: symfonycorp/security-checker-action@v5

View file

@ -0,0 +1,12 @@
name: Pull Request
on: pull_request
jobs:
conventional:
name: Conventional PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v2-beta
- uses: beemojs/conventional-pr-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -0,0 +1,5 @@
vendor/**
.idea
.php-cs-fixer.cache
cghooks.lock
/.phpunit.result.cache

View file

@ -0,0 +1,47 @@
<?php
$finder = PhpCsFixer\Finder::create()
->ignoreDotFiles(false)
->ignoreVCS(true)
->exclude('vendor')
->name('.changelog')
->name('.php_cs')
->name('conventional-changelog')
->in(__DIR__);
$config = new PhpCsFixer\Config();
return $config
->setUsingCache(true)
->setRiskyAllowed(true)
->setCacheFile(__DIR__ . '/.php-cs-fixer.cache')
->setRules([
'@PSR1' => true,
'@PSR2' => true,
'@Symfony' => true,
'psr_autoloading' => true,
// Custom rules
'align_multiline_comment' => ['comment_type' => 'phpdocs_only'], // PSR-5
'phpdoc_to_comment' => false,
'array_indentation' => true,
'array_syntax' => ['syntax' => 'short'],
'cast_spaces' => ['space' => 'none'],
'concat_space' => ['spacing' => 'one'],
'compact_nullable_typehint' => true,
'declare_equal_normalize' => ['space' => 'single'],
'increment_style' => ['style' => 'post'],
'list_syntax' => ['syntax' => 'long'],
'echo_tag_syntax' => ['format' => 'long'],
'phpdoc_align' => false,
'phpdoc_no_empty_return' => false,
'phpdoc_order' => true, // PSR-5
'phpdoc_no_useless_inheritdoc' => false,
'protected_to_private' => false,
'yoda_style' => false,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'const', 'function'],
],
])
->setFinder($finder);

View file

@ -0,0 +1,840 @@
<!--- BEGIN HEADER -->
# Changelog
All notable changes to this project will be documented in this file.
<!--- END HEADER -->
## [1.17.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.16.0...v1.17.0) (2023-03-26)
### Bug Fixes
* Cast `Scope` as string to use it as array key ([aff96f](https://github.com/marcocesarato/php-conventional-changelog/commit/aff96f70c05dec4191190eaf32951930f2b2570c))
* Check string offset exists before using it ([1fe715](https://github.com/marcocesarato/php-conventional-changelog/commit/1fe7152382ea5719a6a39067e4d8ce6e154f31f2))
---
## [1.16.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.6...v1.16.0) (2022-11-26)
### Features
* New option: Do not update CHANGELOG if no commits found. ([00a81e](https://github.com/marcocesarato/php-conventional-changelog/commit/00a81ee59f3f3e3f3b386f454d480a77538892ab))
### Bug Fixes
* Can parse header with carriage return and newline. ([ac4710](https://github.com/marcocesarato/php-conventional-changelog/commit/ac47108d2d1aed3e1e9fd8e7c5ea644a1216f8df))
* Retrieve last tag with extra [#48](https://github.com/marcocesarato/php-conventional-changelog/issues/48) ([454513](https://github.com/marcocesarato/php-conventional-changelog/commit/45451305801803aedc40cf01397440eea4524c0d))
---
## [1.15.6](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.5...v1.15.6) (2022-11-17)
### Bug Fixes
* Resolves problems with bumping alpha, beta and rc releases ([ad9eff](https://github.com/marcocesarato/php-conventional-changelog/commit/ad9efff67b1b83027865a2c1fa583d80370fb433))
* Resolves with extra releases ([e31c16](https://github.com/marcocesarato/php-conventional-changelog/commit/e31c16bfa180952fa8f10e271e49f4ffdfec8290))
---
## [1.15.5](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.4...v1.15.5) (2022-11-04)
### Bug Fixes
* Bump first time with extra version ([22b302](https://github.com/marcocesarato/php-conventional-changelog/commit/22b302aae1490eb78c018db9ed5148669d63c9d9))
* Extra tag bumping ([b6223f](https://github.com/marcocesarato/php-conventional-changelog/commit/b6223f47171af784e5c2a578db13d8afe49f1904))
* Semantic version regex compare ([a71a4b](https://github.com/marcocesarato/php-conventional-changelog/commit/a71a4bbaf46a72ffa27b5153a9da60554845ec31))
---
## [1.15.4](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.3...v1.15.4) (2022-10-27)
### Bug Fixes
* Restore last tag with refname method ([ef97fb](https://github.com/marcocesarato/php-conventional-changelog/commit/ef97fb098fb25b6f9c6c285fe2ffb32d8fc06211))
---
## [1.15.3](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.2...v1.15.3) (2022-10-27)
### Bug Fixes
* Prefix stripping and merged tags only [#47](https://github.com/marcocesarato/php-conventional-changelog/issues/47) ([60274c](https://github.com/marcocesarato/php-conventional-changelog/commit/60274c9bc220d0285ad535b14dfc630548a701cf))
* Semantic version prefix remove ([a821c1](https://github.com/marcocesarato/php-conventional-changelog/commit/a821c1366e9a2323d625af958b4196656ef01788))
---
## [1.15.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.1...v1.15.2) (2022-10-24)
### Bug Fixes
* Enable merged feature with new extra logics ([6a7d70](https://github.com/marcocesarato/php-conventional-changelog/commit/6a7d7090cc754ff73040db408c75bd02e2da2192))
* Extra release generation ([854599](https://github.com/marcocesarato/php-conventional-changelog/commit/854599bccf45b461e66bfd37a306f9c9a4db09c8))
* Force autobump on an extra release ([ce5d80](https://github.com/marcocesarato/php-conventional-changelog/commit/ce5d806de1afa01f87dfe73d35b45dead7c530a2))
---
## [1.15.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.15.0...v1.15.1) (2022-06-03)
### Bug Fixes
* Add blank line after changeLogVersionHeading [#39](https://github.com/marcocesarato/php-conventional-changelog/issues/39) ([9e4108](https://github.com/marcocesarato/php-conventional-changelog/commit/9e41083adb9d898c97c3e5d3d8561e9b84cae410))
---
## [1.15.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.14.1...v1.15.0) (2022-06-02)
### Features
* Add a getCurrentBranch method to Repository ([8b6266](https://github.com/marcocesarato/php-conventional-changelog/commit/8b62668eea440b64683f0af20a13065a7890be3f))
---
## [1.14.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.14.0...v1.14.1) (2022-05-24)
### Bug Fixes
* Breaking changes check [#37](https://github.com/marcocesarato/php-conventional-changelog/issues/37) ([476ff7](https://github.com/marcocesarato/php-conventional-changelog/commit/476ff7f8b2e6d3efffbfa4af152f4d0269491934))
---
## [1.14.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.13.0...v1.14.0) (2022-05-06)
### ⚠ BREAKING CHANGES
* Sort git tags by version instead of creation date, match prefix when listing tags ([042af9](https://github.com/marcocesarato/php-conventional-changelog/commit/042af9ec98d85519a9e91130e2fcbdd15b981565))
### Features
* Annotated tags ([e738b2](https://github.com/marcocesarato/php-conventional-changelog/commit/e738b29b22cbdaa1cd1490408cff3b1b4299a2f2))
### Bug Fixes
* Get last version with prefix ([2460c8](https://github.com/marcocesarato/php-conventional-changelog/commit/2460c81fab175e23260e449e4a4554a2e8d479c6))
* Issues with tag version fetch ([d64f35](https://github.com/marcocesarato/php-conventional-changelog/commit/d64f359450bc0b678c90eeb7aa926e2b39bff4b2))
---
## [1.13.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.12.3...v1.13.0) (2021-12-08)
### Features
* Added support for limiting commits to current branch. ([6aad51](https://github.com/marcocesarato/php-conventional-changelog/commit/6aad514354334b9a3041bfdbe0c81b98e8f118b1))
### Bug Fixes
##### Semver
* Bump minor version when breaking change before 1.0.0 ([5b13aa](https://github.com/marcocesarato/php-conventional-changelog/commit/5b13aa1408459be415f475a23e55983aaa297111))
---
## [1.12.3](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.12.2...v1.12.3) (2021-11-25)
### Features
* Allows own package bumpers ([e4aef9](https://github.com/marcocesarato/php-conventional-changelog/commit/e4aef903f14c981b8978ab1299eedc2f7e0888cb))
---
## [1.12.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.12.1...v1.12.2) (2021-10-22)
### Bug Fixes
* Repository url format detector [#22](https://github.com/marcocesarato/php-conventional-changelog/issues/22) ([63ff44](https://github.com/marcocesarato/php-conventional-changelog/commit/63ff443cbc53d2453ee217950d4664a15e9b3dd4))
---
## [1.12.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.12.0...v1.12.1) (2021-10-22)
### Features
* Allows issue id to be string ([30c20e](https://github.com/marcocesarato/php-conventional-changelog/commit/30c20e52369c723b53b054fa2199c8c52256d9a9))
### Bug Fixes
* Change repo parse remote url regex ([2dd6c8](https://github.com/marcocesarato/php-conventional-changelog/commit/2dd6c87faf67b809a8b37a763a2e33d5357141a4))
* Version separator markdown ([08092d](https://github.com/marcocesarato/php-conventional-changelog/commit/08092d21e8fa223de2bf4954b5d8f738109c36c1))
---
## [1.12.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.11.0...v1.12.0) (2021-08-24)
### Features
* Allows ability to hide version separator. ([e74c4d](https://github.com/marcocesarato/php-conventional-changelog/commit/e74c4d47130baa742397c7701fb27f5f6681d95d))
---
## [1.11.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.7...v1.11.0) (2021-07-28)
### Features
* Allows generated changelog to have configurable version headers ([f57e95](https://github.com/marcocesarato/php-conventional-changelog/commit/f57e953b7a8c3b96519238e0122bbb3cb949b9ba))
---
## [1.10.7](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.6...v1.10.7) (2021-05-15)
### Features
* Add option to render text instead of links in generated changelog ([5cb2e5](https://github.com/marcocesarato/php-conventional-changelog/commit/5cb2e5d2d8b9b445e38eede49dbc3a8036ba36a6))
---
## [1.10.6](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.5...v1.10.6) (2021-05-15)
### Bug Fixes
* Check if valid remote url [#14](https://github.com/marcocesarato/php-conventional-changelog/issues/14) ([7bc76c](https://github.com/marcocesarato/php-conventional-changelog/commit/7bc76cdaa11109efd43699d62ff3e71d1b445a15))
* Explicit compare ([78a084](https://github.com/marcocesarato/php-conventional-changelog/commit/78a084dca3f22e82614d2287ec350fe9cc05f539))
---
## [1.10.5](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.4...v1.10.5) (2021-05-15)
### Bug Fixes
* Output error on check requirements [#14](https://github.com/marcocesarato/php-conventional-changelog/issues/14) ([f020d9](https://github.com/marcocesarato/php-conventional-changelog/commit/f020d9e5a79fb355cd92785882eed8f325152d6a))
* Remove remote url repository requirement [#14](https://github.com/marcocesarato/php-conventional-changelog/issues/14) ([dacd7c](https://github.com/marcocesarato/php-conventional-changelog/commit/dacd7c3a82c2924047fcf4128e2d604f58c94978))
---
## [1.10.4](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.3...v1.10.4) (2021-05-14)
### Bug Fixes
* Add git remote repository url format check ([56aff6](https://github.com/marcocesarato/php-conventional-changelog/commit/56aff6304296c239aad8646e96d5452927857e84))
* Add requirements checks at startup [#14](https://github.com/marcocesarato/php-conventional-changelog/issues/14) ([b0b124](https://github.com/marcocesarato/php-conventional-changelog/commit/b0b1245cfca1bae60950d313fdb6d131c7cd5852))
* History version code ([eebfbe](https://github.com/marcocesarato/php-conventional-changelog/commit/eebfbea5e02a2e93d36eee3ba8ef357ecb269c79))
* Version code format and initialization [#14](https://github.com/marcocesarato/php-conventional-changelog/issues/14) ([b65bb6](https://github.com/marcocesarato/php-conventional-changelog/commit/b65bb669eaa33964b25b8fff97f0b1901d42b152))
##### Git
* Detect is inside work tree check remote url ([cb57a6](https://github.com/marcocesarato/php-conventional-changelog/commit/cb57a6e9c2d62e3c4009af6dd060acd6854b2bc2))
##### Semver
* Add get version code method ([54599f](https://github.com/marcocesarato/php-conventional-changelog/commit/54599fb8e91d6bc06bfc7881978f3a0fcde2602f))
##### Shell
* Add is enabled method ([edee7d](https://github.com/marcocesarato/php-conventional-changelog/commit/edee7de39313697b3c327a8729cc06c8d0774fb1))
---
## [1.10.3](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.2...v1.10.3) (2021-05-11)
### Bug Fixes
##### Composer Json
* Disable composer update on bump [#13](https://github.com/marcocesarato/php-conventional-changelog/issues/13) ([669262](https://github.com/marcocesarato/php-conventional-changelog/commit/669262ba6ceba78f82d6bb557c47564a90710716))
---
## [1.10.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.1...v1.10.2) (2021-04-16)
### Bug Fixes
* Skip tags and --not-tag are not working ([7f4d45](https://github.com/marcocesarato/php-conventional-changelog/commit/7f4d45886ad2723da11e4cb0073a7d29ead14e2d))
---
## [1.10.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.10.0...v1.10.1) (2021-04-15)
### Bug Fixes
##### Composer Json
* Composer update change path command ([18bb19](https://github.com/marcocesarato/php-conventional-changelog/commit/18bb19395d9c33905786ff1b1d834db369142c20))
---
## [1.10.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.10...v1.10.0) (2021-04-15)
### Features
* Add packages bumper [#11](https://github.com/marcocesarato/php-conventional-changelog/issues/11) ([0849aa](https://github.com/marcocesarato/php-conventional-changelog/commit/0849aad6d04e6640777a819391736edde56f00ba))
##### Composer Json
* Add composer update on save [#11](https://github.com/marcocesarato/php-conventional-changelog/issues/11) ([ea8fb7](https://github.com/marcocesarato/php-conventional-changelog/commit/ea8fb7eded13e6924388f044044a9898ec810163))
##### Config
* Add bump package setting [#11](https://github.com/marcocesarato/php-conventional-changelog/issues/11) ([343dd6](https://github.com/marcocesarato/php-conventional-changelog/commit/343dd6e47259c0190aa41cad5a42597df64a0d0d))
* Add package lock commit setting [#11](https://github.com/marcocesarato/php-conventional-changelog/issues/11) ([2fc824](https://github.com/marcocesarato/php-conventional-changelog/commit/2fc8247fa77792ac1a6f6805196f3f42605321ea))
### Bug Fixes
* Add git command exists check ([9ecd9e](https://github.com/marcocesarato/php-conventional-changelog/commit/9ecd9ebb4979352f93e071a4b74686fe10c3b060))
##### Bump
* Add lock files to commit [#11](https://github.com/marcocesarato/php-conventional-changelog/issues/11) ([4da206](https://github.com/marcocesarato/php-conventional-changelog/commit/4da2061af0fb9e8da24215ed174896d5d8f4eb04))
* Unescape slashes on json encode [#11](https://github.com/marcocesarato/php-conventional-changelog/issues/11) ([29a83e](https://github.com/marcocesarato/php-conventional-changelog/commit/29a83e243c8fd29df984dfe33253f2836c8f9435))
---
## [1.9.10](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.9...v1.9.10) (2021-04-13)
### Bug Fixes
* Replace command constants response for symfony 4 compatibility [#10](https://github.com/marcocesarato/php-conventional-changelog/issues/10) ([639aec](https://github.com/marcocesarato/php-conventional-changelog/commit/639aec65cdcb86a0d5cfe9715d8a6516594700e6))
---
## [1.9.9](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.8...v1.9.9) (2021-04-02)
### Bug Fixes
* Commit properties from protected to public for sorting ([d3b658](https://github.com/marcocesarato/php-conventional-changelog/commit/d3b65854ee00139e43577b03d3d2eb04dd489fd3))
---
## [1.9.8](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.7...v1.9.8) (2021-04-02)
### Bug Fixes
* Commit sorting on changelog [#9](https://github.com/marcocesarato/php-conventional-changelog/issues/9) ([0d402c](https://github.com/marcocesarato/php-conventional-changelog/commit/0d402cb2904e1aaff19d95616332462fdf519dec))
---
## [1.9.7](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.6...v1.9.7) (2021-03-27)
### Bug Fixes
* Array key exists on mixed value check ([4007ab](https://github.com/marcocesarato/php-conventional-changelog/commit/4007abe2b325ae7235c69cd4d4f66fb1f2d8d461))
---
## [1.9.6](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.5...v1.9.6) (2021-03-12)
### Bug Fixes
* Incorrect parsing of URLs [#7](https://github.com/marcocesarato/php-conventional-changelog/issues/7) ([24ca1c](https://github.com/marcocesarato/php-conventional-changelog/commit/24ca1c20b579625266ee8de656cb4616bb38fb85))
---
## [1.9.5](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.4...v1.9.5) (2021-03-02)
### Bug Fixes
##### Git
* Incorrect user type in Mention ([0c9fc5](https://github.com/marcocesarato/php-conventional-changelog/commit/0c9fc5b7f1a4ad610a1429d38550e7a80d8c82a6))
---
## [1.9.4](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.3...v1.9.4) (2021-02-28)
### Bug Fixes
* Remove tag prefix and suffix on history release header ([070113](https://github.com/marcocesarato/php-conventional-changelog/commit/070113ac76f2f6606bb2acd9a9c6f03d4fc8da07))
---
## [1.9.3](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.2...v1.9.3) (2021-02-22)
### Bug Fixes
* Add tag prefix and suffix on compare url tag [#5](https://github.com/marcocesarato/php-conventional-changelog/issues/5) ([db81e0](https://github.com/marcocesarato/php-conventional-changelog/commit/db81e0ec63a665d03170165b1623f72b4b70b08e))
---
## [1.9.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.1...v1.9.2) (2021-02-14)
### Features
##### Git
* Add closed attribute to references ([acb104](https://github.com/marcocesarato/php-conventional-changelog/commit/acb10474b5cb4f4002755ca7e55ab785c2750149))
* Add mention and reference class ([b22fa5](https://github.com/marcocesarato/php-conventional-changelog/commit/b22fa5c46b54ed7de6072445c90ad2a8a1b7408a))
* Commit classes auto compose when raw is empty ([0f7f9d](https://github.com/marcocesarato/php-conventional-changelog/commit/0f7f9d7d139c8ee0ccf53effa580735a0a40d508))
### Bug Fixes
* Using new reference class on changelog generation ([03e3a2](https://github.com/marcocesarato/php-conventional-changelog/commit/03e3a2bbaebd26db6fef3a7e699020c30d89511c))
##### Config
* Add isset check on setting ignore types [#4](https://github.com/marcocesarato/php-conventional-changelog/pull/4) ([22ab18](https://github.com/marcocesarato/php-conventional-changelog/commit/22ab180da24dec738f2dc1875590704ad473add9))
* Remove empty check of setting ignore types [#4](https://github.com/marcocesarato/php-conventional-changelog/pull/4) ([4b764d](https://github.com/marcocesarato/php-conventional-changelog/commit/4b764def749372df2270ccee7cbb4f9c8dc7ce01))
##### Git
* Add check empty commit on parse ([a26d6a](https://github.com/marcocesarato/php-conventional-changelog/commit/a26d6aeeecdd23e76773b5549b06800c4a240e09))
* Footer references detection ([6a40c1](https://github.com/marcocesarato/php-conventional-changelog/commit/6a40c1a0a3fa67f088450e1aef45e295371797eb))
---
## [1.9.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.9.0...v1.9.1) (2021-02-13)
### Features
* Add date format config ([05196a](https://github.com/marcocesarato/php-conventional-changelog/commit/05196ad943bffc5317df575d7a782ca04fb53421))
---
## [1.9.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.8.0...v1.9.0) (2021-02-13)
### Features
* Add pre and post run hooks and changes sorting ([a38ef1](https://github.com/marcocesarato/php-conventional-changelog/commit/a38ef1d6f74128322d031ef9404888a6e1706d38), [668d2e](https://github.com/marcocesarato/php-conventional-changelog/commit/668d2ea679f92c9f1022b95f93b83b614fd6d461))
##### Git
* Add delete tag method ([7712c2](https://github.com/marcocesarato/php-conventional-changelog/commit/7712c24494972365270aff896421f127003c2108))
* Add get last commit hash method ([c650b5](https://github.com/marcocesarato/php-conventional-changelog/commit/c650b5e48b3c79959af2d8b941818c2740cc4fae))
* Add no edit param to commit method ([bf4d1a](https://github.com/marcocesarato/php-conventional-changelog/commit/bf4d1a85e8303307dcd42c0318d6a33089bf49d7))
##### Semver
* Pattern version validation ([e19170](https://github.com/marcocesarato/php-conventional-changelog/commit/e191708265b8764823f397dbd28a8597edd2e0cd))
---
## [1.8.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.7.0...v1.8.0) (2021-02-12)
### Features
* Add pretty scope config ([f47356](https://github.com/marcocesarato/php-conventional-changelog/commit/f47356fac49edd01dab3f9cceb29481bab66758c))
* Add hidden configurations ([42c9af](https://github.com/marcocesarato/php-conventional-changelog/commit/42c9afe7aee84ab75ccc4ce9f7add83cf1922f51))
* Add user mentions ([214c75](https://github.com/marcocesarato/php-conventional-changelog/commit/214c75f84874ef4a92fd869230762c257d97c638))
### Bug Fixes
* Breaking changes indicated by a ! and ignore duplicated or empty message [#1](https://github.com/marcocesarato/php-conventional-changelog/issues/1) ([f3ebee](https://github.com/marcocesarato/php-conventional-changelog/commit/f3ebeee6bdac4e0fc1010ce3f7d3afd58bfda381))
---
## [1.7.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.6.2...v1.7.0) (2021-02-12)
### Features
* Urls and release commit formats ([7cb62a](https://github.com/marcocesarato/php-conventional-changelog/commit/7cb62acf85196bb576791bba6afd7ba55e2a3690))
##### Git
* Add parse remote url method ([81812c](https://github.com/marcocesarato/php-conventional-changelog/commit/81812c0838964198d577240ae12e79d059e18cc4))
---
## [1.6.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.6.1...v1.6.2) (2021-02-10)
### Bug Fixes
* Conventional commit wakeup class parse [#3](https://github.com/marcocesarato/php-conventional-changelog/issues/3) ([9eb3fb](https://github.com/marcocesarato/php-conventional-changelog/commit/9eb3fbee01f7e8717be67aaa5199bd51c7a17030))
---
## [1.6.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.6.0...v1.6.1) (2021-02-09)
### Bug Fixes
* Option commit all ([d84122](https://github.com/marcocesarato/php-conventional-changelog/commit/d84122283b244d213c2e5ee7329bce696bf85bc1))
##### Config
* Empty configurations from array [#1](https://github.com/marcocesarato/php-conventional-changelog/issues/1) ([481f05](https://github.com/marcocesarato/php-conventional-changelog/commit/481f051069f5c9a5ae2ec0f1375fa3ac8e81b9b4))
---
## [1.6.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.5.4...v1.6.0) (2021-01-28)
### Features
* Add tag prefix and suffix ([469f16](https://github.com/marcocesarato/php-conventional-changelog/commit/469f166f117478198d9fbdb55a4ea2b7f1d02ff3))
* Add commit all option ([323a5c](https://github.com/marcocesarato/php-conventional-changelog/commit/323a5c0c4bdb030938cea7b41056240cfbc4a9a6))
* Add skip verify, bump and tag ([1ae0e9](https://github.com/marcocesarato/php-conventional-changelog/commit/1ae0e964abf46ba2668c6562f59133654bea700d))
### Bug Fixes
* Option commit all ([d84122](https://github.com/marcocesarato/php-conventional-changelog/commit/d84122283b244d213c2e5ee7329bce696bf85bc1))
---
## [1.5.4](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.5.3...v1.5.4) (2021-01-24)
### Features
* Add commit parent class with raw metadata ([962e0f](https://github.com/marcocesarato/php-conventional-changelog/commit/962e0f7474ba34d7cfe01660025f272df9742eb8))
##### Config
* Add root setting ([2ad49c](https://github.com/marcocesarato/php-conventional-changelog/commit/2ad49c5614e5bcba17c95f142ea11e698d07a644))
### Bug Fixes
##### Config
* Use default settings if is not a valid config return value ([55fd29](https://github.com/marcocesarato/php-conventional-changelog/commit/55fd292f3a5d9068502b9ce54997d5015e03ad94))
* Check valid array ([849f43](https://github.com/marcocesarato/php-conventional-changelog/commit/849f43dcadef5361a64fa0ed5705a67850cef329))
---
## [1.5.3](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.5.2...v1.5.3) (2021-01-23)
### Bug Fixes
* Autobump on specified version ([9e35af](https://github.com/marcocesarato/php-conventional-changelog/commit/9e35af66941124a209bbc0c3ea54963c55a2f92c))
---
## [1.5.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.5.1...v1.5.2) (2021-01-22)
### Features
* Add check if inside a git work tree ([E3ad12](https://github.com/marcocesarato/php-conventional-changelog/commit/E3ad1287e1bfccfddf18bf52152dd1edba4eff2a))
##### Git
* Add inside work tree method ([081196](https://github.com/marcocesarato/php-conventional-changelog/commit/0811968babb8de519a3f249b4dcdb7da33a5ad99))
### Bug Fixes
* Auto bump new version code commit and tag ([Bf26ee](https://github.com/marcocesarato/php-conventional-changelog/commit/Bf26eee06484d2045c7f701f2e4b1f02fd430911))
##### Commit Parser
* Change return to string nullable on getters hash and raw ([5aa565](https://github.com/marcocesarato/php-conventional-changelog/commit/5aa565eb18090287e4033a6178acbe0959537d0b))
##### Git
* Get last tag name ([B265d0](https://github.com/marcocesarato/php-conventional-changelog/commit/B265d06b1f857d1f95db27a912fd74bd574c981a))
* Quotes on values exec return ([4e78c4](https://github.com/marcocesarato/php-conventional-changelog/commit/4e78c4ea8bd4e0f290d20284a7820d5392c04064))
---
## [1.5.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.5.0...v1.5.1) (2021-01-22)
### Bug Fixes
* Commits retrieve on ranges and remove additional whitespaces ([C3bffa](https://github.com/marcocesarato/php-conventional-changelog/commit/C3bffa74021727937c1f5b4a42efe534c820b943))
##### Config
* Possibile issues with empty data ([179c1f](https://github.com/marcocesarato/php-conventional-changelog/commit/179c1fa95fac3853b24a57d0c933d09e5500c328))
##### Git
* Get commits command compatibility and sorting get tags ([28d6ad](https://github.com/marcocesarato/php-conventional-changelog/commit/28d6ad76bb0ebfb1fe21ba70ec4e1b512ef6a3be))
---
## [1.5.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.4.3...v1.5.0) (2021-01-21)
### Features
* Split types and presets configuration ([4239ec](https://github.com/marcocesarato/php-conventional-changelog/commit/4239ec0866ca0d2e21b8bf3394f2d888bf44c08a))
* Add auto versioning semver bump ([82c883](https://github.com/marcocesarato/php-conventional-changelog/commit/82c8839300880d16c428abebca8f999f866baaeb))
* Add summary ([C37116](https://github.com/marcocesarato/php-conventional-changelog/commit/C371169f3dde9bcdb16a9de6e339ae26d70a477a))
* Add to tag and from tag options ([0b0c4b](https://github.com/marcocesarato/php-conventional-changelog/commit/0b0c4b06fcdb5e472914fa33f3175dbad3bac60e))
##### Command
* Add custom configuration file option ([D65fc7](https://github.com/marcocesarato/php-conventional-changelog/commit/D65fc7472885116e891f383a6600d3df43b72e58))
##### Commit Parser
* Add getters and has scope method ([83d99a](https://github.com/marcocesarato/php-conventional-changelog/commit/83d99aa44cba23bc0fe6b829c337cb9814a10bf9))
* Set default values on body and scope ([B362bd](https://github.com/marcocesarato/php-conventional-changelog/commit/B362bd5167b2951333c12397c544233266676eda))
### Bug Fixes
* Breaking changes preserving the initial commit and customizable label ([607f6e](https://github.com/marcocesarato/php-conventional-changelog/commit/607f6e09026eaee8b1b7e83fa4a7475d7a4f5694))
* Add hash check and rename parser to conventional ([3e8996](https://github.com/marcocesarato/php-conventional-changelog/commit/3e899655c82737ca363995530ec300d4215c2a64))
* Customize changelog file path ([1795ff](https://github.com/marcocesarato/php-conventional-changelog/commit/1795ffba53ea35108c05138054772e3bcde73b09))
##### Semver
* Bump major and minor ([Fd7485](https://github.com/marcocesarato/php-conventional-changelog/commit/Fd7485f2b7ce77e32542b4431e37ea271d031878))
### Documentation
##### Readme
* Add usage gif image and improve usage section ([Fbfdfe](https://github.com/marcocesarato/php-conventional-changelog/commit/Fbfdfe0f888aa5d36451c3e8bd589ddc7674b163))
* Add config option and adjust usage list example ([Fbe9fd](https://github.com/marcocesarato/php-conventional-changelog/commit/Fbe9fdba9ee21ee915dcd18a04a3ff9240c90b1b))
* Merged notes on configuration section ([9efedc](https://github.com/marcocesarato/php-conventional-changelog/commit/9efedcd91d1d84c022586e3d52ad88c02544547e))
### Chores
* Implement types configuration ([4d90f1](https://github.com/marcocesarato/php-conventional-changelog/commit/4d90f1d2c1c7b87e6adfdb7b31de3cbc1df659c5))
##### Preset
* Change fixes description ([Cbaa8f](https://github.com/marcocesarato/php-conventional-changelog/commit/Cbaa8fd627650303874f72e3206f8a1a6664064c))
---
## [1.4.4](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.4.3...v1.4.4) (2021-01-21)
### Features
* Split types and presets configuration ([4239ec](https://github.com/marcocesarato/php-conventional-changelog/commit/4239ec0866ca0d2e21b8bf3394f2d888bf44c08a))
* Add summary ([C37116](https://github.com/marcocesarato/php-conventional-changelog/commit/C371169f3dde9bcdb16a9de6e339ae26d70a477a))
##### Commit Parser
* Add getters and has scope method ([83d99a](https://github.com/marcocesarato/php-conventional-changelog/commit/83d99aa44cba23bc0fe6b829c337cb9814a10bf9))
* Set default values on body and scope ([B362bd](https://github.com/marcocesarato/php-conventional-changelog/commit/B362bd5167b2951333c12397c544233266676eda))
### Bug Fixes
* Breaking changes preserving the initial commit and customizable label ([607f6e](https://github.com/marcocesarato/php-conventional-changelog/commit/607f6e09026eaee8b1b7e83fa4a7475d7a4f5694))
* Add hash check and rename parser to conventional ([3e8996](https://github.com/marcocesarato/php-conventional-changelog/commit/3e899655c82737ca363995530ec300d4215c2a64))
* Customize changelog file path ([1795ff](https://github.com/marcocesarato/php-conventional-changelog/commit/1795ffba53ea35108c05138054772e3bcde73b09))
##### Git
* Format option quotes ([693860](https://github.com/marcocesarato/php-conventional-changelog/commit/693860b5ba9c5ddfd13919a48186aeaf22931f12))
### Chores
##### Preset
* Change fixes description ([Cbaa8f](https://github.com/marcocesarato/php-conventional-changelog/commit/Cbaa8fd627650303874f72e3206f8a1a6664064c))
---
## [1.4.3](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.4.2...v1.4.3) (2021-01-19)
### Features
* Implement new commit parser and refactoring of helper classes ([F2f2ec](https://github.com/marcocesarato/php-conventional-changelog/commit/F2f2ec80adfa787d453817faa64906e9dd988e44))
* Add new conventional commit parser ([603f72](https://github.com/marcocesarato/php-conventional-changelog/commit/603f724dc759f4762c9b09489cb7620a98a10d67))
* Add breaking changes and issues references ([Bda545](https://github.com/marcocesarato/php-conventional-changelog/commit/Bda5458e6c190a861417fb0239f86057c1d0c22f))
### Bug Fixes
* Semantic version extra part split ([183116](https://github.com/marcocesarato/php-conventional-changelog/commit/18311683929859060af3c449a76fad41cb2baa52))
* Commit changes list generation ([68b1ff](https://github.com/marcocesarato/php-conventional-changelog/commit/68b1ff9f424b47d0965702724e3cce5866b88892))
* Uppercase first char of scope on stringify ([4eaebe](https://github.com/marcocesarato/php-conventional-changelog/commit/4eaebe96c84fa74aefa39d60fa1aeb3777316ce9))
* Move scope to string method to pretty string ([D64421](https://github.com/marcocesarato/php-conventional-changelog/commit/D644210b973b1b16e3feeb140c3da881667888fa))
### Chores
* Change php requirements ([5997ec](https://github.com/marcocesarato/php-conventional-changelog/commit/5997ecf5f02bbeedd1e31fe98cc5f6d8d743cb14))
---
## [1.4.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.4.1...v1.4.2) (2021-01-19)
### Bug Fixes
* Add chores to not notable types ([2c4d1c](https://github.com/marcocesarato/php-conventional-changelog/commit/2c4d1cdc23455f899922d1796bcd6fa343c6d589))
---
## [1.4.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.4.0...v1.4.1) (2021-01-18)
### Features
* Add semantic version parser ([7b5a9f](https://github.com/marcocesarato/php-conventional-changelog/commit/7b5a9fd99a4557bc7c167b99baa7ac3107d6f6c6))
* Add rc, alpha and beta release method ([f955e1](https://github.com/marcocesarato/php-conventional-changelog/commit/f955e18309fa8c304bb3fce0faaf23eb3e35eebe))
### Documentation
##### Readme
* Add new options on command list ([1ce4e3](https://github.com/marcocesarato/php-conventional-changelog/commit/1ce4e3b60c065e4ab7b4a0eaf272e427ecd58425))
---
## [1.4.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.3.0...v1.4.0) (2021-01-18)
### Features
* Add amend and no verify options ([625ed1](https://github.com/marcocesarato/php-conventional-changelog/commit/625ed1bfa6fc70716736be303b1f7a048b894756))
* Add not tag option and add errors on commit and tagging ([285ad0](https://github.com/marcocesarato/php-conventional-changelog/commit/285ad0a1bfe13b2460653a02ad40243184e7d80a))
### Bug Fixes
##### Config
* Get configs from project root or working dir ([ee9d65](https://github.com/marcocesarato/php-conventional-changelog/commit/ee9d654f3e340575cb69e3fa940083891c150716))
* Improve configuration and adjusted some settings ([cd6bdf](https://github.com/marcocesarato/php-conventional-changelog/commit/cd6bdf1de89c603afce1735487c4264c683473ab))
### Documentation
##### Readme
* Update description ([02c2c3](https://github.com/marcocesarato/php-conventional-changelog/commit/02c2c36c9e20aabbb708f6a12531300ea80e02b0))
* Fix configuration comment ([da733a](https://github.com/marcocesarato/php-conventional-changelog/commit/da733a985030a9d5ab99fd3683551fa44267f0ff))
* Add configuration notes and update example ([10c1f5](https://github.com/marcocesarato/php-conventional-changelog/commit/10c1f5a709961cb52a97009b63189dfaea8d1bf9))
---
## [1.3.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.2.0...v1.3.0) (2021-01-17)
### Features
* Add config file inclusion from working dir ([2cbb9f](https://github.com/marcocesarato/php-conventional-changelog/commit/2cbb9ff0bae2d25d4801845fec46d01d529fafe9))
* Implement configuration system ([28001b](https://github.com/marcocesarato/php-conventional-changelog/commit/28001b4fa9de6da256641a5cf377a72f281bbc2a))
### Documentation
##### Readme
* Add configuration section with an example ([bc6aa3](https://github.com/marcocesarato/php-conventional-changelog/commit/bc6aa3ea23d0cef82c150620d33fb6d50f4125c0))
---
## [1.2.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.1.0...v1.2.0) (2021-01-17)
### Features
* Add history option ([d53555](https://github.com/marcocesarato/php-conventional-changelog/commit/d53555442dbff0375db46649a306234309ba60ae))
* Add commit type exclusions options and add changelog file on commit ([055497](https://github.com/marcocesarato/php-conventional-changelog/commit/0554976a445d82db914cd256cc931f70b5923db6))
### Bug Fixes
* Add current release on history only with commit option flagged ([bac00d](https://github.com/marcocesarato/php-conventional-changelog/commit/bac00d478a9a93831af797080841c6991c6d660b))
* Git commit add files to the repository ([301184](https://github.com/marcocesarato/php-conventional-changelog/commit/301184dc9d17649b25db06f4e4d45ac316533c74))
* Auto commit success message ([5dec89](https://github.com/marcocesarato/php-conventional-changelog/commit/5dec89ce412e37476868076a0e550b41d122049d))
* Git commit release message ([b6f3e4](https://github.com/marcocesarato/php-conventional-changelog/commit/b6f3e461d348e21fbfde278385a8a7119defdf98))
### Documentation
##### Readme
* Add history option with examples ([33d51b](https://github.com/marcocesarato/php-conventional-changelog/commit/33d51b1ab1c7c9821554c407e69f6b95925755bc))
* Add no chores and no refactor options on command list ([106114](https://github.com/marcocesarato/php-conventional-changelog/commit/106114359633a230270139b867b0be18af8086e2))
### Chores
* Update history option description ([b7d180](https://github.com/marcocesarato/php-conventional-changelog/commit/b7d180fb0a506d79828b6d4ef88df7b33f4aab2d))
##### Composer
* Add scripts for changelog and release ([a5bd92](https://github.com/marcocesarato/php-conventional-changelog/commit/a5bd92f4baff375a1cafb4bc87190ff346930cb9))
---
## [v1.1.0](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.0.2...vv1.1.0) (2021-01-17)
### Features
* Add autoloader ([8e5667](https://github.com/marcocesarato/php-conventional-changelog/commit/8e5667550c2188e78a0d3ec8e5d388f413f0b813))
* Add information output about commit tag and file path ([7c7f4f](https://github.com/marcocesarato/php-conventional-changelog/commit/7c7f4f43995dd88a5b836b7c5f45b1144b7856fe))
* Implementing symfony console ([78e11b](https://github.com/marcocesarato/php-conventional-changelog/commit/78e11be242147cee1f7ab8ce526a9b969ef79b09))
* Add first release option ([143d6b](https://github.com/marcocesarato/php-conventional-changelog/commit/143d6b7828c8989e33ef23d22476ed14a7d4d935))
### Documentation
* Update description and add new instructions on readme ([3e1086](https://github.com/marcocesarato/php-conventional-changelog/commit/3e1086f844bd4af6609911333cbf97b0ed99cdee))
##### Readme
* Fix json composer scripts ([118cfd](https://github.com/marcocesarato/php-conventional-changelog/commit/118cfd275099a8534cd38176d5b6e1820fc88a4e))
* Remove duplicate line ([d88a00](https://github.com/marcocesarato/php-conventional-changelog/commit/d88a00b704f7d621f664facb5656e384b273f531))
* Clarify bump version processs ([ba9227](https://github.com/marcocesarato/php-conventional-changelog/commit/ba92277bdd60c2ff97a1ee26918f8bfca5527652))
* Add first release option, update commands list and last version code ([59e3bb](https://github.com/marcocesarato/php-conventional-changelog/commit/59e3bb92016dfcc1d553cd83450a0427007069a4))
### Chores
* Indicate default value of patch flag on helper list ([91df9c](https://github.com/marcocesarato/php-conventional-changelog/commit/91df9cc1600b9a2fb91be2ad2ba3eed9dafd3802))
##### Code Standard
* Change script for code standard fixing ([dba6bb](https://github.com/marcocesarato/php-conventional-changelog/commit/dba6bb1807ef72808cb879d913dad86eeee53c90))
---
## [v1.0.2](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.0.1...vv1.0.2) (2021-01-17)
### Documentation
* Specified date format to use on commands ([cdb712](https://github.com/marcocesarato/php-conventional-changelog/commit/cdb71211ce49c04e8789338b700839aaeb5303e1))
### Chores
##### Bin
* Move conventional changelog file to root ([f7a20c](https://github.com/marcocesarato/php-conventional-changelog/commit/f7a20c5ae16ea372b26f6402695d94a47b432866))
---
## [v1.0.1](https://github.com/marcocesarato/php-conventional-changelog/compare/v1.0.0...vv1.0.1) (2021-01-17)
### Documentation
##### Readme
* Add package description ([5a63a4](https://github.com/marcocesarato/php-conventional-changelog/commit/5a63a4e9e59347aa99753444e7156ca23b2b6058))
* Add info shields ([b96291](https://github.com/marcocesarato/php-conventional-changelog/commit/b9629156f0af4c3207e77739a52ccb5ddcf4f4da))
### Chores
* Report only fatal error ([0de3e0](https://github.com/marcocesarato/php-conventional-changelog/commit/0de3e0fc6d5419bd823bd73dd0bae27ecb7c3d33))
---
## [v1.0.0](https://github.com/marcocesarato/php-conventional-changelog/compare/021a49f43ef65ac7a594450374f1772eef1fd8b0...vv1.0.0) (2021-01-17)
### Description
- Generate changelogs and release notes from a project's commit messages and metadata using php composer and automate versioning with semver.org and conventionalcommits.org
---

View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View file

@ -0,0 +1,212 @@
<div align="center">
<h1 align="center">PHP Conventional Changelog</h1>
![Version](https://img.shields.io/badge/version-1.17.0-brightgreen?style=for-the-badge)
![Requirements](https://img.shields.io/badge/php-%3E%3D%207.1.3-4F5D95?style=for-the-badge)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow?style=for-the-badge)](https://conventionalcommits.org)
![License](https://img.shields.io/github/license/marcocesarato/php-conventional-changelog?style=for-the-badge)
[![GitHub](https://img.shields.io/badge/GitHub-Repo-6f42c1?style=for-the-badge)](https://github.com/marcocesarato/php-conventional-changelog)
#### If this project helped you out, please support us with a star :star:
<br>
![](docs/images/logo.png)
</div>
## Description
When a new release of a software project is announced, it is convenient to generate documents that let the project
users know what were the changes and other relevant notes about the new project release.
This package can help to automatically generate changelog and release note files, so the developers of the project
reduce the work that is necessary to finalize and check the new release of the project.
This package can generate a changelog from a project's committing history messages and metadata using composer and automate versioning
with [**semver**](https://semver.org) and [**conventional-commits**](https://conventionalcommits.org).
It provides a command that can be run from the terminal, or using composer scripts,
to generate a changelog file in **markdown** for the current project.
The command may take parameters that define the releases of the project that will be considered to extract the changes
from the git history to generate the file. The package uses a configuration system with that permit to customize the
settings you may want to have your desired changelog generated.
Look at our [CHANGELOG](CHANGELOG.md) file if you are looking for an example of a possible final result.
### How to contribute
Have an idea? Found a bug? Please raise to [ISSUES](https://github.com/marcocesarato/php-conventional-changelog/issues)
or [PULL REQUEST](https://github.com/marcocesarato/php-conventional-changelog/pulls). Contributions are welcome and are
greatly appreciated! Every little bit helps.
## 📘 Requirements
- [php](https://www.php.net) >= 7.1.3
- [git](https://git-scm.com) >= 2.1.4
## 📖 Installation
You can install it easily with composer
`composer require --dev marcocesarato/php-conventional-changelog`
#### Scripts *(Optional)*
For easy use the changelog generator or release faster your new version you can add to your `composer.json` the scripts:
> **Notes:** you can customize it according to your needs
```
{
...
"scripts": {
"changelog": "conventional-changelog",
"release": "conventional-changelog --commit",
"release:patch": "conventional-changelog --patch --commit",
"release:minor": "conventional-changelog --minor --commit",
"release:major": "conventional-changelog --major --commit"
},
...
}
```
Now you can just run `composer changelog` or `composer release` (the last one will autobump the version code and commit changes) to generate your changelog.
## 📘 Configuration
> **Notes:** this procedure is *optional* and permit to overwriting/merging the default settings
For customize settings you just needs to create a file named `.changelog` on the root of your project/on the working
dir or use the `--config` option to specify the location of your configuration file.
> **Notes:**<br>
> - When a setting on the configuration file is not necessary just omit it
> - The default ignored types are: `build`, `chore`, `ci`, `docs`, `perf`, `refactor`, `revert`, `style`, `test`
> - To allow all types just keep empty `types` and set empty `ignoreTypes`
You can have more info about reading the [config documentation](./docs/config.md).
## 💻 Usage
The changelog generator will generate a log of changes from the date of the last tag to the current date, and it will
put all commit logs in the latest version just created.
![](docs/images/usage.gif)
> **Notes:**<br>
> - Some of these options listed on examples could be used together at the same time (ex. `--first-release --commit`)
> - Auto bump of version code using the [Semantic Versioning](https://semver.org) (`MAJOR.MINOR.PATCH`) is enabled by default if not specified the release method.
> - `MAJOR`: At least one breaking change.
> - `MINOR`: At least one new feature.
> - `PATCH`: Default
> - Use these options to specify the release method: `--major`, `--minor`, `--patch`, `--rc`, `--beta`, `--alpha`.
### Examples
#### First version
> **Notes:** use this option only if you don't need all history changes or is the first version, else run with `--history` option
To generate your changelog for the first version run:
```shell
php vendor/bin/conventional-changelog --first-release
```
#### New version
To generate your changelog *(without committing files)*
```shell
php vendor/bin/conventional-changelog
```
#### New release (with commit and tag)
To generate your changelog with auto commit and auto versioning tagging run:
```shell
php vendor/bin/conventional-changelog --commit
```
or to amend at an existing commit you can run:
```shell
php vendor/bin/conventional-changelog --amend
```
#### History
To generate your changelog with the entire history of changes of all releases
> **Warn:** this operation will overwrite the `CHANGELOG.md` file if it already exists
```shell
php vendor/bin/conventional-changelog --history
```
#### Date range
To generate your changelog from a specified date to another specified date
```shell
php vendor/bin/conventional-changelog --from-date="2020-12-01" --to-date="2021-01-01"
```
#### Tag range
To generate your changelog from a specified tag to another specified tag
```shell
php vendor/bin/conventional-changelog --from-tag="v1.0.2" --to-tag="1.0.4"
```
#### Specific version
To generate your changelog with a specific version code
```shell
php vendor/bin/conventional-changelog --ver="2.0.1"
```
### Commands List
> **Info:** You can have more info about running `php vendor/bin/conventional-changelog --help`
```
Description:
Generate changelogs and release notes from a project's commit messagesand metadata and automate versioning with semver.org and conventionalcommits.org
Usage:
changelog [options] [--] [<path>]
Arguments:
path Specify the path directory where generate changelog
Options:
--config=CONFIG Specify the configuration file path
-c, --commit Commit the new release once changelog is generated
-a, --amend Amend commit the new release once changelog is generated
--commit-all Commit all changes the new release once changelog is generated
--first-release Run at first release (if --ver isn't specified version code it will be 1.0.0)
--from-date=FROM-DATE Get commits from specified date [YYYY-MM-DD]
--to-date=TO-DATE Get commits last tag date (or specified on --from-date) to specified date [YYYY-MM-DD]
--from-tag=FROM-TAG Get commits from specified tag
--to-tag=TO-TAG Get commits last tag (or specified on --from-tag) to specified tag
--major Major release (important changes)
--minor Minor release (add functionality)
--patch Patch release (bug fixes) [default]
--rc Release candidate
--beta Beta release
--alpha Alpha release
--ver=VER Specify the next release version code (semver)
--history Generate the entire history of changes of all releases
--no-verify Bypasses the pre-commit and commit-msg hooks
--no-tag Disable release auto tagging
--no-change-without-commits Do not apply change if no commits
--annotate-tag[=ANNOTATE-TAG] Make an unsigned, annotated tag object once changelog is generated [default: false]
--merged Only include commits whose tips are reachable from HEAD
-h, --help Display help for the given command. When no command is given display help for the changelog command
```

View file

@ -0,0 +1,3 @@
# TODO
- Check version history on semver code generator

View file

@ -0,0 +1,14 @@
<?php
// Autoload
$files = [
__DIR__ . '/../../autoload.php',
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/vendor/autoload.php',
];
foreach ($files as $file) {
if (file_exists($file)) {
require_once $file;
break;
}
}

View file

@ -0,0 +1,77 @@
{
"name": "marcocesarato/php-conventional-changelog",
"description": "Generate changelogs and release notes from a project's commit messages and metadata and automate versioning with semver.org and conventionalcommits.org",
"version": "1.17.0",
"type": "library",
"license": "GPL-3.0-or-later",
"minimum-stability": "stable",
"bin": [
"conventional-changelog"
],
"keywords": [
"conventional-changelog",
"readme",
"generation",
"git",
"php",
"conventional-commit",
"conventional-commits",
"conventionalcommits",
"changelog",
"history",
"tag",
"commit",
"commits",
"conventional",
"convention",
"conventional-changelog-preset"
],
"authors": [
{
"name": "Marco Cesarato",
"email": "cesarato.developer@gmail.com"
}
],
"autoload": {
"psr-4": {
"ConventionalChangelog\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"changelog": "php conventional-changelog",
"check-cs": "php-cs-fixer fix --dry-run --format=txt --verbose --diff --diff-format=udiff --config=.php-cs-fixer.php",
"fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php --verbose",
"release": "php conventional-changelog --commit",
"release:patch": "php conventional-changelog --commit --patch",
"release:minor": "php conventional-changelog --commit --minor",
"release:major": "php conventional-changelog --commit --major",
"test": "phpunit ./tests",
"hooks": "cghooks",
"post-install-cmd": "cghooks add --ignore-lock",
"post-update-cmd": "cghooks update"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.8",
"brainmaestro/composer-git-hooks": "^2.8",
"phpunit/phpunit": "^9.6",
"php-mock/php-mock": "^2.3",
"php-mock/php-mock-phpunit": "^2.6"
},
"require": {
"php": ">=7.1.3",
"ext-json": "*",
"symfony/console": "^4 || ^5 || ^6"
},
"extra": {
"hooks": {
"pre-commit": "composer fix-cs",
"pre-push": "composer check-cs",
"post-merge": "composer install"
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
#!/usr/bin/env php
<?php
use ConventionalChangelog\DefaultCommand;
use Symfony\Component\Console\Application;
require_once __DIR__ . '/autoload.php';
// Report only fatal errors
error_reporting(2039);
// Config
$config = [];
$configName = '.changelog';
$configFiles = [
getcwd() . '/' . $configName, // Working dir
__DIR__ . '/../../../' . $configName, // Project path
];
foreach ($configFiles as $file) {
if (is_file($file) && empty($config)) {
$config = require $file;
}
}
// Command
$command = new DefaultCommand($config);
$commandName = $command->getName();
// Run application single command
$application = new Application('conventional-changelog', '1.17.0');
$application->add($command);
$application->setDefaultCommand($commandName, true);
$application->run();

View file

@ -0,0 +1,168 @@
# Configuration
For customize settings you just needs to create a file named `.changelog` on the root of your project/ on the working
dir or use the `--config` option to specify the location of your configuration file.
> **Notes:**<br>
> - When a setting on the configuration file is not necessary just omit it
> - To allow all types just keep empty `types` and set empty `ignoreTypes`
## Settings
- **Root:** Working directory of your project
- **Path:** Path to your changelog file (relative to the working dir/root)
- **Header Title:** Header title of the changelog
- **Header Description:** Header subtitle of the changelog
- **Sort By**: Sort changes by commit metadata (date, subject, authorName, authorEmail, authorDate, committerName,
committerEmail, committerDate)
- **Preset:** Add new types preset or modify existing types preset labels and description
- **Types:** Types allowed and showed on changelog. This setting could overwrite ignored types.
- **Package Bump:** Bump the package version in `composer.json` or `package.json` if files exists on the root path
- **Package Lock Commit:** Commit the package lock file (ex. `composer.lock`, `package.lock`, `yarn.lock`...)
- **Ignore Types:** Types ignored and so hidden on changelog
- **Ignore Patterns:** Patterns ignored and so hidden on changelog with a specific description. *(Regex are enabled)*
- **Tag Prefix:** Add prefix to release tag
- **Tag Suffix:** Add suffix to release tag
- **Skip Bump:** Skip automatic version code bump
- **Package Bumps:** Array of files to replace version, defaults to `['ConventionalChangelog\PackageBump\ComposerJson', 'ConventionalChangelog\PackageBump\PackageJson']`
- **Skip Tag:** Skip automatic commit tagging
- **Skip Verify:** Skip the pre-commit and commit-msg hooks
- **Disable Links:** Render text instead of link in changelog
- **Hidden Hash:** Hide commit hash from changelog
- **Hidden Mentions:** Hide users mentions from changelog
- **Hidden References:** Hide issue references from changelog
- **Pretty Scope:** Prettify the scope commit part (section name) on changelog *(ex. UserManager => User Manager or
user_config => User config)*
- **Url Protocol:** The URL protocol of all repository urls on changelogs (http/https)
- **Date Format:** The [format](https://www.php.net/manual/en/datetime.format.php) of the outputted date string
- **Changelog Version Format:** Allows the version header in changelog to have a configurable format
- **Commit Url Format:** A URL representing a specific commit at a hash
- **Compare Url Format:** A URL representing the comparison between two git sha
- **Issue Url Format:** A URL representing the issue format (allowing a different URL format to be swapped in for
Gitlab, Bitbucket, etc)
- **User Url Format:** A URL representing the a user's profile URL on GitHub, Gitlab, etc. This URL is used for
substituting @abc with https://github.com/abc in commit messages
- **Hidden Version Separator:** Hide version separator
- **Release Commit Message Format:** A string to be used to format the auto-generated release commit message
- **Pre Run**: Run a callback or command before run the script
- **Post Run**: Run a callback or command after run the script
- **Merged**: Only include commits whose tips are reachable from HEAD
### Default settings
These are the default settings:
```php
<?php
return [
'root' => getcwd(),
'path' => 'CHANGELOG.md',
'headerTitle' => 'Changelog',
'headerDescription' => 'All notable changes to this project will be documented in this file.',
'sortBy' => 'subject',
'preset' => [
// Breaking changes section
'breaking_changes' => ['label' => '⚠ BREAKING CHANGES', 'description' => 'Code changes that potentially causes other components to fail'],
// Types section
'feat' => ['label' => 'Features', 'description' => 'New features'],
'perf' => ['label' => 'Performance Improvements', 'description' => 'Code changes that improves performance'],
'fix' => ['label' => 'Bug Fixes', 'description' => 'Bugs and issues resolution'],
'refactor' => ['label' => 'Code Refactoring', 'description' => 'A code change that neither fixes a bug nor adds a feature'],
'style' => ['label' => 'Styles', 'description' => 'Changes that do not affect the meaning of the code'],
'test' => ['label' => 'Tests', 'description' => 'Adding missing tests or correcting existing tests'],
'build' => ['label' => 'Builds', 'description' => 'Changes that affect the build system or external dependencies '],
'ci' => ['label' => 'Continuous Integrations', 'description' => 'Changes to CI configuration files and scripts'],
'docs' => ['label' => 'Documentation', 'description' => 'Documentation changes'],
'chore' => ['label' => 'Chores', 'description' => "Other changes that don't modify the source code or test files"],
'revert' => ['label' => 'Reverts', 'description' => 'Reverts a previous commit'],
],
'types' => [],
'packageBump' => true,
'packageBumps' => [],
'packageLockCommit' => true,
'ignoreTypes' => ['build', 'chore', 'ci', 'docs', 'perf', 'refactor', 'revert', 'style', 'test'],
'ignorePatterns' => ['/^chore\(release\):/i'],
'tagPrefix' => 'v',
'tagSuffix' => '',
'skipBump' => false,
'skipTag' => false,
'skipVerify' => false,
'disableLinks' => false,
'hiddenHash' => false,
'hiddenMentions' => false,
'hiddenReferences' => false,
'prettyScope' => true,
'urlProtocol' => 'https',
'dateFormat' => 'Y-m-d',
'changelogVersionFormat' => '## {{version}} ({{date}})',
'commitUrlFormat' => '{{host}}/{{owner}}/{{repository}}/commit/{{hash}}',
'compareUrlFormat' => '{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}',
'issueUrlFormat' => '{{host}}/{{owner}}/{{repository}}/issues/{{id}}',
'userUrlFormat' => '{{host}}/{{user}}',
'releaseCommitMessageFormat' => 'chore(release): {{currentTag}}',
'hiddenVersionSeparator' => false,
'preRun' => null,
'postRun' => null,
'merged' => false,
];
```
## Examples
Configure your preferences with the help of the following examples.
#### Short Example
```php
<?php
return [
// Types allowed on changelog
'types' => ['feat', 'fix', 'perf', 'docs', 'chore'],
// Ignore chores with changelogs scope
'ignorePatterns' => [
'/chore\(changelog\)[:].*/i'
],
];
```
#### Full Example
```php
<?php
return [
'root' => dirname(__DIR__), // (ex. configs/changelog.php using --config option)
// File changelog (relative to the working dir/root)
'path' => 'docs/CHANGELOG.md', // You can specify a different folder
'headerTitle' => 'My changelog',
'headerDescription' => 'This is my changelog file.',
'preset' => [
// Add improvements type (deprecated type)
'improvements' => [
'label' => 'Improvements',
'description' => 'Improvements to existing features'
],
'chore' => [
// Change chore default label
'label' => 'Others'
],
],
// Types allowed on changelog
'types' => ['feat', 'fix', 'pref'], // These could overwrite ignored types
// Exclude not notables types (following types are the default excluded types)
'ignoreTypes' => ['build', 'chore', 'ci', 'docs', 'refactor', 'revert', 'style', 'test'],
'ignorePatterns' => [
// Exclude all commits with this specific description
'chore(deps): update dependencies',
// You can also use regex to exclude all commit like 'chore(changelog): updated'
'/chore\(changelog\)[:].*/i'
],
'tagPrefix' => 'ver',
'tagSuffix' => '',
'skipBump' => false,
'skipTag' => false,
'skipVerify' => true,
];
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Test">
<directory>./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>

View file

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

View file

@ -0,0 +1,694 @@
<?php
namespace ConventionalChangelog;
use ConventionalChangelog\Git\ConventionalCommit;
use ConventionalChangelog\Git\Repository;
use ConventionalChangelog\Helper\Formatter;
use ConventionalChangelog\Helper\SemanticVersion;
use ConventionalChangelog\PackageBump\ComposerJson;
use ConventionalChangelog\PackageBump\PackageJson;
use ConventionalChangelog\Type\PackageBump;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class Changelog
{
/**
* @var Configuration
*/
protected $config;
/**
* Remote url parse.
*
* @var array
*/
protected $remote = [];
/**
* Has valid remote url.
*
* @var bool
*/
protected $hasValidRemoteUrl = false;
/**
* Changelog constructor.
*/
public function __construct(Configuration $config)
{
$this->config = $config;
$this->remote = Repository::parseRemoteUrl();
}
/**
* Generate changelog.
*/
public function generate(string $root, InputInterface $input, SymfonyStyle $output): int
{
$nextVersion = $input->getOption('ver');
$autoCommit = $input->getOption('commit'); // Commit once changelog is generated
$autoCommitAll = $input->getOption('commit-all'); // Commit all changes once changelog is generated
$autoTag = !($input->getOption('no-tag') || $this->config->skipTag()); // Tag release once is committed
$noChangeWithoutCommits = $input->getOption('no-change-without-commits');
$annotateTag = $input->getOption('annotate-tag');
$amend = $input->getOption('amend'); // Amend commit
$hooks = !$input->getOption('no-verify'); // Verify git hooks
$hooks = $hooks && $this->config->skipVerify() ? false : true;
$fromDate = $input->getOption('from-date');
$toDate = $input->getOption('to-date');
$fromTag = $input->getOption('from-tag');
$toTag = $input->getOption('to-tag');
$history = $input->getOption('history');
$dateFormat = $this->config->getDateFormat();
$sortBy = $this->config->getSortBy();
$sortOrientation = $this->config->getSortOrientation($sortBy);
$merged = $input->getOption('merged');
$lastVersion = null;
$firstRelease = $input->getOption('first-release');
$alphaRelease = $input->getOption('alpha');
$betaRelease = $input->getOption('beta');
$preRelease = $input->getOption('rc');
$patchRelease = $input->getOption('patch');
$minorRelease = $input->getOption('minor');
$majorRelease = $input->getOption('major');
$tagPrefix = $this->config->getTagPrefix();
$tagSuffix = $this->config->getTagSuffix();
$autoCommit = $autoCommit || $autoCommitAll;
$autoBump = false;
/**
* @var PackageBump[]
*/
$packageBumps = [
ComposerJson::class,
PackageJson::class,
];
// Allow config to specify own packages.
if ($this->config->getPackageBumps()) {
$packageBumps = $this->config->getPackageBumps();
}
$this->hasValidRemoteUrl = Repository::hasRemoteUrl() && !empty(Repository::parseRemoteUrl());
// Hook pre run
$this->config->preRun();
// If have amend option enable commit
if ($amend) {
$autoCommit = true;
}
// Initialize changelogs
$file = $this->config->getPath();
$dirname = dirname($file);
if (!is_file($file) && !is_dir($dirname)) {
$file = $root . DIRECTORY_SEPARATOR . $file;
}
$changelogCurrent = '';
$changelogNew = '';
$mainHeaderPrefix = "<!--- BEGIN HEADER -->\n# ";
$mainHeaderSuffix = "\n<!--- END HEADER -->\n\n";
$mainHeaderContent = $this->config->getHeaderTitle() . "\n\n" . $this->config->getHeaderDescription();
$mainHeader = $mainHeaderPrefix . $mainHeaderContent . $mainHeaderSuffix;
// Get changelogs content
if (file_exists($file)) {
$changelogCurrent = $this->removeHeader(
file_get_contents($file)
);
}
// Current Dates
$today = new \DateTime();
// First release
$newVersion = '1.0.0';
// First commit
$firstCommit = Repository::getFirstCommit();
if (!$firstRelease) {
$lastVersion = Repository::getLastTag($tagPrefix, $merged);
$bumpRelease = SemanticVersion::PATCH;
if ($majorRelease) {
$bumpRelease = SemanticVersion::MAJOR;
} elseif ($minorRelease) {
$bumpRelease = SemanticVersion::MINOR;
} elseif ($patchRelease) {
$bumpRelease = SemanticVersion::PATCH;
} elseif ($preRelease) {
$bumpRelease = SemanticVersion::RC;
$lastVersion = Repository::getLastTag($tagPrefix, $merged, SemanticVersion::RC);
$autoBump = !$this->config->skipBump();
} elseif ($betaRelease) {
$bumpRelease = SemanticVersion::BETA;
$lastVersion = Repository::getLastTag($tagPrefix, $merged, SemanticVersion::BETA);
$autoBump = !$this->config->skipBump();
} elseif ($alphaRelease) {
$bumpRelease = SemanticVersion::ALPHA;
$lastVersion = Repository::getLastTag($tagPrefix, $merged, SemanticVersion::ALPHA);
$autoBump = !$this->config->skipBump();
} else {
$autoBump = !$this->config->skipBump();
}
// Generate new version code
$semver = new SemanticVersion($lastVersion, $tagPrefix);
$newVersion = $semver->bump($bumpRelease);
}
if (!empty($nextVersion)) {
$newVersion = $nextVersion;
$autoBump = false;
}
$newVersion = preg_replace('/^' . preg_quote($tagPrefix, '/') . '/', '', $newVersion);
$options = []; // Git retrieve options per version
if ($history) {
$changelogCurrent = ''; // Clean changelog file
$tags = Repository::getTags($tagPrefix);
$previousTag = null;
foreach ($tags as $key => $toTag) {
$fromTag = $firstCommit;
if (!empty($previousTag) && $key !== 0) {
$fromTag = $previousTag;
}
$commitDate = Repository::getCommitDate($toTag);
$options[$toTag] = [
'from' => $fromTag,
'to' => $toTag,
'date' => $commitDate->format($dateFormat),
'options' => "{$fromTag}...{$toTag}",
'autoBump' => false,
];
$previousTag = $toTag;
}
if ($autoCommit) {
$options[$newVersion] = [
'from' => $lastVersion,
'to' => $newVersion,
'date' => $today->format($dateFormat),
'options' => "{$lastVersion}...HEAD",
'autoBump' => false,
];
}
$options = array_reverse($options);
} else {
if ($firstRelease) {
// Get all commits from the first one
$additionalParams = "{$firstCommit}...HEAD";
$lastVersion = $firstCommit;
if (empty($fromTag)) {
$fromTag = $firstCommit;
}
} else {
// Get latest commits from last version date
$additionalParams = "{$lastVersion}...HEAD";
if (empty($fromTag)) {
$fromTag = $lastVersion;
}
}
// Clean ranges
if ((!empty($fromDate) || !empty($toDate)) &&
empty($fromTag) &&
empty($toTag)) {
$additionalParams = '';
}
// Tag range
if (!empty($fromTag) ||
!empty($toTag)) {
if (empty($toTag)) {
$toTag = 'HEAD';
}
$additionalParams = "{$fromTag}...{$toTag}";
}
// Date range
if (!empty($fromDate) ||
!empty($toDate)) {
if (!empty($fromDate)) {
$additionalParams .= ' --since="' . date('Y-m-d', strtotime($fromDate)) . '"';
}
if (!empty($toDate)) {
$time = strtotime($toDate);
$additionalParams .= ' --before="' . date('Y-m-d', $time) . '"';
$today->setTimestamp($time);
}
}
$options[$newVersion] = [
'from' => $lastVersion,
'to' => $newVersion,
'date' => $today->format($dateFormat),
'options' => $additionalParams,
'autoBump' => $autoBump,
];
}
$summary = [];
foreach ($this->config->getTypes() as $type) {
$summary[$type] = 0;
}
foreach ($options as $params) {
$commitsRaw = Repository::getCommits($params['options']);
usort($commitsRaw, function ($x, $y) use ($sortBy, $sortOrientation) {
if (property_exists($x, $sortBy)) {
if ($sortOrientation === 'ASC') {
return $x->{$sortBy} <=> $y->{$sortBy};
}
return $y->{$sortBy} <=> $x->{$sortBy};
}
return 0;
});
// Get all commits information
$commits = [];
foreach ($commitsRaw as $commitRaw) {
$commit = ConventionalCommit::fromCommit($commitRaw);
// Not a conventional commit
if (!$commit->isValid()) {
continue;
}
// Check ignored commit
$ignore = false;
foreach ($this->config->getIgnorePatterns() as $pattern) {
if (preg_match($pattern, $commit->getHeader())) {
$ignore = true;
break;
}
}
// Add commit
if (!$ignore) {
$commits[] = $commit;
}
}
// Changes groups sorting
$changes = [];
foreach ($this->config->getTypes() as $type) {
$changes[$type] = [];
}
// Group all changes to lists by type
$types = $this->config->getAllowedTypes();
foreach ($commits as $commit) {
if (in_array($commit->getType(), $types) || $commit->isBreakingChange()) {
$itemKey = $this->getItemKey($commit->getDescription());
$breakingChanges = $commit->getBreakingChanges();
$type = (string)$commit->getType();
$scope = $commit->getScope();
if ($this->config->isPrettyScope()) {
$scope = $commit->getScope()->toPrettyString();
}
$hash = $commit->getHash();
foreach ($breakingChanges as $description) {
$breakingType = Configuration::BREAKING_CHANGES_TYPE;
$key = $this->getItemKey($description);
if (empty($description) || $itemKey === $key) {
$commit->setBreakingChange(true);
continue;
}
// Clone commit as breaking with different description message
$breakingCommit = new ConventionalCommit();
$breakingCommit->setType($breakingType)
->setDescription($description)
->setScope($scope)
->setHash($hash);
$changes[$breakingType][(string)$scope][$key][$hash] = $breakingCommit;
$summary[$breakingType]++;
}
$changes[$type][(string)$scope][$itemKey][$hash] = $commit;
$summary[$type]++;
}
}
if ($params['autoBump']) {
$semver = new SemanticVersion($params['from'], $tagPrefix);
$bumpRelease = SemanticVersion::PATCH;
if ($summary['breaking_changes'] > 0) {
$bumpRelease = SemanticVersion::MAJOR;
if (version_compare($semver->getVersion(), '1.0.0', '<')) {
$bumpRelease = SemanticVersion::MINOR;
}
} elseif ($summary['feat'] > 0) {
$bumpRelease = SemanticVersion::MINOR;
}
$newVersion = $semver->bump($bumpRelease);
if ($preRelease) {
$extraRelease = Repository::getLastReleaseCandidateTag($tagPrefix, $merged);
$bumpExtra = SemanticVersion::RC;
} elseif ($alphaRelease) {
$extraRelease = Repository::getLastAlphaTag($tagPrefix, $merged);
$bumpExtra = SemanticVersion::ALPHA;
} elseif ($betaRelease) {
$extraRelease = Repository::getLastBetaTag($tagPrefix, $merged);
$bumpExtra = SemanticVersion::BETA;
}
if (!empty($bumpExtra)) {
$semVer = new SemanticVersion($extraRelease, $tagPrefix);
$newVersion = $semVer->bump($bumpRelease, $bumpExtra);
}
$params['to'] = $newVersion;
}
// Initialize changelogs
$params['to'] = $this->getVersionCode($params['to'], $tagPrefix, $tagSuffix);
$compareUrl = $this->getCompareUrl($params['from'], "{$tagPrefix}{$params['to']}{$tagSuffix}");
$markdownCompareLink = $this->getMarkdownLink($params['to'], $compareUrl);
$changeLogVersionHeading = $this->getChangelogVersionHeading($markdownCompareLink, $params['date']);
$changelogNew .= $changeLogVersionHeading . "\n";
// Add all changes list to new changelog
$changelogNew .= $this->getMarkdownChanges($changes);
}
$filesToCommit = [$file];
if (isset($commits) && !count($commits) && $noChangeWithoutCommits) {
$output->warning('No commits since last version.');
return 0; // Command::SUCCESS;
}
if ($this->config->isPackageBump()) {
foreach ($packageBumps as $packageBump) {
try {
/**
* @var PackageBump
*/
$bumper = new $packageBump($root);
if ($bumper->exists()) {
$bumper->setVersion($newVersion);
$bumper->save();
$filesToCommit[] = $bumper->getFilePath();
if ($this->config->isPackageLockCommit()) {
$filesToCommit = array_merge($filesToCommit, $bumper->getExistingLockFiles());
}
}
} catch (\Exception $e) {
$output->error('An error occurred bumping package version: ' . $e->getMessage());
}
}
$filesToCommit = array_unique($filesToCommit);
}
// Print summary
if (!empty($summary)) {
$output->section('Summary');
$elements = [];
foreach ($summary as $type => $count) {
if ($count > 0) {
$elements[] = $count . ' ' . $this->config->getTypeDescription($type);
}
}
$output->listing($elements);
}
// Save new changelog prepending the current one
file_put_contents($file, $mainHeader . "{$changelogNew}{$changelogCurrent}");
$output->success('Changelog generated!');
$output->writeln(" > Changelog file: {$file}");
// Create commit
if ($autoCommit) {
if ($autoCommitAll) {
Repository::addAll();
}
$message = $this->getReleaseCommitMessage($newVersion);
$result = Repository::commit($message, $filesToCommit, $amend, $hooks);
if ($result !== false) {
$output->success('Release committed!');
// Create tag
if ($autoTag) {
$tag = $tagPrefix . $newVersion . $tagSuffix;
$result = Repository::tag($tag, $annotateTag);
if ($result !== false) {
$output->success("Release tagged with success! New version: {$tag}");
} else {
$output->error('An error occurred tagging the release!');
return 1; // Command::FAILURE;
}
}
} else {
$output->error('An error occurred committing the release!');
return 1; // Command::FAILURE;
}
}
// Hook post run
$this->config->postRun();
return 0; // Command::SUCCESS;
}
protected function removeHeader(string $content): string
{
return ltrim(preg_replace(
'#<!--- BEGIN HEADER -->.*<!--- END HEADER -->(.*?)#iUs',
'\1',
$content
));
}
/**
* Get version code.
*/
protected function getVersionCode(string $tag, string $prefix, string $suffix): string
{
$version = preg_replace('#^v#i', '', $tag);
// Remove tag prefix
$rePrefix = '/^' . preg_quote($prefix, '/') . '/';
$version = preg_replace($rePrefix, '', $version);
// Remove tag suffix
$reSuffix = '/' . preg_quote($suffix, '/') . '$/';
$version = preg_replace($reSuffix, '', $version);
return $version;
}
/**
* Generate item key for merge commit with similar description.
*/
protected function getItemKey(string $string): string
{
return strtolower(preg_replace('/[^a-zA-Z0-9_-]+/', '', $string));
}
/**
* Generate markdown from changes.
*
* @param ConventionalCommit[][][][] $changes
*/
protected function getMarkdownChanges(array $changes): string
{
$changelog = '';
// Add all changes list to new changelog
foreach ($changes as $type => $list) {
if (empty($list)) {
continue;
}
$label = $this->config->getTypeLabel($type);
$changelog .= "\n### {$label}\n\n";
ksort($list);
foreach ($list as $scope => $items) {
if ($this->config->getSortBy() === 'subject') {
ksort($items, SORT_NATURAL);
}
if (is_string($scope) && !empty($scope)) {
// scope section
$changelog .= "\n##### {$scope}\n\n";
}
foreach ($items as $itemsList) {
$description = '';
$sha = '';
$references = '';
$mentions = '';
$shaGroup = [];
$refsGroup = [];
$mentionsGroup = [];
foreach ($itemsList as $item) {
$description = ucfirst($item->getDescription());
// Hashes
if (!$this->config->isHiddenHash() && !empty($item->getHash())) {
$commitUrl = $this->getCommitUrl($item->getHash());
$shaGroup[] = $this->getMarkdownLink($item->getShortHash(), $commitUrl);
}
// References
if (!$this->config->isHiddenReferences()) {
$refs = $item->getReferences();
foreach ($refs as $ref) {
$refId = $ref->getId();
$refUrl = $this->getIssueUrl($refId);
$refsGroup[] = $this->getMarkdownLink($ref, $refUrl);
}
}
// Mentions
$commitMentions = $item->getMentions();
foreach ($commitMentions as $mention) {
$user = $mention->getUser();
$userUrl = $this->getUserUrl($user);
$text = $this->getMarkdownLink("*{$mention}*", $userUrl);
if (strpos($description, (string)$mention) !== false) {
$description = str_replace($mention, $text, $description);
} elseif (!$this->config->isHiddenMentions()) {
$mentionsGroup[] = $text;
}
}
}
if (!$this->config->isHiddenHash() && !empty($shaGroup)) {
$sha = '(' . implode(', ', $shaGroup) . ')';
}
if (!$this->config->isHiddenReferences() && !empty($refsGroup)) {
$references = implode(', ', $refsGroup);
}
if (!$this->config->isHiddenMentions() && !empty($mentionsGroup)) {
$mentions = '*[*' . implode(', ', $mentionsGroup) . '*]*';
}
$changelog .= Formatter::clean("* {$description} {$references} {$sha} {$mentions}");
$changelog .= PHP_EOL;
}
}
}
// Add version separator
if (!$this->config->isHiddenVersionSeparator()) {
$changelog .= "\n\n---\n";
}
$changelog .= "\n";
return $changelog;
}
/**
* Get Markdown link.
*/
protected function getMarkdownLink(string $text, string $url): string
{
$url = preg_replace('/([^:])(\/{2,})/', '$1/', $url); // Remove double slashes
return !$this->config->isDisableLinks() ? "[{$text}]({$url})" : $text;
}
protected function getChangelogVersionHeading($markdownCompareLink, $versionDate)
{
$versionFormat = $this->config->getChangelogVersionFormat();
// Handle the replacements.
$versionData = $this->getCompiledString($versionFormat, [
'version' => $markdownCompareLink,
'date' => $versionDate,
]);
return $versionData;
}
/**
* Compile string with values.
*/
public function getCompiledString(string $format, array $values): string
{
$string = $format;
$values = array_merge($this->remote, $values);
foreach ($values as $key => $value) {
$string = str_replace('{{' . $key . '}}', $value, $string);
}
return $string;
}
/**
* Get commit url.
*/
public function getCommitUrl(string $hash): string
{
if (!$this->hasValidRemoteUrl) {
return '#';
}
$protocol = $this->config->getUrlProtocol();
$format = $this->config->getCommitUrlFormat();
$url = $this->getCompiledString($format, ['hash' => $hash]);
return "{$protocol}://{$url}";
}
/**
* Get commit compare url.
*/
public function getCompareUrl(string $previousTag, string $currentTag): string
{
if (!$this->hasValidRemoteUrl) {
return '#';
}
$protocol = $this->config->getUrlProtocol();
$format = $this->config->getCompareUrlFormat();
$url = $this->getCompiledString($format, ['previousTag' => $previousTag, 'currentTag' => $currentTag]);
return "{$protocol}://{$url}";
}
/**
* Get issue url.
*/
public function getIssueUrl(string $id): string
{
if (!$this->hasValidRemoteUrl) {
return '#';
}
$protocol = $this->config->getUrlProtocol();
$format = $this->config->getIssueUrlFormat();
$url = $this->getCompiledString($format, ['id' => $id]);
return "{$protocol}://{$url}";
}
/**
* Get user url.
*/
public function getUserUrl(string $user): string
{
if (!$this->hasValidRemoteUrl) {
return '#';
}
$protocol = $this->config->getUrlProtocol();
$format = $this->config->getUserUrlFormat();
$url = $this->getCompiledString($format, ['user' => $user]);
return "{$protocol}://{$url}";
}
/**
* Get release commit message.
*/
public function getReleaseCommitMessage(string $tag): string
{
$format = $this->config->getReleaseCommitMessageFormat();
return $this->getCompiledString($format, ['currentTag' => $tag]);
}
}

View file

@ -0,0 +1,933 @@
<?php
namespace ConventionalChangelog;
class Configuration
{
/**
* Working dir.
*
* @var string
*/
protected $root = './';
/**
* Changelog filename.
*
* @var string
*/
protected $path = 'CHANGELOG.md';
/**
* Header description.
*
* @var string
*/
protected $headerTitle = 'Changelog';
/**
* Header title.
*
* @var string
*/
protected $headerDescription = 'All notable changes to this project will be documented in this file.';
/**
* Preset of types allowed on changelog and labels.
* Sorting must be preserved.
*
* @var string[][]
*/
protected $preset = [
'feat' => ['label' => 'Features', 'description' => 'New features'],
'perf' => ['label' => 'Performance Improvements', 'description' => 'Code changes that improves performance'],
'fix' => ['label' => 'Bug Fixes', 'description' => 'Bugs and issues resolution'],
'refactor' => ['label' => 'Code Refactoring', 'description' => 'A code change that neither fixes a bug nor adds a feature'],
'style' => ['label' => 'Styles', 'description' => 'Changes that do not affect the meaning of the code'],
'test' => ['label' => 'Tests', 'description' => 'Adding missing tests or correcting existing tests'],
'build' => ['label' => 'Builds', 'description' => 'Changes that affect the build system or external dependencies '],
'ci' => ['label' => 'Continuous Integrations', 'description' => 'Changes to CI configuration files and scripts'],
'docs' => ['label' => 'Documentation', 'description' => 'Documentation changes'],
'chore' => ['label' => 'Chores', 'description' => "Other changes that don't modify the source code or test files"],
'revert' => ['label' => 'Reverts', 'description' => 'Reverts a previous commit'],
];
/**
* Key of breaking changes.
*
* @var string
*/
public const BREAKING_CHANGES_TYPE = 'breaking_changes';
/**
* Preset of breaking changes.
*
* @var string[][]
*/
protected $breakingChangesPreset = ['label' => '⚠ BREAKING CHANGES', 'description' => 'Code changes that potentially causes other components to fail'];
/**
* Types allowed on changelog.
*
* @var string[][]
*/
protected $types = [];
/**
* Bump package.
*
* @var bool
*/
protected $packageBump = true;
/**
* Bump packages.
*
* @var array
*/
protected $packageBumps = [];
/**
* Commit package lock file.
*
* @var bool
*/
protected $packageLockCommit = true;
/**
* Ignore message commit patterns.
*
* @var string[]
*/
protected $ignorePatterns = [
'/^chore\(release\):/i',
];
/**
* Ignore types.
*
* @var string[]
*/
protected $ignoreTypes = ['build', 'chore', 'ci', 'docs', 'perf', 'refactor', 'revert', 'style', 'test'];
/**
* Tag prefix.
*
* @var string
*/
protected $tagPrefix = 'v';
/**
* Skip tag.
*
* @var bool
*/
protected $skipTag = false;
/**
* Skip bump.
*
* @var bool
*/
protected $skipBump = false;
/**
* Skip verify.
*
* @var bool
*/
protected $skipVerify = false;
/**
* Render text instead of links.
*
* @var bool
*/
protected $disableLinks = false;
/**
* Hidden references.
*
* @var bool
*/
protected $hiddenReferences = false;
/**
* Hidden mentions.
*
* @var bool
*/
protected $hiddenMentions = false;
/**
* Hidden hash.
*
* @var bool
*/
protected $hiddenHash = false;
/**
* Tag suffix.
*
* @var string
*/
protected $tagSuffix = '';
/**
* The URL protocol of all repository urls on changelogs.
*
* @var string
*/
protected $urlProtocol = 'https';
/**
* Date format.
*
* @var string
*/
protected $dateFormat = 'Y-m-d';
/**
* Allows configurable changelog version header format.
*
* @var string
*/
protected $changelogVersionFormat = '## {{version}} ({{date}})';
/**
* A URL representing a specific commit at a hash.
*
* @var string
*/
protected $commitUrlFormat = '{{host}}/{{owner}}/{{repository}}/commit/{{hash}}';
/**
* A URL representing the comparison between two git sha.
*
* @var string
*/
protected $compareUrlFormat = '{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}';
/**
* A URL representing the issue format (allowing a different URL format to be swapped in for Gitlab, Bitbucket, etc).
*
* @var string
*/
protected $issueUrlFormat = '{{host}}/{{owner}}/{{repository}}/issues/{{id}}';
/**
* A URL representing the a user's profile URL on GitHub, Gitlab, etc. This URL is used for substituting @abc with https://github.com/abc in commit messages.
*
* @var string
*/
protected $userUrlFormat = '{{host}}/{{user}}';
/**
* A string to be used to format the auto-generated release commit message.
*
* @var string
*/
protected $releaseCommitMessageFormat = 'chore(release): {{currentTag}}';
/**
* Prettify scope.
*
* @var bool
*/
protected $prettyScope = true;
/**
* Hide Version Separator.
*
* @var bool
*/
protected $hiddenVersionSeparator = false;
/**
* Sort by options and orientation.
*
* @var string[]
*/
protected const SORT_BY = [
'date' => 'DESC',
'subject' => 'ASC',
'authorName' => 'ASC',
'authorEmail' => 'ASC',
'authorDate' => 'DESC',
'committerName' => 'ASC',
'committerEmail' => 'ASC',
'committerDate' => 'DESC',
];
/**
* Commit sorting.
*
* @var string
*/
protected $sortBy = 'subject';
/**
* Pre run.
*
* @var mixed
*/
protected $preRun;
/**
* Post run.
*
* @var mixed
*/
protected $postRun;
/**
* Constructor.
*/
public function __construct(array $settings = [])
{
$this->setRoot();
$this->setTypes($this->preset);
$this->fromArray($settings);
}
/**
* From array.
*/
public function fromArray(array $array)
{
$defaults = [
'root' => null,
'headerTitle' => $this->getHeaderTitle(),
'headerDescription' => $this->getHeaderDescription(),
'sortBy' => $this->getSortBy(),
'path' => $this->getPath(),
'preset' => $this->getPreset(),
'types' => [],
'packageBump' => $this->isPackageBump(),
'packageBumps' => [],
'packageLockCommit' => $this->isPackageLockCommit(),
'ignoreTypes' => $this->getIgnoreTypes(),
'ignorePatterns' => $this->getIgnorePatterns(),
'tagPrefix' => $this->getTagPrefix(),
'tagSuffix' => $this->getTagSuffix(),
'skipBump' => $this->skipBump(),
'skipTag' => $this->skipTag(),
'skipVerify' => $this->skipVerify(),
'disableLinks' => $this->isDisableLinks(),
'hiddenHash' => $this->isHiddenHash(),
'hiddenMentions' => $this->isHiddenMentions(),
'hiddenReferences' => $this->isHiddenReferences(),
'prettyScope' => $this->isPrettyScope(),
'urlProtocol' => $this->getUrlProtocol(),
'dateFormat' => $this->getDateFormat(),
'changelogVersionFormat' => $this->getChangelogVersionFormat(),
'commitUrlFormat' => $this->getCommitUrlFormat(),
'compareUrlFormat' => $this->getCompareUrlFormat(),
'issueUrlFormat' => $this->getIssueUrlFormat(),
'userUrlFormat' => $this->getUserUrlFormat(),
'hiddenVersionSeparator' => $this->isHiddenVersionSeparator(),
'releaseCommitMessageFormat' => $this->getReleaseCommitMessageFormat(),
'preRun' => $this->getPreRun(),
'postRun' => $this->getPostRun(),
];
$params = array_replace_recursive($defaults, $array);
// Ignore Types
if (isset($array['ignoreTypes'])) {
$params['ignoreTypes'] = $array['ignoreTypes']; // Overwrite ignored types
}
// Set Types (overwrite ignored types)
if (!empty($array['types'])) {
foreach ($this->preset as $type => $value) {
if (!in_array($type, $array['types'])) {
if (isset($params['preset'][$type])) {
unset($params['preset'][$type]);
}
} else {
$params['preset'][$type] = $value;
}
}
}
// Add breaking changes
$breakingPreset = $this->getBreakingChangesPreset();
$params['preset'] = array_merge($breakingPreset, $params['preset']);
$this
// Paths
->setRoot($params['root'])
->setPath($params['path'])
// Types
->setIgnorePatterns($params['ignorePatterns'])
->setIgnoreTypes($params['ignoreTypes'])
->setTypes($params['preset'])
// Package
->setPackageBump($params['packageBump'])
->setPackageBumps($params['packageBumps'])
->setPackageLockCommit($params['packageLockCommit'])
// Document
->setHeaderTitle($params['headerTitle'])
->setHeaderDescription($params['headerDescription'])
->setSortBy($params['sortBy'])
// Tag
->setTagPrefix($params['tagPrefix'])
->setTagSuffix($params['tagSuffix'])
// Skips
->setSkipBump($params['skipBump'])
->setSkipTag($params['skipTag'])
->setSkipVerify($params['skipVerify'])
// Links
->setDisableLinks($params['disableLinks'])
// Hidden
->setHiddenHash($params['hiddenHash'])
->setHiddenMentions($params['hiddenMentions'])
->setHiddenReferences($params['hiddenReferences'])
->setHiddenVersionSeparator($params['hiddenVersionSeparator'])
// Formats
->setPrettyScope($params['prettyScope'])
->setUrlProtocol($params['urlProtocol'])
->setDateFormat($params['dateFormat'])
->setCommitUrlFormat($params['commitUrlFormat'])
->setCompareUrlFormat($params['compareUrlFormat'])
->setIssueUrlFormat($params['issueUrlFormat'])
->setUserUrlFormat($params['userUrlFormat'])
->setReleaseCommitMessageFormat($params['releaseCommitMessageFormat'])
->setDisableLinks($params['disableLinks'])
->setChangelogVersionFormat($params['changelogVersionFormat'])
// Hooks
->setPreRun($params['preRun'])
->setPostRun($params['postRun']);
}
/**
* @return string[]
*/
public function getTypes(): array
{
return array_keys($this->types);
}
/**
* @return string[]
*/
public function getAllowedTypes(): array
{
$types = $this->types;
unset($types['breaking_changes']);
return array_keys($types);
}
public function getTypeLabel(string $type): string
{
return $this->types[$type]['label'];
}
public function getTypeDescription(string $type): string
{
return $this->types[$type]['description'] ?? '';
}
/**
* @param string[][] $types
*/
public function setTypes(array $types): self
{
$ignoreTypes = $this->getIgnoreTypes();
foreach ($ignoreTypes as $type) {
unset($types[$type]); // Unset excluded types
}
$this->types = $types;
return $this;
}
public function getPath(): string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
public function getHeaderTitle(): string
{
return $this->headerTitle;
}
public function setHeaderTitle(string $headerTitle): self
{
$this->headerTitle = $headerTitle;
return $this;
}
public function getHeaderDescription(): string
{
return $this->headerDescription;
}
public function setHeaderDescription(string $headerDescription): self
{
$this->headerDescription = $headerDescription;
return $this;
}
/**
* @return string[]
*/
public function getIgnorePatterns(): array
{
return $this->ignorePatterns;
}
/**
* @param string[] $ignorePatterns
*/
public function setIgnorePatterns(array $ignorePatterns): self
{
foreach ($ignorePatterns as $key => $pattern) {
if (!$this->isRegex($pattern)) {
$ignorePatterns[$key] = '#' . preg_quote($pattern, '#') . '#i';
}
}
$this->ignorePatterns = $ignorePatterns;
return $this;
}
/**
* @return string[][]
*/
public function getPreset(): array
{
return array_merge($this->getBreakingChangesPreset(), $this->preset);
}
/**
* @param string[] $ignoreTypes
*/
public function setIgnoreTypes(array $ignoreTypes): self
{
$types = $this->getTypes();
foreach ($ignoreTypes as $type) {
unset($types[$type]); // Unset excluded types
}
$this->ignoreTypes = $ignoreTypes;
$this->types = $types;
return $this;
}
/**
* @return string[]
*/
public function getIgnoreTypes(): array
{
return $this->ignoreTypes;
}
public function getRoot(): string
{
return $this->root;
}
/**
* @param string $root
*/
public function setRoot(?string $root = null): self
{
if (empty($root) || !is_dir($root)) {
$root = getcwd();
}
$this->root = $root;
return $this;
}
public function getTagPrefix(): string
{
return $this->tagPrefix;
}
public function setTagPrefix(string $tagPrefix): self
{
$this->tagPrefix = $tagPrefix;
return $this;
}
public function getTagSuffix(): string
{
return $this->tagSuffix;
}
public function setTagSuffix(string $tagSuffix): self
{
$this->tagSuffix = $tagSuffix;
return $this;
}
public function skipTag(): bool
{
return $this->skipTag;
}
public function setSkipTag(bool $skipTag): self
{
$this->skipTag = $skipTag;
return $this;
}
public function skipBump(): bool
{
return $this->skipBump;
}
public function setSkipBump(bool $skipBump): self
{
$this->skipBump = $skipBump;
return $this;
}
public function skipVerify(): bool
{
return $this->skipVerify;
}
public function setSkipVerify(bool $skipVerify): self
{
$this->skipVerify = $skipVerify;
return $this;
}
/**
* @return \string[][]
*/
public function getBreakingChangesPreset(): array
{
return [self::BREAKING_CHANGES_TYPE => $this->breakingChangesPreset];
}
public function getCommitUrlFormat(): string
{
return $this->commitUrlFormat;
}
public function setCommitUrlFormat(string $commitUrlFormat): self
{
$this->commitUrlFormat = $commitUrlFormat;
return $this;
}
public function getCompareUrlFormat(): string
{
return $this->compareUrlFormat;
}
public function setCompareUrlFormat(string $compareUrlFormat): self
{
$this->compareUrlFormat = $compareUrlFormat;
return $this;
}
public function getIssueUrlFormat(): string
{
return $this->issueUrlFormat;
}
public function setIssueUrlFormat(string $issueUrlFormat): self
{
$this->issueUrlFormat = $issueUrlFormat;
return $this;
}
public function getUserUrlFormat(): string
{
return $this->userUrlFormat;
}
public function setUserUrlFormat(string $userUrlFormat): self
{
$this->userUrlFormat = $userUrlFormat;
return $this;
}
public function getReleaseCommitMessageFormat(): string
{
return $this->releaseCommitMessageFormat;
}
public function setReleaseCommitMessageFormat(string $releaseCommitMessageFormat): self
{
$this->releaseCommitMessageFormat = $releaseCommitMessageFormat;
return $this;
}
public function getUrlProtocol(): string
{
return $this->urlProtocol;
}
public function setUrlProtocol(string $urlProtocol): self
{
$this->urlProtocol = $urlProtocol;
return $this;
}
public function getDateFormat(): string
{
return $this->dateFormat;
}
public function setDateFormat(string $dateFormat): self
{
$this->dateFormat = $dateFormat;
return $this;
}
public function isDisableLinks(): bool
{
return $this->disableLinks;
}
public function setDisableLinks(bool $disableLinks): self
{
$this->disableLinks = $disableLinks;
return $this;
}
public function getChangelogVersionFormat(): string
{
return $this->changelogVersionFormat;
}
public function setChangelogVersionFormat($changelogVersionFormat): self
{
$this->changelogVersionFormat = $changelogVersionFormat;
return $this;
}
public function isHiddenReferences(): bool
{
return $this->hiddenReferences;
}
public function setHiddenReferences(bool $hiddenReferences): self
{
$this->hiddenReferences = $hiddenReferences;
return $this;
}
public function isHiddenMentions(): bool
{
return $this->hiddenMentions;
}
public function setHiddenMentions(bool $hiddenMentions): self
{
$this->hiddenMentions = $hiddenMentions;
return $this;
}
public function isHiddenHash(): bool
{
return $this->hiddenHash;
}
public function setHiddenHash(bool $hiddenHash): self
{
$this->hiddenHash = $hiddenHash;
return $this;
}
public function isPrettyScope(): bool
{
return $this->prettyScope;
}
public function setPrettyScope(bool $prettyScope): self
{
$this->prettyScope = $prettyScope;
return $this;
}
public function getSortBy(): string
{
$sortBy = $this->sortBy;
if ($sortBy === 'date') {
$sortBy = 'committerDate';
}
return $sortBy;
}
public function setSortBy(string $sortBy): self
{
$sortBy = trim($sortBy);
if (!array_key_exists($sortBy, self::SORT_BY)) {
$sortBy = 'date';
}
$this->sortBy = $sortBy;
return $this;
}
public function getSortOrientation(string $sort): string
{
return self::SORT_BY[$sort];
}
/**
* @param mixed $hook
*/
public function runHook($hook)
{
if (is_string($hook)) {
system($hook);
} elseif (is_callable($hook)) {
call_user_func($hook);
}
}
/**
* @return mixed
*/
public function getPreRun()
{
return $this->preRun;
}
public function preRun()
{
$this->runHook($this->preRun);
}
/**
* @param mixed $preRun
*/
public function setPreRun($preRun): self
{
$this->preRun = $preRun;
return $this;
}
/**
* @return mixed
*/
public function getPostRun()
{
return $this->postRun;
}
public function postRun()
{
$this->runHook($this->postRun);
}
/**
* @param mixed $postRun
*/
public function setPostRun($postRun): self
{
$this->postRun = $postRun;
return $this;
}
public function isPackageBump(): bool
{
return $this->packageBump;
}
public function setPackageBump(bool $packageBump): Configuration
{
$this->packageBump = $packageBump;
return $this;
}
public function setPackageBumps(array $packageBumps): Configuration
{
$this->packageBumps = $packageBumps;
return $this;
}
public function getPackageBumps(): array
{
return $this->packageBumps;
}
public function isPackageLockCommit(): bool
{
return $this->packageLockCommit;
}
public function setPackageLockCommit(bool $packageLockCommit): Configuration
{
$this->packageLockCommit = $packageLockCommit;
return $this;
}
public function isHiddenVersionSeparator(): bool
{
return $this->hiddenVersionSeparator;
}
public function setHiddenVersionSeparator($hiddenVersionSeparator): Configuration
{
$this->hiddenVersionSeparator = $hiddenVersionSeparator;
return $this;
}
/**
* Validate settings.
*
* @param mixed $settings
*/
public static function validate($settings): bool
{
return is_array($settings);
}
/**
* Check if is regex.
*
* @return bool
*/
protected function isRegex(string $pattern)
{
return @preg_match($pattern, '') !== false;
}
}

View file

@ -0,0 +1,210 @@
<?php
namespace ConventionalChangelog;
use ConventionalChangelog\Git\Repository;
use ConventionalChangelog\Helper\SemanticVersion;
use ConventionalChangelog\Helper\ShellCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class DefaultCommand extends Command
{
/**
* Changelog.
*
* @var Changelog
*/
public $changelog;
/**
* @var Configuration
*/
public $config;
/**
* @var array
*/
public $settings = [];
/**
* Command name.
*
* @var string
*/
protected static $defaultName = 'changelog';
/**
* Output with style.
*
* @var SymfonyStyle
*/
protected $output;
/**
* Constructor.
*/
public function __construct(array $settings = [])
{
parent::__construct(self::$defaultName);
$this->settings = $settings;
}
/**
* Configure.
*
* @return void
*/
protected function configure()
{
$this
->setDescription("Generate changelogs and release notes from a project's commit messages" .
'and metadata and automate versioning with semver.org and conventionalcommits.org')
->setDefinition([
new InputArgument('path', InputArgument::OPTIONAL, 'Specify the path directory where generate changelog'),
new InputOption('config', null, InputOption::VALUE_REQUIRED, 'Specify the configuration file path'),
new InputOption('commit', 'c', InputOption::VALUE_NONE, 'Commit the new release once changelog is generated'),
new InputOption('amend', 'a', InputOption::VALUE_NONE, 'Amend commit the new release once changelog is generated'),
new InputOption('commit-all', null, InputOption::VALUE_NONE, 'Commit all changes the new release once changelog is generated'),
new InputOption('first-release', null, InputOption::VALUE_NONE, "Run at first release (if --ver isn't specified version code it will be 1.0.0)"),
new InputOption('from-date', null, InputOption::VALUE_REQUIRED, 'Get commits from specified date [YYYY-MM-DD]'),
new InputOption('to-date', null, InputOption::VALUE_REQUIRED, 'Get commits last tag date (or specified on --from-date) to specified date [YYYY-MM-DD]'),
new InputOption('from-tag', null, InputOption::VALUE_REQUIRED, 'Get commits from specified tag'),
new InputOption('to-tag', null, InputOption::VALUE_REQUIRED, 'Get commits last tag (or specified on --from-tag) to specified tag'),
new InputOption('major', null, InputOption::VALUE_NONE, 'Major release (important changes)'),
new InputOption('minor', null, InputOption::VALUE_NONE, 'Minor release (add functionality)'),
new InputOption('patch', null, InputOption::VALUE_NONE, 'Patch release (bug fixes) [default]'),
new InputOption('rc', null, InputOption::VALUE_NONE, 'Release candidate'),
new InputOption('beta', null, InputOption::VALUE_NONE, 'Beta release'),
new InputOption('alpha', null, InputOption::VALUE_NONE, 'Alpha release'),
new InputOption('ver', null, InputOption::VALUE_REQUIRED, 'Specify the next release version code (semver)'),
new InputOption('history', null, InputOption::VALUE_NONE, 'Generate the entire history of changes of all releases'),
new InputOption('no-verify', null, InputOption::VALUE_NONE, 'Bypasses the pre-commit and commit-msg hooks'),
new InputOption('no-tag', null, InputOption::VALUE_NONE, 'Disable release auto tagging'),
new InputOption('no-change-without-commits', null, InputOption::VALUE_NONE, 'Do not apply change if no commits'),
new InputOption('annotate-tag', null, InputOption::VALUE_OPTIONAL, 'Make an unsigned, annotated tag object once changelog is generated', false),
new InputOption('merged', null, InputOption::VALUE_NONE, 'Only include commits whose tips are reachable from HEAD'),
]);
}
/**
* Execute.
*
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
/**
* Initialize.
*/
$this->output = new SymfonyStyle($input, $output);
if ($this->getApplication() !== null) {
$appName = $this->getApplication()->getName();
$appVersion = $this->getApplication()->getVersion();
$this->output->title($appName . ' ' . $appVersion);
}
/**
* Retrieve configuration settings.
*/
$config = $input->getOption('config');
if (!empty($config) && is_file($config)) {
$this->settings = require $config;
}
if (!Configuration::validate($this->settings)) {
$this->output->error('Not a valid configuration! Using default settings.');
$this->settings = [];
}
$this->config = new Configuration($this->settings);
// Get root path
$root = $input->getArgument('path');
if (empty($root) || !is_dir($root)) {
$root = $this->config->getRoot();
}
// Set working directory
chdir($root);
/**
* Check environment.
*/
$this->output->writeln('Checking environment requirements');
// Check shell exec function
if (!ShellCommand::isEnabled()) {
$this->output->error(
'It looks like shell_exec function is disabled on disable_functions config of your php.ini settings file. ' .
'Please check you configuration and enabled shell_exec to proceed.'
);
return 1; // Command::FAILURE;
}
$this->validRequirement('Commands executions enabled');
// Check git command
if (!ShellCommand::exists('git')) {
$this->output->error(
'It looks like Git is not installed on your system. ' .
'Please check how to install it from https://git-scm.com before run this command.'
);
return 1; // Command::FAILURE;
}
$this->validRequirement('Git detected correctly');
// Check git version
$gitVersion = ShellCommand::exec('git --version');
$gitSemver = new SemanticVersion($gitVersion);
$gitVersionCode = $gitSemver->getVersionCode();
if (version_compare($gitVersionCode, '2.1.4', '<')) {
$this->output->error(
'It looks like your Git version is ' . $gitVersionCode . ' that isn\'t compatible with this tool (min required is 2.1.4). ' .
'Please check how to update it from https://git-scm.com before run this command.'
);
return 1; // Command::FAILURE;
}
$this->validRequirement('Git version detected: ' . $gitVersionCode);
// Check working directory
if (!Repository::isInsideWorkTree()) {
$this->output->error('The directory "' . $root . '" isn\'t a valid git repository or isn\'t been detected correctly.');
return 1; // Command::FAILURE;
}
$this->validRequirement('Valid git worktree directory: ' . $root);
// Check git repository
if (Repository::hasRemoteUrl()) {
$url = Repository::getRemoteUrl();
if (empty(Repository::parseRemoteUrl())) {
$this->output->warning(
'The remote url of your git repository ("' . $url . '") not been parsed correctly. ' .
'Please open and issue including your repository url format to make it compatible with this tool here ' .
'https://github.com/marcocesarato/php-conventional-changelog/issues'
);
} else {
$this->validRequirement('Valid git remote repository url: ' . $url);
}
} else {
$this->output->warning(
'Remote repository url not detected. ' .
'It looks like your project is running only locally and not on a remote repository. ' .
'Care, all urls on the changelog will be empty.'
);
}
$this->output->newLine();
/**
* Generate changelog.
*/
$this->output->writeln('Generating changelog');
$this->changelog = new Changelog($this->config);
return $this->changelog->generate($root, $input, $this->output);
}
/**
* Print with valid requirement format.
*/
protected function validRequirement(string $messages)
{
$this->output->writeln(' ✓ ' . $messages);
}
}

View file

@ -0,0 +1,399 @@
<?php
namespace ConventionalChangelog\Git;
use ConventionalChangelog\Git\Commit\Body;
use ConventionalChangelog\Git\Commit\Footer;
use ConventionalChangelog\Git\Commit\Mention;
use ConventionalChangelog\Git\Commit\Subject;
use ConventionalChangelog\Helper\Formatter;
use ConventionalChangelog\Type\Stringable;
class Commit implements Stringable
{
/**
* @var string
*/
protected const PATTERN_FOOTER = "/(?<token>^([a-z0-9_-]+|BREAKING[[:blank:]]CHANGES?))(?<value>([:][[:blank:]]|[:]?[[:blank:]][#](?=\w)).*?)$/iums";
/**
* @var string
*/
protected const PATTERN_MENTION = "/(?:^|\s+)(?<mention>@(?<user>[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}))(?:$|\s+)/smi";
/**
* Raw content.
*
* @var string
*/
public $raw;
/**
* Subject content.
*
* @var Subject
*/
public $subject;
/**
* Body content.
*
* @var Body
*/
public $body;
/**
* Footers.
*
* @var Footer[]
*/
public $footers = [];
/**
* User Mentions.
*
* @var Mention[]
*/
public $mentions = [];
/**
* Sha hash.
*
* @var string
*/
public $hash;
/**
* @var \DateTime
*/
public $authorDate;
/**
* @var string
*/
public $authorName;
/**
* @var string
*/
public $authorEmail;
/**
* @var \DateTime
*/
public $committerDate;
/**
* @var string
*/
public $committerName;
/**
* @var string
*/
public $committerEmail;
public function __construct(string $commit = null)
{
// New commit or empty commit
if (empty($commit)) {
$this->setSubject('')
->setBody('');
return;
}
$raw = Formatter::clean($commit);
$this->setRaw($raw);
$this->parse();
}
public function __wakeup()
{
$this->parse();
}
public function __toString(): string
{
if (!empty($this->raw)) {
return $this->raw;
}
$header = $this->getSubject();
$message = $this->getMessage();
$string = $header . "\n\n" . $message;
return Formatter::clean($string);
}
/**
* From array.
*
* @throws \Exception
*/
public function fromArray(array $array): self
{
if (isset($array['raw'])) {
$this->setRaw($array['raw']);
}
if (isset($array['hash'])) {
$this->setHash($array['hash']);
}
if (isset($array['authorName'])) {
$this->setAuthorName($array['authorName']);
}
if (isset($array['authorEmail'])) {
$this->setAuthorEmail($array['authorEmail']);
}
if (isset($array['authorDate'])) {
$date = new \DateTime($array['authorDate']);
$this->setAuthorDate($date);
}
if (isset($array['committerName'])) {
$this->setCommitterName($array['committerName']);
}
if (isset($array['committerEmail'])) {
$this->setCommitterEmail($array['committerEmail']);
}
if (isset($array['committerDate'])) {
$date = new \DateTime($array['committerDate']);
$this->setCommitterDate($date);
}
$this->parse();
return $this;
}
public function setRaw(string $raw): self
{
$this->raw = $raw;
return $this;
}
public function setHash(string $hash): self
{
if ($this->isValidHash($hash)) {
$this->hash = $hash;
}
return $this;
}
public function getRaw(): ?string
{
return $this->raw;
}
public function getHash(): ?string
{
return $this->hash;
}
public function getShortHash(): ?string
{
return substr($this->hash, 0, 6);
}
public function getAuthorDate(): \DateTime
{
return $this->authorDate;
}
/**
* @param \DateTime|\DateTimeImmutable $authorDate
*/
public function setAuthorDate(\DateTimeInterface $authorDate): Commit
{
$this->authorDate = $authorDate;
return $this;
}
public function getAuthorName(): ?string
{
return $this->authorName;
}
public function setAuthorName(string $authorName): Commit
{
$this->authorName = $authorName;
return $this;
}
public function getAuthorEmail(): ?string
{
return $this->authorEmail;
}
public function setAuthorEmail(string $authorEmail): Commit
{
$this->authorEmail = $authorEmail;
return $this;
}
public function getCommitterDate(): \DateTime
{
return $this->committerDate;
}
/**
* @param \DateTime|\DateTimeImmutable $committerDate
*/
public function setCommitterDate(\DateTimeInterface $committerDate): Commit
{
$this->committerDate = $committerDate;
return $this;
}
public function getCommitterName(): ?string
{
return $this->committerName;
}
public function setCommitterName(string $committerName): Commit
{
$this->committerName = $committerName;
return $this;
}
public function getCommitterEmail(): ?string
{
return $this->committerEmail;
}
public function setCommitterEmail(string $committerEmail): Commit
{
$this->committerEmail = $committerEmail;
return $this;
}
public function getBody(): Body
{
return $this->body;
}
public function setBody(string $body): self
{
$body = Formatter::clean($body);
$footers = [];
if (preg_match_all(self::PATTERN_FOOTER, $body, $matches, PREG_SET_ORDER, 0)) {
foreach ($matches as $match) {
$footer = $match[0];
$body = str_replace($footer, '', $body);
$value = ltrim((string)$match['value'], ':');
$value = Formatter::clean($value);
$footers[] = new Footer((string)$match['token'], $value);
}
}
$this->setFooters($footers);
$body = Formatter::clean($body);
$this->body = new Body($body);
return $this;
}
public function getSubject(): Subject
{
return $this->subject;
}
public function setSubject(string $subject): self
{
$this->subject = new Subject($subject);
return $this;
}
/**
* @param Footer[] $footers
*/
public function setFooters(array $footers): self
{
$this->footers = $footers;
return $this;
}
/**
* @return Footer[]
*/
public function getFooters(): array
{
return $this->footers;
}
/**
* Set mentions.
*
* @param Mention[] $mentions
*/
public function setMentions(array $mentions): self
{
$this->mentions = array_unique($mentions);
return $this;
}
/**
* Get mentions.
*
* @return Mention[]
*/
public function getMentions(): array
{
return $this->mentions;
}
public function getMessage(): string
{
$footer = implode("\n", $this->footers);
return $this->body . "\n\n" . $footer;
}
/**
* Check if is valid SHA-1.
*/
protected function isValidHash(string $hash): bool
{
return (bool)preg_match('/^[0-9a-f]{40}$/i', $hash);
}
/**
* Parse raw commit.
*/
protected function parse()
{
// Empty
if (empty($this->raw)) {
return;
}
$rows = explode("\n", $this->raw);
$subject = $rows[0];
$body = '';
foreach ($rows as $i => $row) {
if ($i !== 0) {
$body .= $row . "\n";
}
}
$mentions = [];
if (preg_match_all(self::PATTERN_MENTION, $this->raw, $matches)) {
foreach ($matches['user'] as $match) {
$mentions[] = new Mention($match);
}
}
$this->setSubject($subject)
->setBody($body)
->setMentions($mentions);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Body implements Stringable
{
/**
* @var string
*/
protected $content = '';
public function __construct(?string $content = '')
{
$this->content = (string)$content;
}
public function __toString(): string
{
return ucfirst($this->content);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Description implements Stringable
{
/**
* @var string
*/
protected $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function __toString(): string
{
return $this->content;
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Footer implements Stringable
{
/**
* @var string[]
*/
public const TOKEN_CLOSE_ISSUE = [
'close',
'closes',
'closed',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved',
];
/**
* Token.
*
* @var string
*/
protected $token;
/**
* Value.
*
* @var string
*/
protected $value;
/**
* References.
*
* @var Reference[]
*/
protected $references = [];
public function __construct(string $token, string $value)
{
$this->token = trim($token);
$this->value = trim($value);
$refs = [];
$tokenLower = strtolower($this->token);
$values = preg_split('/[,\s]+/', $this->value, -1, PREG_SPLIT_NO_EMPTY);
foreach ($values as $val) {
if (isset($val[0]) && $val[0] === '#') {
$ref = ltrim($val, '#');
$obj = new Reference($ref);
if (in_array($tokenLower, self::TOKEN_CLOSE_ISSUE)) {
$obj->setClosed(true);
}
$refs[] = $obj;
}
}
$this->setReferences($refs);
}
public function __toString(): string
{
return $this->token . ': ' . $this->value;
}
public function getToken(): string
{
return strtolower($this->token);
}
public function getValue(): string
{
return ucfirst($this->value);
}
/**
* Set issues references.
*
* @return Reference[]
*/
public function setReferences(array $references): self
{
$this->references = array_unique($references);
return $this;
}
/**
* Get issues references.
*
* @return Reference[]
*/
public function getReferences(): array
{
return $this->references;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Mention implements Stringable
{
/**
* @var string
*/
protected $user;
public function __construct(string $user)
{
$this->user = $user;
}
public function __toString(): string
{
return '@' . $this->user;
}
public function getUser(): string
{
return $this->user;
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Reference implements Stringable
{
/**
* @var string
*/
protected $id;
/**
* @var bool
*/
protected $closed = false;
public function __construct(string $id)
{
$this->id = $id;
}
public function __toString(): string
{
return '#' . $this->id;
}
public function getId(): string
{
return $this->id;
}
public function isClosed(): bool
{
return $this->closed;
}
public function setClosed(bool $closed): self
{
$this->closed = $closed;
return $this;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Helper\Formatter;
use ConventionalChangelog\Type\Stringable;
class Scope implements Stringable
{
/**
* @var string
*/
protected $content;
public function __construct(?string $content = '')
{
$this->content = (string)$content;
}
public function __toString(): string
{
return $this->content;
}
/**
* Prettify.
*/
public function toPrettyString(): string
{
$string = ucfirst($this->content);
$string = preg_replace('/[_]+/m', ' ', $string);
$string = preg_replace('/((?<=\p{Ll})\p{Lu})|((?!\A)\p{Lu}(?>\p{Ll}))/u', ' $0', $string);
$string = preg_replace('/\.(php|md|json|txt|csv|js)($|\s)/', '', $string);
return Formatter::clean($string);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Subject implements Stringable
{
/**
* @var string
*/
protected $content = '';
public function __construct(?string $content = '')
{
$this->content = (string)$content;
}
public function __toString(): string
{
return $this->content;
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace ConventionalChangelog\Git\Commit;
use ConventionalChangelog\Type\Stringable;
class Type implements Stringable
{
/**
* @var string
*/
protected $content;
public function __construct(string $content)
{
$this->content = strtolower($content);
}
public function __toString(): string
{
return $this->content;
}
}

View file

@ -0,0 +1,222 @@
<?php
namespace ConventionalChangelog\Git;
use ConventionalChangelog\Configuration;
use ConventionalChangelog\Git\Commit\Description;
use ConventionalChangelog\Git\Commit\Reference;
use ConventionalChangelog\Git\Commit\Scope;
use ConventionalChangelog\Git\Commit\Type;
use ConventionalChangelog\Helper\Formatter;
class ConventionalCommit extends Commit
{
/**
* @var string
*/
protected const PATTERN_HEADER = "/^(?<type>[a-z]+)(?<breaking_before>[!]?)(\((?<scope>.+)\))?(?<breaking_after>[!]?)[:][[:blank:]](?<description>.+)/iums";
/**
* Type.
*
* @var Type
*/
public $type;
/**
* Scope.
*
* @var Scope
*/
public $scope;
/**
* Is breaking change.
*
* @var bool
*/
public $isBreakingChange = false;
/**
* Description.
*
* @var Description
*/
public $description;
public function __construct(?string $commit = null)
{
parent::__construct($commit);
$this->parse();
}
public function __toString(): string
{
if (!empty($this->raw)) {
return $this->raw;
}
$header = $this->getHeader();
$message = $this->getMessage();
$string = $header . "\n\n" . $message;
return Formatter::clean($string);
}
/**
* Check if is valid conventional commit.
*/
public function isValid(): bool
{
return (bool)preg_match(self::PATTERN_HEADER, $this->raw);
}
public function getType(): Type
{
$type = $this->type;
if ($this->isBreakingChange()) {
$type = new Type(Configuration::BREAKING_CHANGES_TYPE);
}
return $type;
}
public function getScope(): Scope
{
return $this->scope;
}
public function hasScope(): bool
{
return !empty((string)$this->scope);
}
public function isBreakingChange(): bool
{
return $this->isBreakingChange;
}
public function getDescription(): Description
{
return $this->description;
}
public function getBreakingChanges(): array
{
$messages = [];
foreach ($this->footers as $footer) {
if (preg_match('/^breaking[[:blank:]]changes?$/', $footer->getToken())) {
$messages[] = $footer->getValue();
}
}
return $messages;
}
/**
* Get issues references.
*
* @return Reference[]
*/
public function getReferences(): array
{
$refs = [];
foreach ($this->footers as $footer) {
$refs = array_merge($refs, $footer->getReferences());
}
return array_unique($refs);
}
public function getHeader(): string
{
$header = $this->type;
if ($this->hasScope()) {
$header .= '(' . $this->scope . ')';
}
if ($this->isBreakingChange) {
$header .= '!';
}
return $header . (': ' . $this->description);
}
public function setType(string $type): self
{
$this->type = new Type($type);
return $this;
}
public function setScope(?string $scope): self
{
$this->scope = new Scope($scope);
return $this;
}
public function setBreakingChange(bool $isBreakingChange): self
{
$this->isBreakingChange = $isBreakingChange;
return $this;
}
public function setDescription(string $description): self
{
$this->description = new Description($description);
return $this;
}
/**
* From commit.
*/
public static function fromCommit(Commit $commit): self
{
return unserialize(
preg_replace('/^O:\d+:"[^"]++"/', 'O:' . strlen(self::class) . ':"' . self::class . '"', serialize($commit)),
[self::class]
);
}
/**
* Parse raw commit.
*/
protected function parse()
{
parent::parse();
// Empty
if (empty($this->raw)) {
return;
}
// Not parsable
if (!$this->isValid()) {
return;
}
$header = $this->getSubject();
$this->parseHeader($header);
}
/**
* Parse header.
*/
protected function parseHeader(string $header)
{
preg_match(self::PATTERN_HEADER, $header, $matches);
$type = $matches['type'] ?? '';
$scope = $matches['scope'] ?? '';
$breakingBefore = $matches['breaking_before'] ?? '';
$breakingAfter = $matches['breaking_after'] ?? '';
$description = $matches['description'] ?? '';
$this->setType($type)
->setScope($scope)
->setBreakingChange(!empty($breakingBefore || !empty($breakingAfter)))
->setDescription($description);
}
}

View file

@ -0,0 +1,363 @@
<?php
namespace ConventionalChangelog\Git;
use ConventionalChangelog\Helper\SemanticVersion;
use ConventionalChangelog\Helper\ShellCommand;
class Repository
{
/**
* @var string
*/
protected static $delimiter = '----DELIMITER---';
/**
* Run shell command on working dir.
*/
protected static function run(string $string): string
{
$value = ShellCommand::exec($string);
// Fix for some git versions
$value = trim($value, "'");
return str_replace(self::$delimiter . "'\n'", self::$delimiter . "\n", $value);
}
/**
* Is inside work tree.
*/
public static function isInsideWorkTree(): bool
{
$result = self::run('git rev-parse --is-inside-work-tree');
return $result === 'true';
}
/**
* Get first commit hash.
*/
public static function getFirstCommit(): string
{
return self::run('git rev-list --max-parents=0 HEAD');
}
/**
* Get last commit hash.
*/
public static function getLastCommit(): string
{
return self::run('git log -1 --pretty=format:%H');
}
/**
* Get last tag.
*/
public static function getLastTagRefname($prefix = '', $merged = false): string
{
$merged = $merged ? '--merged' : '';
return self::run('git for-each-ref ' /* 'refs/tags/" . $prefix . "*' */ . " --sort=-v:refname --format='%(refname:strip=2)' --count=1 {$merged}");
}
/**
* Get last tag.
*/
public static function getLastTag(string $prefix = '', bool $merged = false, string $extra = ''): string
{
$mergedArg = ($merged) ? '--merged' : '';
$tags = self::run("git for-each-ref --sort=-v:refname --format='%(refname:strip=2)" . self::$delimiter . "' {$mergedArg}");
$tagsArray = explode(self::$delimiter . "\n", $tags);
$prefixQuote = preg_quote($prefix);
$tagsFound = preg_grep('/^' . $prefixQuote . '[^-]*$/', $tagsArray);
$lastBaseTag = '0.0.0';
if (count($tagsFound) > 0) {
foreach ($tagsFound as $found) {
if (SemanticVersion::validate($found, $prefix)) {
$lastBaseTag = $found;
break;
}
}
}
if (!empty($extra)) {
$extraQuote = preg_quote("-{$extra}");
$tagsFound = preg_grep('/^' . $prefixQuote . '[^-]*' . $extraQuote . '/', $tagsArray);
}
$lastTag = '0.0.0';
if (count($tagsFound) > 0) {
foreach ($tagsFound as $found) {
if (SemanticVersion::validate($found, $prefix)) {
$lastTag = $found;
break;
}
}
}
if (SemanticVersion::compareBase($lastBaseTag, $lastTag) > 0) {
return $lastBaseTag;
}
return $lastTag;
}
/**
* Get last alpha tag based on a tag pattern.
*/
public static function getLastAlphaTag(string $prefix = '', bool $merged = false): ?string
{
return self::getLastTag($prefix, $merged, 'alpha');
}
/**
* Get last release candidate tag based on a tag pattern.
*/
public static function getLastReleaseCandidateTag(string $prefix = '', bool $merged = false): ?string
{
return self::getLastTag($prefix, $merged, 'rc');
}
/**
* Get last beta tag based on a tag pattern.
*/
public static function getLastBetaTag(string $prefix = '', bool $merged = false): ?string
{
return self::getLastTag($prefix, $merged, 'beta');
}
private static function getTag(string $tag, string $match): ?string
{
$find = self::run(sprintf('git describe --tags --match "*%s*-%s*" --abbrev=0', $tag, $match));
return !empty($find) ? $find : null;
}
/**
* Get last tag commit hash.
*/
public static function getLastTagCommit($prefix = ''): string
{
$lastTag = self::getLastTag($prefix);
return self::run("git rev-parse --verify {$lastTag}");
}
/**
* Get last tag commit hash.
*/
public static function getLastTagRefnameCommit($prefix = ''): string
{
$lastTag = self::getLastTagRefname($prefix);
return self::run("git rev-parse --verify {$lastTag}");
}
/**
* Get current branch name.
*/
public static function getCurrentBranch(): string
{
return self::run('git branch --show-current');
}
/**
* Get commit date.
*/
public static function getCommitDate($hash): \DateTime
{
$date = self::run("git log -1 --format=%aI {$hash}");
return new \DateTime($date);
}
/**
* Get remote url.
*/
public static function getRemoteUrl(): string
{
$url = self::run('git config --get remote.origin.url');
$url = preg_replace("/\.git$/", '', $url);
return preg_replace('/^(https?:\/\/)([0-9a-z.\-_:%]+@)/i', '$1', $url);
}
/**
* Has remote url.
*/
public static function hasRemoteUrl(): bool
{
$url = self::getRemoteUrl();
return !empty($url);
}
/**
* Get commits.
*/
public static function getCommits(string $options = ''): array
{
$commits = [];
$shortcodes = [
'raw' => '%B',
'hash' => '%H',
'authorName' => '%aN',
'authorEmail' => '%aE',
'authorDate' => '%aI',
'committerName' => '%cN',
'committerEmail' => '%cE',
'committerDate' => '%cI',
];
$format = '';
foreach ($shortcodes as $key => $value) {
$format .= "[{$key}]{$value}[/{$key}]";
}
$format .= self::$delimiter;
$commitsLogs = self::run("git log --pretty=format:'" . $format . "' {$options}") . "\n";
$commitsArray = explode(self::$delimiter . "\n", $commitsLogs);
array_pop($commitsArray);
$shortcodesKeys = array_keys($shortcodes);
foreach ($commitsArray as $commitRaw) {
$parse = self::parseShortcodes($commitRaw, $shortcodesKeys);
$commit = new Commit();
$commit->fromArray($parse);
$commits[] = $commit;
}
return $commits;
}
/**
* Get tags.
*/
public static function getTags($prefix = ''): array
{
$tags = self::run("git tag '" . $prefix . "*' --sort=-v:refname --list --format='%(refname:strip=2)" . self::$delimiter . "'") . "\n";
$tagsArray = explode(self::$delimiter . "\n", $tags);
array_pop($tagsArray);
return array_reverse($tagsArray);
}
/**
* Add all.
*
* @return string
*/
public static function addAll()
{
system('git add --all');
}
/**
* Add files.
*
* @return string
*/
public static function add($files)
{
if (!is_array($files)) {
$files = [$files];
}
foreach ($files as $file) {
system("git add \"{$file}\"");
}
}
/**
* Commit.
*
* @return string
*/
public static function commit(string $message, array $files = [], bool $amend = false, bool $verify = true, $noEdit = false)
{
self::add($files);
$message = str_replace('"', "'", $message); // Escape
$command = "git commit -m \"{$message}\"";
if ($amend) {
$command .= ' --amend';
}
if (!$verify) {
$command .= ' --no-verify';
}
if ($noEdit) {
$command .= ' --no-edit';
}
return exec($command);
}
/**
* Tag.
*
* @return string
*/
public static function tag(string $name, $annotation = false)
{
$message = $annotation ?: $name;
$flags = $annotation !== false ? "-a -m {$message}" : '';
return exec("git tag {$flags} {$name}");
}
/**
* Delete Tag.
*
* @return string
*/
public static function deleteTag(string $name)
{
return exec("git tag -d {$name}");
}
/**
* Parse shortcode.
*
* @return array
*/
protected static function parseShortcodes($content, $shortcodes)
{
$result = [];
foreach ($shortcodes as $key) {
$result[$key] = null;
$key = preg_quote($key, '/');
$pattern = "/\[[\s]*" . $key . "[\s]*\](.+?)\[[\s]*\/[\s]*" . $key . "[\s]*\]/si";
preg_match_all($pattern, $content, $match);
if (count($match) > 0 && !empty($match[0]) && isset($match[1][0])) {
$result[$key] = $match[1][0];
}
}
return $result;
}
/**
* Parse remote url.
*
* @return array
*/
public static function parseRemoteUrl()
{
$url = self::getRemoteUrl();
$patterns = [
'#^(?P<protocol>https?|git|ssh|rsync)\://(?:(?P<user>.+)@)*(?P<host>[a-z0-9_.-]*)[:/]*(?P<port>[\d]+){0,1}(?P<pathname>\/((?P<owner>.+)\/)?((?P<repository>.+?)(\.git|\/)?)?)$#smi',
'#(git\+)?((?P<protocol>\w+)://)((?P<user>\w+)@)?((?P<host>[\w\.\-]+))(:(?P<port>\d+))?(?P<pathname>(\/(?P<owner>.+)/)?(\/?(?P<repository>.+)(\.git|\/)?)?)$#smi',
'#^(?:(?P<user>.+)@)*(?P<host>[a-z0-9_.-]*)[:]*(?P<port>[\d]+){0,1}(?P<pathname>\/?(?P<owner>.+)/(?P<repository>.+).git)$#smi',
'#((?P<user>\w+)@)?((?P<host>[\w\.\-]+))[\:\/]{1,2}(?P<pathname>((?P<owner>.+)/)?((?P<repository>.+)(\.git|\/)?)?)$#smi',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $url, $match)) {
return array_filter($match, 'is_string', ARRAY_FILTER_USE_KEY);
}
}
return [];
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace ConventionalChangelog\Helper;
class Formatter
{
/**
* Clean string removing double spaces and trimming.
*/
public static function clean(string $string): string
{
$string = trim($string);
return preg_replace('/[[:blank:]]+/m', ' ', $string);
}
}

View file

@ -0,0 +1,157 @@
<?php
namespace ConventionalChangelog\Helper;
class SemanticVersion
{
/**
* Pattern to detect semver.
*
* @var string
*/
public const PATTERN = '([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?';
/**
* Pattern no extra.
*
* @var string
*/
public const PATTERN_NO_EXTRA = '([0-9]+)\.([0-9]+)\.([0-9]+)';
/**
* @var string
*/
public const MAJOR = 'major';
/**
* @var string
*/
public const MINOR = 'minor';
/**
* @var string
*/
public const PATCH = 'patch';
/**
* @var string
*/
public const RC = 'rc';
/**
* @var string
*/
public const BETA = 'beta';
/**
* @var string
*/
public const ALPHA = 'alpha';
/**
* @var string
*/
protected $version;
/**
* Constructor.
*/
public function __construct($version, $prefix = 'v')
{
$version = trim($version);
$version = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $version);
$this->setVersion($version);
}
/**
* Bump version.
*/
public function bump(string $release, string $extraRelease = ''): string
{
$version = $this->getVersion();
$newVersion = [0, 0, 0];
if (!self::validate($version)) {
return $version;
}
// Generate new version code
$split = explode('-', $version, 2);
$extra = empty($split[1]) ? '' : $split[1];
$skipBumpRelease = false;
$extraReleases = [self::RC, self::BETA, self::ALPHA];
if (in_array($extraRelease, $extraReleases)) {
$partsExtra = explode('.', $extra, 2);
$extraVersion = 0;
if (count($partsExtra) > 1) {
$extraVersion = $partsExtra[1];
}
if (empty($extraVersion) || !is_numeric($extraVersion)) {
$extraVersion = 0;
} else {
$skipBumpRelease = true;
}
$extraVersion++;
$extra = "{$extraRelease}.{$extraVersion}";
}
$parts = explode('.', $split[0]);
foreach ($parts as $key => $value) {
$newVersion[$key] = (int)$value;
}
if (!$skipBumpRelease) {
if ($release === self::MAJOR) {
$newVersion[0]++;
$newVersion[1] = 0;
$newVersion[2] = 0;
} elseif ($release === self::MINOR) {
$newVersion[1]++;
$newVersion[2] = 0;
} elseif ($release === self::PATCH) {
$newVersion[2]++;
}
}
// Recompose semver
$version = implode('.', $newVersion) . (empty($extra) ? '' : '-' . $extra);
$this->setVersion($version);
return $version;
}
public function getVersion(): string
{
return $this->version;
}
public function getVersionCode(): string
{
if (preg_match('/' . self::PATTERN_NO_EXTRA . '/', $this->version, $match)) {
return $match[0];
}
return '0.0.0';
}
public function setVersion(string $version): SemanticVersion
{
$this->version = $version;
return $this;
}
public static function validate($version, $prefix = 'v'): bool
{
$version = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $version);
return preg_match('/^' . self::PATTERN . '$/', $version);
}
public static function compareBase($v1, $v2)
{
$v1 = preg_replace('/^.*?(' . self::PATTERN_NO_EXTRA . ').*?$/', '$1', $v1);
$v2 = preg_replace('/^.*?(' . self::PATTERN_NO_EXTRA . ').*?$/', '$1', $v2);
return version_compare($v1, $v2);
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace ConventionalChangelog\Helper;
class ShellCommand
{
/**
* Run shell command on working dir.
*/
public static function exec(string $string): string
{
$value = shell_exec($string);
return Formatter::clean((string)$value);
}
/**
* Check if command exists.
*
* @return bool
*/
public static function exists(string $command)
{
$whereIsCommand = (PHP_OS === 'WINNT') ? 'where' : 'which';
$checkCommand = sprintf($whereIsCommand . ' %s', escapeshellarg($command));
$return = self::exec($checkCommand);
return !empty($return);
}
/**
* Check if can execute shell commands.
*/
public static function isEnabled(): bool
{
return function_exists('shell_exec') &&
is_callable('shell_exec') &&
stripos(ini_get('disable_functions'), 'shell_exec') === false;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace ConventionalChangelog\PackageBump;
use ConventionalChangelog\Helper\ShellCommand;
use ConventionalChangelog\Type\PackageBump;
class ComposerJson extends PackageBump
{
/**
* {@inheritdoc}
*/
protected $fileName = 'composer.json';
/**
* {@inheritdoc}
*/
protected $lockFiles = ['composer.lock'];
/**
* {@inheritdoc}
*/
protected $fileType = 'json';
/**
* {@inheritdoc}
*/
public function getVersion(): ?string
{
return $this->content->version;
}
/**
* {@inheritdoc}
*/
public function setVersion(string $version): self
{
$this->content->version = $version;
return $this;
}
/**
* {@inheritdoc}
*/
public function save(): self
{
parent::save();
/* Feature: https://github.com/marcocesarato/php-conventional-changelog/issues/11#issuecomment-819829828
/* Bug: https://github.com/marcocesarato/php-conventional-changelog/issues/13
if (ShellCommand::exists('composer')) {
exec('cd ' . escapeshellarg($this->getPath()) . ' && composer update');
}*/
return $this;
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace ConventionalChangelog\PackageBump;
use ConventionalChangelog\Type\PackageBump;
class PackageJson extends PackageBump
{
/**
* {@inheritdoc}
*/
protected $fileName = 'package.json';
/**
* {@inheritdoc}
*/
protected $lockFiles = ['package.lock', 'yarn.lock', 'pnpm-lock.yaml'];
/**
* {@inheritdoc}
*/
protected $fileType = 'json';
/**
* {@inheritdoc}
*/
public function getVersion(): ?string
{
return $this->content->version;
}
/**
* {@inheritdoc}
*/
public function setVersion(string $version): self
{
$this->content->version = $version;
return $this;
}
}

View file

@ -0,0 +1,195 @@
<?php
namespace ConventionalChangelog\Type;
abstract class PackageBump
{
/**
* Package filename.
*
* @var string
*/
protected $fileName;
/**
* Package lock files.
*
* @var array
*/
protected $lockFiles;
/**
* Package file type.
*
* @var string
*/
protected $fileType;
/**
* Base path.
*
* @var string
*/
protected $path;
/**
* Content.
*
* @var mixed
*/
protected $content;
/**
* Original content for backup usage.
*
* @var mixed
*/
protected $originContent;
/**
* Bump constructor.
*/
public function __construct(string $path)
{
$this->path = $path;
if ($this->exists()) {
$raw = file_get_contents($this->getFilePath());
switch ($this->fileType) {
case 'json':
$this->originContent = json_decode($raw);
if ($this->originContent === null && json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(json_last_error_msg(), json_last_error());
}
$this->content = clone $this->originContent;
break;
default:
$this->originContent = $raw;
$this->content = $raw;
}
}
}
/**
* Get version.
*/
public function getVersion(): ?string
{
return null;
}
/**
* Set version.
*
* @return $this
*/
public function setVersion(string $version)
{
return $this;
}
/**
* Get root path.
*/
public function getPath(): string
{
if (empty($this->path)) {
$this->path = getcwd();
}
return $this->path;
}
/**
* Get file path.
*/
public function getFilePath(): string
{
$path = $this->getPath() . DIRECTORY_SEPARATOR . $this->fileName;
return preg_replace('/' . preg_quote(DIRECTORY_SEPARATOR, '/') . '+/', DIRECTORY_SEPARATOR, $path);
}
/**
* Get filename.
*/
public function getFileName(): string
{
return $this->fileName;
}
/**
* Get all existing lock files.
*/
public function getExistingLockFiles(): array
{
$paths = [];
foreach ($this->lockFiles as $lockFile) {
$path = $this->getPath() . DIRECTORY_SEPARATOR . $lockFile;
$path = preg_replace('/' . preg_quote(DIRECTORY_SEPARATOR, '/') . '+/', DIRECTORY_SEPARATOR, $path);
if (is_file($path)) {
$paths[] = $path;
}
}
return $paths;
}
/**
* Get lock file name.
*/
public function getLockFiles(): array
{
return $this->lockFiles;
}
/**
* Package file exists.
*/
public function exists(): bool
{
return is_file($this->getFilePath());
}
/**
* Save content.
*
* @return $this
*/
public function save()
{
if ($this->exists()) {
switch ($this->fileType) {
case 'json':
$content = json_encode($this->content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if ($content === null && json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(json_last_error_msg(), json_last_error());
}
break;
default:
$content = $this->content;
}
file_put_contents($this->getFilePath(), $content);
}
return $this;
}
/**
* Set content.
*
* @param mixed $content
*
* @return $this
*/
protected function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content and back backup.
*
* @return mixed
*/
protected function getContent()
{
return $this->content;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace ConventionalChangelog\Type;
/**
* Convertible to string.
*
* @internal
*/
interface Stringable
{
public function __toString(): string;
}

View file

@ -0,0 +1,113 @@
<?php
namespace Tests;
use ConventionalChangelog\Changelog;
use ConventionalChangelog\Configuration;
use PHPUnit\Framework\TestCase;
class ChangelogTest extends TestCase
{
use \phpmock\phpunit\PHPMock;
private Configuration $configuration;
private Changelog $changelog;
protected function setUp(): void
{
$this->configuration = $this->createMock(Configuration::class);
$this->changelog = new Changelog($this->configuration);
}
protected function setUpMocks($callback = null): void
{
$exec = $this->getFunctionMock(__NAMESPACE__, 'shell_exec');
$exec->expects($this->any())->willReturnCallback(
function ($command) use ($callback) {
$output = null;
if (!empty($callback)) {
$output = $callback($command);
}
if ($output !== null) {
return $output;
}
// getCommitDate
$commitDate = 'git log -1 --format=%aI';
if (substr($commitDate, 0, strlen($command)) === $command) {
return '2000-01-01T00:00:00+00:00';
}
switch ($command) {
case 'git config --get remote.origin.url':
// getRemoteUrl
return 'https://fake.remote.com/user/repo.git';
case 'git rev-list --max-parents=0 HEAD':
// getFirstCommit
return '021a49f43ef65ac7a894450374f1772eef1fd8b0';
case 'git log -1 --pretty=format:%H':
// getLastCommit
return '84d151f93f36474055c9ff406710a47aa3d6e168';
case 'git branch --show-current':
// getCurrentBranch
return 'main';
default:
break;
}
}
);
}
/** @test */
public function testRemoveHeader(): void
{
$class = new \ReflectionClass($this->changelog);
$method = $class->getMethod('removeHeader');
$method->setAccessible(true);
$content = <<<EOF
<!--- BEGIN HEADER -->
# Changelog
All notable changes to this project will be documented in this file.
<!--- END HEADER -->
## 0.0.1 (1970-01-01)
### Features
* Here we go !
EOF;
$expectedContent = <<<EOF
## 0.0.1 (1970-01-01)
### Features
* Here we go !
EOF;
$actualContent = $method->invokeArgs(
$this->changelog,
[$content]
);
$this->assertEquals($expectedContent, $actualContent);
}
/** @test */
public function testExecMock()
{
$this->setUpMocks(function ($command) {
if ($command == 'bar') {
return 'foo';
}
});
$output = shell_exec('git config --get remote.origin.url');
$this->assertEquals('https://fake.remote.com/user/repo.git', $output);
$output = shell_exec('bar');
$this->assertEquals('foo', $output);
}
}

3
vendor/psr/container/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
composer.lock
composer.phar
/vendor/

21
vendor/psr/container/LICENSE vendored Normal file
View file

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

13
vendor/psr/container/README.md vendored Normal file
View file

@ -0,0 +1,13 @@
Container interface
==============
This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url].
Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container.
The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
[psr-url]: https://www.php-fig.org/psr/psr-11/
[package-url]: https://packagist.org/packages/psr/container
[implementation-url]: https://packagist.org/providers/psr/container-implementation

27
vendor/psr/container/composer.json vendored Normal file
View file

@ -0,0 +1,27 @@
{
"name": "psr/container",
"type": "library",
"description": "Common Container Interface (PHP FIG PSR-11)",
"keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"],
"homepage": "https://github.com/php-fig/container",
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": ">=7.4.0"
},
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
}
}

View file

@ -0,0 +1,12 @@
<?php
namespace Psr\Container;
use Throwable;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface extends Throwable
{
}

View file

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id): bool;
}

View file

@ -0,0 +1,10 @@
<?php
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}

1331
vendor/symfony/console/Application.php vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Attribute;
/**
* Service tag to autoconfigure commands.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class AsCommand
{
public function __construct(
public string $name,
public ?string $description = null,
array $aliases = [],
bool $hidden = false,
) {
if (!$hidden && !$aliases) {
return;
}
$name = explode('|', $name);
$name = array_merge($name, $aliases);
if ($hidden && '' !== $name[0]) {
array_unshift($name, '');
}
$this->name = implode('|', $name);
}
}

261
vendor/symfony/console/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,261 @@
CHANGELOG
=========
6.4
---
* Add `SignalMap` to map signal value to its name
* Multi-line text in vertical tables is aligned properly
* The application can also catch errors with `Application::setCatchErrors(true)`
* Add `RunCommandMessage` and `RunCommandMessageHandler`
* Dispatch `ConsoleTerminateEvent` after an exit on signal handling and add `ConsoleTerminateEvent::getInterruptingSignal()`
6.3
---
* Add support for choosing exit code while handling signal, or to not exit at all
* Add `ProgressBar::setPlaceholderFormatter` to set a placeholder attached to a instance, instead of being global.
* Add `ReStructuredTextDescriptor`
6.2
---
* Improve truecolor terminal detection in some cases
* Add support for 256 color terminals (conversion from Ansi24 to Ansi8 if terminal is capable of it)
* Deprecate calling `*Command::setApplication()`, `*FormatterStyle::setForeground/setBackground()`, `Helper::setHelpSet()`, `Input*::setDefault()`, `Question::setAutocompleterCallback/setValidator()`without any arguments
* Change the signature of `OutputFormatterStyleInterface::setForeground/setBackground()` to `setForeground/setBackground(?string)`
* Change the signature of `HelperInterface::setHelperSet()` to `setHelperSet(?HelperSet)`
6.1
---
* Add support to display table vertically when calling setVertical()
* Add method `__toString()` to `InputInterface`
* Added `OutputWrapper` to prevent truncated URL in `SymfonyStyle::createBlock`.
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
* Add suggested values for arguments and options in input definition, for input completion
* Add `$resumeAt` parameter to `ProgressBar#start()`, so that one can easily 'resume' progress on longer tasks, and still get accurate `getEstimate()` and `getRemaining()` results.
6.0
---
* `Command::setHidden()` has a default value (`true`) for `$hidden` parameter and is final
* Remove `Helper::strlen()`, use `Helper::width()` instead
* Remove `Helper::strlenWithoutDecoration()`, use `Helper::removeDecoration()` instead
* `AddConsoleCommandPass` can not be configured anymore
* Remove `HelperSet::setCommand()` and `getCommand()` without replacement
5.4
---
* Add `TesterTrait::assertCommandIsSuccessful()` to test command
* Deprecate `HelperSet::setCommand()` and `getCommand()` without replacement
5.3
---
* Add `GithubActionReporter` to render annotations in a Github Action
* Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options
* Add the `Command::$defaultDescription` static property and the `description` attribute
on the `console.command` tag to allow the `list` command to instantiate commands lazily
* Add option `--short` to the `list` command
* Add support for bright colors
* Add `#[AsCommand]` attribute for declaring commands on PHP 8
* Add `Helper::width()` and `Helper::length()`
* The `--ansi` and `--no-ansi` options now default to `null`.
5.2.0
-----
* Added `SingleCommandApplication::setAutoExit()` to allow testing via `CommandTester`
* added support for multiline responses to questions through `Question::setMultiline()`
and `Question::isMultiline()`
* Added `SignalRegistry` class to stack signals handlers
* Added support for signals:
* Added `Application::getSignalRegistry()` and `Application::setSignalsToDispatchEvent()` methods
* Added `SignalableCommandInterface` interface
* Added `TableCellStyle` class to customize table cell
* Removed `php ` prefix invocation from help messages.
5.1.0
-----
* `Command::setHidden()` is final since Symfony 5.1
* Add `SingleCommandApplication`
* Add `Cursor` class
5.0.0
-----
* removed support for finding hidden commands using an abbreviation, use the full name instead
* removed `TableStyle::setCrossingChar()` method in favor of `TableStyle::setDefaultCrossingChar()`
* removed `TableStyle::setHorizontalBorderChar()` method in favor of `TableStyle::setDefaultCrossingChars()`
* removed `TableStyle::getHorizontalBorderChar()` method in favor of `TableStyle::getBorderChars()`
* removed `TableStyle::setVerticalBorderChar()` method in favor of `TableStyle::setVerticalBorderChars()`
* removed `TableStyle::getVerticalBorderChar()` method in favor of `TableStyle::getBorderChars()`
* removed support for returning `null` from `Command::execute()`, return `0` instead
* `ProcessHelper::run()` accepts only `array|Symfony\Component\Process\Process` for its `command` argument
* `Application::setDispatcher` accepts only `Symfony\Contracts\EventDispatcher\EventDispatcherInterface`
for its `dispatcher` argument
* renamed `Application::renderException()` and `Application::doRenderException()`
to `renderThrowable()` and `doRenderThrowable()` respectively.
4.4.0
-----
* deprecated finding hidden commands using an abbreviation, use the full name instead
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
* added method `minSecondsBetweenRedraws()` and `maxSecondsBetweenRedraws()` on `ProgressBar`
* `Application` implements `ResetInterface`
* marked all dispatched event classes as `@final`
* added support for displaying table horizontally
* deprecated returning `null` from `Command::execute()`, return `0` instead
* Deprecated the `Application::renderException()` and `Application::doRenderException()` methods,
use `renderThrowable()` and `doRenderThrowable()` instead.
* added support for the `NO_COLOR` env var (https://no-color.org/)
4.3.0
-----
* added support for hyperlinks
* added `ProgressBar::iterate()` method that simplify updating the progress bar when iterating
* added `Question::setAutocompleterCallback()` to provide a callback function
that dynamically generates suggestions as the user types
4.2.0
-----
* allowed passing commands as `[$process, 'ENV_VAR' => 'value']` to
`ProcessHelper::run()` to pass environment variables
* deprecated passing a command as a string to `ProcessHelper::run()`,
pass it the command as an array of its arguments instead
* made the `ProcessHelper` class final
* added `WrappableOutputFormatterInterface::formatAndWrap()` (implemented in `OutputFormatter`)
* added `capture_stderr_separately` option to `CommandTester::execute()`
4.1.0
-----
* added option to run suggested command if command is not found and only 1 alternative is available
* added option to modify console output and print multiple modifiable sections
* added support for iterable messages in output `write` and `writeln` methods
4.0.0
-----
* `OutputFormatter` throws an exception when unknown options are used
* removed `QuestionHelper::setInputStream()/getInputStream()`
* removed `Application::getTerminalWidth()/getTerminalHeight()` and
`Application::setTerminalDimensions()/getTerminalDimensions()`
* removed `ConsoleExceptionEvent`
* removed `ConsoleEvents::EXCEPTION`
3.4.0
-----
* added `SHELL_VERBOSITY` env var to control verbosity
* added `CommandLoaderInterface`, `FactoryCommandLoader` and PSR-11
`ContainerCommandLoader` for commands lazy-loading
* added a case-insensitive command name matching fallback
* added static `Command::$defaultName/getDefaultName()`, allowing for
commands to be registered at compile time in the application command loader.
Setting the `$defaultName` property avoids the need for filling the `command`
attribute on the `console.command` tag when using `AddConsoleCommandPass`.
3.3.0
-----
* added `ExceptionListener`
* added `AddConsoleCommandPass` (originally in FrameworkBundle)
* [BC BREAK] `Input::getOption()` no longer returns the default value for options
with value optional explicitly passed empty
* added console.error event to catch exceptions thrown by other listeners
* deprecated console.exception event in favor of console.error
* added ability to handle `CommandNotFoundException` through the
`console.error` event
* deprecated default validation in `SymfonyQuestionHelper::ask`
3.2.0
------
* added `setInputs()` method to CommandTester for ease testing of commands expecting inputs
* added `setStream()` and `getStream()` methods to Input (implement StreamableInputInterface)
* added StreamableInputInterface
* added LockableTrait
3.1.0
-----
* added truncate method to FormatterHelper
* added setColumnWidth(s) method to Table
2.8.3
-----
* remove readline support from the question helper as it caused issues
2.8.0
-----
* use readline for user input in the question helper when available to allow
the use of arrow keys
2.6.0
-----
* added a Process helper
* added a DebugFormatter helper
2.5.0
-----
* deprecated the dialog helper (use the question helper instead)
* deprecated TableHelper in favor of Table
* deprecated ProgressHelper in favor of ProgressBar
* added ConsoleLogger
* added a question helper
* added a way to set the process name of a command
* added a way to set a default command instead of `ListCommand`
2.4.0
-----
* added a way to force terminal dimensions
* added a convenient method to detect verbosity level
* [BC BREAK] made descriptors use output instead of returning a string
2.3.0
-----
* added multiselect support to the select dialog helper
* added Table Helper for tabular data rendering
* added support for events in `Application`
* added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()`
* added a way to set the progress bar progress via the `setCurrent` method
* added support for multiple InputOption shortcuts, written as `'-a|-b|-c'`
* added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG
2.2.0
-----
* added support for colorization on Windows via ConEmu
* add a method to Dialog Helper to ask for a question and hide the response
* added support for interactive selections in console (DialogHelper::select())
* added support for autocompletion as you type in Dialog Helper
2.1.0
-----
* added ConsoleOutputInterface
* added the possibility to disable a command (Command::isEnabled())
* added suggestions when a command does not exist
* added a --raw option to the list command
* added support for STDERR in the console output class (errors are now sent
to STDERR)
* made the defaults (helper set, commands, input definition) in Application
more easily customizable
* added support for the shell even if readline is not available
* added support for process isolation in Symfony shell via
`--process-isolation` switch
* added support for `--`, which disables options parsing after that point
(tokens will be parsed as arguments)

View file

@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CI;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Utility class for Github actions.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class GithubActionReporter
{
private OutputInterface $output;
/**
* @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85
*/
private const ESCAPED_DATA = [
'%' => '%25',
"\r" => '%0D',
"\n" => '%0A',
];
/**
* @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94
*/
private const ESCAPED_PROPERTIES = [
'%' => '%25',
"\r" => '%0D',
"\n" => '%0A',
':' => '%3A',
',' => '%2C',
];
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
public static function isGithubActionEnvironment(): bool
{
return false !== getenv('GITHUB_ACTIONS');
}
/**
* Output an error using the Github annotations format.
*
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
*/
public function error(string $message, string $file = null, int $line = null, int $col = null): void
{
$this->log('error', $message, $file, $line, $col);
}
/**
* Output a warning using the Github annotations format.
*
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
*/
public function warning(string $message, string $file = null, int $line = null, int $col = null): void
{
$this->log('warning', $message, $file, $line, $col);
}
/**
* Output a debug log using the Github annotations format.
*
* @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
*/
public function debug(string $message, string $file = null, int $line = null, int $col = null): void
{
$this->log('debug', $message, $file, $line, $col);
}
private function log(string $type, string $message, string $file = null, int $line = null, int $col = null): void
{
// Some values must be encoded.
$message = strtr($message, self::ESCAPED_DATA);
if (!$file) {
// No file provided, output the message solely:
$this->output->writeln(sprintf('::%s::%s', $type, $message));
return;
}
$this->output->writeln(sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message));
}
}

133
vendor/symfony/console/Color.php vendored Normal file
View file

@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Color
{
private const COLORS = [
'black' => 0,
'red' => 1,
'green' => 2,
'yellow' => 3,
'blue' => 4,
'magenta' => 5,
'cyan' => 6,
'white' => 7,
'default' => 9,
];
private const BRIGHT_COLORS = [
'gray' => 0,
'bright-red' => 1,
'bright-green' => 2,
'bright-yellow' => 3,
'bright-blue' => 4,
'bright-magenta' => 5,
'bright-cyan' => 6,
'bright-white' => 7,
];
private const AVAILABLE_OPTIONS = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
'reverse' => ['set' => 7, 'unset' => 27],
'conceal' => ['set' => 8, 'unset' => 28],
];
private string $foreground;
private string $background;
private array $options = [];
public function __construct(string $foreground = '', string $background = '', array $options = [])
{
$this->foreground = $this->parseColor($foreground);
$this->background = $this->parseColor($background, true);
foreach ($options as $option) {
if (!isset(self::AVAILABLE_OPTIONS[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
}
$this->options[$option] = self::AVAILABLE_OPTIONS[$option];
}
}
public function apply(string $text): string
{
return $this->set().$text.$this->unset();
}
public function set(): string
{
$setCodes = [];
if ('' !== $this->foreground) {
$setCodes[] = $this->foreground;
}
if ('' !== $this->background) {
$setCodes[] = $this->background;
}
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
}
if (0 === \count($setCodes)) {
return '';
}
return sprintf("\033[%sm", implode(';', $setCodes));
}
public function unset(): string
{
$unsetCodes = [];
if ('' !== $this->foreground) {
$unsetCodes[] = 39;
}
if ('' !== $this->background) {
$unsetCodes[] = 49;
}
foreach ($this->options as $option) {
$unsetCodes[] = $option['unset'];
}
if (0 === \count($unsetCodes)) {
return '';
}
return sprintf("\033[%sm", implode(';', $unsetCodes));
}
private function parseColor(string $color, bool $background = false): string
{
if ('' === $color) {
return '';
}
if ('#' === $color[0]) {
return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color);
}
if (isset(self::COLORS[$color])) {
return ($background ? '4' : '3').self::COLORS[$color];
}
if (isset(self::BRIGHT_COLORS[$color])) {
return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];
}
throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
}
}

View file

@ -0,0 +1,725 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Base class for all commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Command
{
// see https://tldp.org/LDP/abs/html/exitcodes.html
public const SUCCESS = 0;
public const FAILURE = 1;
public const INVALID = 2;
/**
* @var string|null The default command name
*
* @deprecated since Symfony 6.1, use the AsCommand attribute instead
*/
protected static $defaultName;
/**
* @var string|null The default command description
*
* @deprecated since Symfony 6.1, use the AsCommand attribute instead
*/
protected static $defaultDescription;
private ?Application $application = null;
private ?string $name = null;
private ?string $processTitle = null;
private array $aliases = [];
private InputDefinition $definition;
private bool $hidden = false;
private string $help = '';
private string $description = '';
private ?InputDefinition $fullDefinition = null;
private bool $ignoreValidationErrors = false;
private ?\Closure $code = null;
private array $synopsis = [];
private array $usages = [];
private ?HelperSet $helperSet = null;
public static function getDefaultName(): ?string
{
$class = static::class;
if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
return $attribute[0]->newInstance()->name;
}
$r = new \ReflectionProperty($class, 'defaultName');
if ($class !== $r->class || null === static::$defaultName) {
return null;
}
trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);
return static::$defaultName;
}
public static function getDefaultDescription(): ?string
{
$class = static::class;
if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
return $attribute[0]->newInstance()->description;
}
$r = new \ReflectionProperty($class, 'defaultDescription');
if ($class !== $r->class || null === static::$defaultDescription) {
return null;
}
trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);
return static::$defaultDescription;
}
/**
* @param string|null $name The name of the command; passing null means it must be set in configure()
*
* @throws LogicException When the command name is empty
*/
public function __construct(string $name = null)
{
$this->definition = new InputDefinition();
if (null === $name && null !== $name = static::getDefaultName()) {
$aliases = explode('|', $name);
if ('' === $name = array_shift($aliases)) {
$this->setHidden(true);
$name = array_shift($aliases);
}
$this->setAliases($aliases);
}
if (null !== $name) {
$this->setName($name);
}
if ('' === $this->description) {
$this->setDescription(static::getDefaultDescription() ?? '');
}
$this->configure();
}
/**
* Ignores validation errors.
*
* This is mainly useful for the help command.
*
* @return void
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
/**
* @return void
*/
public function setApplication(Application $application = null)
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
$this->application = $application;
if ($application) {
$this->setHelperSet($application->getHelperSet());
} else {
$this->helperSet = null;
}
$this->fullDefinition = null;
}
/**
* @return void
*/
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set.
*/
public function getHelperSet(): ?HelperSet
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*/
public function getApplication(): ?Application
{
return $this->application;
}
/**
* Checks whether the command is enabled or not in the current environment.
*
* Override this to check for x or y and return false if the command cannot
* run properly under the current conditions.
*
* @return bool
*/
public function isEnabled()
{
return true;
}
/**
* Configures the current command.
*
* @return void
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int 0 if everything went fine, or an exit code
*
* @throws LogicException When this abstract method is not implemented
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*
* @return void
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command after the input has been bound and before the input
* is validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*
* @return void
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* The code to execute is either defined directly with the
* setCode() method or by overriding the execute() method
* in a sub-class.
*
* @return int The command exit code
*
* @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
*
* @see setCode()
* @see execute()
*/
public function run(InputInterface $input, OutputInterface $output): int
{
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try {
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if (null !== $this->processTitle) {
if (\function_exists('cli_set_process_title')) {
if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === \PHP_OS) {
$output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
} else {
cli_set_process_title($this->processTitle);
}
}
} elseif (\function_exists('setproctitle')) {
setproctitle($this->processTitle);
} elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
}
}
if ($input->isInteractive()) {
$this->interact($input, $output);
}
// The command name argument is often omitted when a command is executed directly with its run() method.
// It would fail the validation if we didn't make sure the command argument is present,
// since it's required by the application.
if ($input->hasArgument('command') && null === $input->getArgument('command')) {
$input->setArgument('command', $this->getName());
}
$input->validate();
if ($this->code) {
$statusCode = ($this->code)($input, $output);
} else {
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
*/
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$definition = $this->getDefinition();
if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
$definition->getOption($input->getCompletionName())->complete($input, $suggestions);
} elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
$definition->getArgument($input->getCompletionName())->complete($input, $suggestions);
}
}
/**
* Sets the code to execute when running this command.
*
* If this method is used, it overrides the code defined
* in the execute() method.
*
* @param callable $code A callable(InputInterface $input, OutputInterface $output)
*
* @return $this
*
* @throws InvalidArgumentException
*
* @see execute()
*/
public function setCode(callable $code): static
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
set_error_handler(static function () {});
try {
if ($c = \Closure::bind($code, $this)) {
$code = $c;
}
} finally {
restore_error_handler();
}
}
} else {
$code = $code(...);
}
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*
* This method is not part of public API and should not be used directly.
*
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
*
* @internal
*/
public function mergeApplicationDefinition(bool $mergeArgs = true): void
{
if (null === $this->application) {
return;
}
$this->fullDefinition = new InputDefinition();
$this->fullDefinition->setOptions($this->definition->getOptions());
$this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());
if ($mergeArgs) {
$this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
$this->fullDefinition->addArguments($this->definition->getArguments());
} else {
$this->fullDefinition->setArguments($this->definition->getArguments());
}
}
/**
* Sets an array of argument and option instances.
*
* @return $this
*/
public function setDefinition(array|InputDefinition $definition): static
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->fullDefinition = null;
return $this;
}
/**
* Gets the InputDefinition attached to this Command.
*/
public function getDefinition(): InputDefinition
{
return $this->fullDefinition ?? $this->getNativeDefinition();
}
/**
* Gets the InputDefinition to be used to create representations of this Command.
*
* Can be overridden to provide the original command representation when it would otherwise
* be changed by merging with the application InputDefinition.
*
* This method is not part of public API and should not be used directly.
*/
public function getNativeDefinition(): InputDefinition
{
return $this->definition ?? throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
}
/**
* Adds an argument.
*
* @param $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param $default The default value (for InputArgument::OPTIONAL mode only)
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*
* @return $this
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
{
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
}
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
$this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
return $this;
}
/**
* Adds an option.
*
* @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param $mode The option mode: One of the InputOption::VALUE_* constants
* @param $default The default value (must be null for InputOption::VALUE_NONE)
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*
* @return $this
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
}
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
$this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @return $this
*
* @throws InvalidArgumentException When the name is invalid
*/
public function setName(string $name): static
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* Sets the process title of the command.
*
* This feature should be used only when creating a long process command,
* like a daemon.
*
* @return $this
*/
public function setProcessTitle(string $title): static
{
$this->processTitle = $title;
return $this;
}
/**
* Returns the command name.
*/
public function getName(): ?string
{
return $this->name;
}
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
*
* @return $this
*/
public function setHidden(bool $hidden = true): static
{
$this->hidden = $hidden;
return $this;
}
/**
* @return bool whether the command should be publicly shown or not
*/
public function isHidden(): bool
{
return $this->hidden;
}
/**
* Sets the description for the command.
*
* @return $this
*/
public function setDescription(string $description): static
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*/
public function getDescription(): string
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @return $this
*/
public function setHelp(string $help): static
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*/
public function getHelp(): string
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*/
public function getProcessedHelp(): string
{
$name = $this->name;
$isSingleCommand = $this->application?->isSingleCommand();
$placeholders = [
'%command.name%',
'%command.full_name%',
];
$replacements = [
$name,
$isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
];
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
}
/**
* Sets the aliases for the command.
*
* @param string[] $aliases An array of aliases for the command
*
* @return $this
*
* @throws InvalidArgumentException When an alias is invalid
*/
public function setAliases(iterable $aliases): static
{
$list = [];
foreach ($aliases as $alias) {
$this->validateName($alias);
$list[] = $alias;
}
$this->aliases = \is_array($aliases) ? $aliases : $list;
return $this;
}
/**
* Returns the aliases for the command.
*/
public function getAliases(): array
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @param bool $short Whether to show the short version of the synopsis (with options folded) or not
*/
public function getSynopsis(bool $short = false): string
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* Add a command usage example, it'll be prefixed with the command name.
*
* @return $this
*/
public function addUsage(string $usage): static
{
if (!str_starts_with($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* Returns alternative usages of the command.
*/
public function getUsages(): array
{
return $this->usages;
}
/**
* Gets a helper instance by name.
*
* @return HelperInterface
*
* @throws LogicException if no HelperSet is defined
* @throws InvalidArgumentException if the helper is not defined
*/
public function getHelper(string $name): mixed
{
if (null === $this->helperSet) {
throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
}
return $this->helperSet->get($name);
}
/**
* Validates a command name.
*
* It must be non-empty and parts can optionally be separated by ":".
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName(string $name): void
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
}

View file

@ -0,0 +1,223 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
use Symfony\Component\Console\Completion\Output\FishCompletionOutput;
use Symfony\Component\Console\Completion\Output\ZshCompletionOutput;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Responsible for providing the values to the shell completion.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')]
final class CompleteCommand extends Command
{
public const COMPLETION_API_VERSION = '1';
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultName = '|_complete';
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultDescription = 'Internal command to provide shell completion suggestions';
private array $completionOutputs;
private bool $isDebug = false;
/**
* @param array<string, class-string<CompletionOutputInterface>> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value
*/
public function __construct(array $completionOutputs = [])
{
// must be set before the parent constructor, as the property value is used in configure()
$this->completionOutputs = $completionOutputs + [
'bash' => BashCompletionOutput::class,
'fish' => FishCompletionOutput::class,
'zsh' => ZshCompletionOutput::class,
];
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script')
->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated')
;
}
protected function initialize(InputInterface $input, OutputInterface $output): void
{
$this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
// "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1
$version = $input->getOption('symfony') ? '1' : $input->getOption('api-version');
if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) {
$message = sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION);
$this->log($message);
$output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');
return 126;
}
$shell = $input->getOption('shell');
if (!$shell) {
throw new \RuntimeException('The "--shell" option must be set.');
}
if (!$completionOutput = $this->completionOutputs[$shell] ?? false) {
throw new \RuntimeException(sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs))));
}
$completionInput = $this->createCompletionInput($input);
$suggestions = new CompletionSuggestions();
$this->log([
'',
'<comment>'.date('Y-m-d H:i:s').'</>',
'<info>Input:</> <comment>("|" indicates the cursor position)</>',
' '.(string) $completionInput,
'<info>Command:</>',
' '.(string) implode(' ', $_SERVER['argv']),
'<info>Messages:</>',
]);
$command = $this->findCommand($completionInput, $output);
if (null === $command) {
$this->log(' No command found, completing using the Application class.');
$this->getApplication()->complete($completionInput, $suggestions);
} elseif (
$completionInput->mustSuggestArgumentValuesFor('command')
&& $command->getName() !== $completionInput->getCompletionValue()
&& !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true)
) {
$this->log(' No command found, completing using the Application class.');
// expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear")
$suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases())));
} else {
$command->mergeApplicationDefinition();
$completionInput->bind($command->getDefinition());
if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
$this->log(' Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');
$suggestions->suggestOptions($command->getDefinition()->getOptions());
} else {
$this->log([
' Completing using the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> class.',
' Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
]);
if (null !== $compval = $completionInput->getCompletionValue()) {
$this->log(' Current value: <comment>'.$compval.'</>');
}
$command->complete($completionInput, $suggestions);
}
}
/** @var CompletionOutputInterface $completionOutput */
$completionOutput = new $completionOutput();
$this->log('<info>Suggestions:</>');
if ($options = $suggestions->getOptionSuggestions()) {
$this->log(' --'.implode(' --', array_map(fn ($o) => $o->getName(), $options)));
} elseif ($values = $suggestions->getValueSuggestions()) {
$this->log(' '.implode(' ', $values));
} else {
$this->log(' <comment>No suggestions were provided</>');
}
$completionOutput->write($suggestions, $output);
} catch (\Throwable $e) {
$this->log([
'<error>Error!</error>',
(string) $e,
]);
if ($output->isDebug()) {
throw $e;
}
return 2;
}
return 0;
}
private function createCompletionInput(InputInterface $input): CompletionInput
{
$currentIndex = $input->getOption('current');
if (!$currentIndex || !ctype_digit($currentIndex)) {
throw new \RuntimeException('The "--current" option must be set and it must be an integer.');
}
$completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex);
try {
$completionInput->bind($this->getApplication()->getDefinition());
} catch (ExceptionInterface) {
}
return $completionInput;
}
private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command
{
try {
$inputName = $completionInput->getFirstArgument();
if (null === $inputName) {
return null;
}
return $this->getApplication()->find($inputName);
} catch (CommandNotFoundException) {
}
return null;
}
private function log($messages): void
{
if (!$this->isDebug) {
return;
}
$commandName = basename($_SERVER['argv'][0]);
file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND);
}
}

View file

@ -0,0 +1,161 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
/**
* Dumps the completion script for the current shell.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
#[AsCommand(name: 'completion', description: 'Dump the shell completion script')]
final class DumpCompletionCommand extends Command
{
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultName = 'completion';
/**
* @deprecated since Symfony 6.1
*/
protected static $defaultDescription = 'Dump the shell completion script';
private array $supportedShells;
protected function configure(): void
{
$fullCommand = $_SERVER['PHP_SELF'];
$commandName = basename($fullCommand);
$fullCommand = @realpath($fullCommand) ?: $fullCommand;
$shell = $this->guessShell();
[$rcFile, $completionFile] = match ($shell) {
'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"],
'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName],
default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"],
};
$supportedShells = implode(', ', $this->getSupportedShells());
$this
->setHelp(<<<EOH
The <info>%command.name%</> command dumps the shell completion script required
to use shell autocompletion (currently, {$supportedShells} completion are supported).
<comment>Static installation
-------------------</>
Dump the script to a global completion file and restart your shell:
<info>%command.full_name% {$shell} | sudo tee {$completionFile}</>
Or dump the script to a local file and source it:
<info>%command.full_name% {$shell} > completion.sh</>
<comment># source the file whenever you use the project</>
<info>source completion.sh</>
<comment># or add this line at the end of your "{$rcFile}" file:</>
<info>source /path/to/completion.sh</>
<comment>Dynamic installation
--------------------</>
Add this to the end of your shell configuration file (e.g. <info>"{$rcFile}"</>):
<info>eval "$({$fullCommand} completion {$shell})"</>
EOH
)
->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...))
->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$commandName = basename($_SERVER['argv'][0]);
if ($input->getOption('debug')) {
$this->tailDebugLog($commandName, $output);
return 0;
}
$shell = $input->getArgument('shell') ?? self::guessShell();
$completionFile = __DIR__.'/../Resources/completion.'.$shell;
if (!file_exists($completionFile)) {
$supportedShells = $this->getSupportedShells();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if ($shell) {
$output->writeln(sprintf('<error>Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").</>', $shell, implode('", "', $supportedShells)));
} else {
$output->writeln(sprintf('<error>Shell not detected, Symfony shell completion only supports "%s").</>', implode('", "', $supportedShells)));
}
return 2;
}
$output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile)));
return 0;
}
private static function guessShell(): string
{
return basename($_SERVER['SHELL'] ?? '');
}
private function tailDebugLog(string $commandName, OutputInterface $output): void
{
$debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log';
if (!file_exists($debugFile)) {
touch($debugFile);
}
$process = new Process(['tail', '-f', $debugFile], null, null, null, 0);
$process->run(function (string $type, string $line) use ($output): void {
$output->write($line);
});
}
/**
* @return string[]
*/
private function getSupportedShells(): array
{
if (isset($this->supportedShells)) {
return $this->supportedShells;
}
$shells = [];
foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) {
if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) {
$shells[] = $file->getExtension();
}
}
sort($shells);
return $this->supportedShells = $shells;
}
}

View file

@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelpCommand extends Command
{
private Command $command;
/**
* @return void
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Display help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:
<info>%command.full_name% list</info>
You can also output the help in other formats by using the <comment>--format</comment> option:
<info>%command.full_name% --format=xml list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
)
;
}
/**
* @return void
*/
public function setCommand(Command $command)
{
$this->command = $command;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->command ??= $this->getApplication()->find($input->getArgument('command_name'));
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
]);
unset($this->command);
return 0;
}
}

View file

@ -0,0 +1,207 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class LazyCommand extends Command
{
private \Closure|Command $command;
private ?bool $isEnabled;
public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
{
$this->setName($name)
->setAliases($aliases)
->setHidden($isHidden)
->setDescription($description);
$this->command = $commandFactory;
$this->isEnabled = $isEnabled;
}
public function ignoreValidationErrors(): void
{
$this->getCommand()->ignoreValidationErrors();
}
public function setApplication(Application $application = null): void
{
if (1 > \func_num_args()) {
trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
}
if ($this->command instanceof parent) {
$this->command->setApplication($application);
}
parent::setApplication($application);
}
public function setHelperSet(HelperSet $helperSet): void
{
if ($this->command instanceof parent) {
$this->command->setHelperSet($helperSet);
}
parent::setHelperSet($helperSet);
}
public function isEnabled(): bool
{
return $this->isEnabled ?? $this->getCommand()->isEnabled();
}
public function run(InputInterface $input, OutputInterface $output): int
{
return $this->getCommand()->run($input, $output);
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$this->getCommand()->complete($input, $suggestions);
}
public function setCode(callable $code): static
{
$this->getCommand()->setCode($code);
return $this;
}
/**
* @internal
*/
public function mergeApplicationDefinition(bool $mergeArgs = true): void
{
$this->getCommand()->mergeApplicationDefinition($mergeArgs);
}
public function setDefinition(array|InputDefinition $definition): static
{
$this->getCommand()->setDefinition($definition);
return $this;
}
public function getDefinition(): InputDefinition
{
return $this->getCommand()->getDefinition();
}
public function getNativeDefinition(): InputDefinition
{
return $this->getCommand()->getNativeDefinition();
}
/**
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
$this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);
return $this;
}
/**
* @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
*/
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
{
$suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
$this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
return $this;
}
public function setProcessTitle(string $title): static
{
$this->getCommand()->setProcessTitle($title);
return $this;
}
public function setHelp(string $help): static
{
$this->getCommand()->setHelp($help);
return $this;
}
public function getHelp(): string
{
return $this->getCommand()->getHelp();
}
public function getProcessedHelp(): string
{
return $this->getCommand()->getProcessedHelp();
}
public function getSynopsis(bool $short = false): string
{
return $this->getCommand()->getSynopsis($short);
}
public function addUsage(string $usage): static
{
$this->getCommand()->addUsage($usage);
return $this;
}
public function getUsages(): array
{
return $this->getCommand()->getUsages();
}
public function getHelper(string $name): HelperInterface
{
return $this->getCommand()->getHelper($name);
}
public function getCommand(): parent
{
if (!$this->command instanceof \Closure) {
return $this->command;
}
$command = $this->command = ($this->command)();
$command->setApplication($this->getApplication());
if (null !== $this->getHelperSet()) {
$command->setHelperSet($this->getHelperSet());
}
$command->setName($this->getName())
->setAliases($this->getAliases())
->setHidden($this->isHidden())
->setDescription($this->getDescription());
// Will throw if the command is not correctly initialized.
$command->getDefinition();
return $command;
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* ListCommand displays the list of all available commands for the application.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ListCommand extends Command
{
/**
* @return void
*/
protected function configure()
{
$this
->setName('list')
->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
])
->setDescription('List commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:
<info>%command.full_name%</info>
You can also display the commands for a specific namespace:
<info>%command.full_name% test</info>
You can also output the information in other formats by using the <comment>--format</comment> option:
<info>%command.full_name% --format=xml</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>%command.full_name% --raw</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
'short' => $input->getOption('short'),
]);
return 0;
}
}

View file

@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
/**
* Basic lock feature for commands.
*
* @author Geoffrey Brier <geoffrey.brier@gmail.com>
*/
trait LockableTrait
{
private ?LockInterface $lock = null;
/**
* Locks a command.
*/
private function lock(string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');
}
if (null !== $this->lock) {
throw new LogicException('A lock is already in place.');
}
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
$this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
if (!$this->lock->acquire($blocking)) {
$this->lock = null;
return false;
}
return true;
}
/**
* Releases the command lock if there is one.
*/
private function release(): void
{
if ($this->lock) {
$this->lock->release();
$this->lock = null;
}
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
/**
* Interface for command reacting to signal.
*
* @author Grégoire Pineau <lyrixx@lyrix.info>
*/
interface SignalableCommandInterface
{
/**
* Returns the list of signals to subscribe.
*/
public function getSubscribedSignals(): array;
/**
* The method will be called when the application is signaled.
*
* @param int|false $previousExitCode
*
* @return int|false The exit code to return or false to continue the normal execution
*/
public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */);
}

View file

@ -0,0 +1,356 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @internal
*
* @author Jules Pietri <jules@heahprod.com>
*/
final class TraceableCommand extends Command implements SignalableCommandInterface
{
public readonly Command $command;
public int $exitCode;
public ?int $interruptedBySignal = null;
public bool $ignoreValidation;
public bool $isInteractive = false;
public string $duration = 'n/a';
public string $maxMemoryUsage = 'n/a';
public InputInterface $input;
public OutputInterface $output;
/** @var array<string, mixed> */
public array $arguments;
/** @var array<string, mixed> */
public array $options;
/** @var array<string, mixed> */
public array $interactiveInputs = [];
public array $handledSignals = [];
public function __construct(
Command $command,
private readonly Stopwatch $stopwatch,
) {
if ($command instanceof LazyCommand) {
$command = $command->getCommand();
}
$this->command = $command;
// prevent call to self::getDefaultDescription()
$this->setDescription($command->getDescription());
parent::__construct($command->getName());
// init below enables calling {@see parent::run()}
[$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(function () {
return [$this->code, $this->processTitle, $this->ignoreValidationErrors];
}, $command, Command::class)();
if (\is_callable($code)) {
$this->setCode($code);
}
if ($processTitle) {
parent::setProcessTitle($processTitle);
}
if ($ignoreValidationErrors) {
parent::ignoreValidationErrors();
}
$this->ignoreValidation = $ignoreValidationErrors;
}
public function __call(string $name, array $arguments): mixed
{
return $this->command->{$name}(...$arguments);
}
public function getSubscribedSignals(): array
{
return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : [];
}
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
{
if (!$this->command instanceof SignalableCommandInterface) {
return false;
}
$event = $this->stopwatch->start($this->getName().'.handle_signal');
$exit = $this->command->handleSignal($signal, $previousExitCode);
$event->stop();
if (!isset($this->handledSignals[$signal])) {
$this->handledSignals[$signal] = [
'handled' => 0,
'duration' => 0,
'memory' => 0,
];
}
++$this->handledSignals[$signal]['handled'];
$this->handledSignals[$signal]['duration'] += $event->getDuration();
$this->handledSignals[$signal]['memory'] = max(
$this->handledSignals[$signal]['memory'],
$event->getMemory() >> 20
);
return $exit;
}
/**
* {@inheritdoc}
*
* Calling parent method is required to be used in {@see parent::run()}.
*/
public function ignoreValidationErrors(): void
{
$this->ignoreValidation = true;
$this->command->ignoreValidationErrors();
parent::ignoreValidationErrors();
}
public function setApplication(Application $application = null): void
{
$this->command->setApplication($application);
}
public function getApplication(): ?Application
{
return $this->command->getApplication();
}
public function setHelperSet(HelperSet $helperSet): void
{
$this->command->setHelperSet($helperSet);
}
public function getHelperSet(): ?HelperSet
{
return $this->command->getHelperSet();
}
public function isEnabled(): bool
{
return $this->command->isEnabled();
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
$this->command->complete($input, $suggestions);
}
/**
* {@inheritdoc}
*
* Calling parent method is required to be used in {@see parent::run()}.
*/
public function setCode(callable $code): static
{
$this->command->setCode($code);
return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int {
$event = $this->stopwatch->start($this->getName().'.code');
$this->exitCode = $code($input, $output);
$event->stop();
return $this->exitCode;
});
}
/**
* @internal
*/
public function mergeApplicationDefinition(bool $mergeArgs = true): void
{
$this->command->mergeApplicationDefinition($mergeArgs);
}
public function setDefinition(array|InputDefinition $definition): static
{
$this->command->setDefinition($definition);
return $this;
}
public function getDefinition(): InputDefinition
{
return $this->command->getDefinition();
}
public function getNativeDefinition(): InputDefinition
{
return $this->command->getNativeDefinition();
}
public function addArgument(string $name, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
{
$this->command->addArgument($name, $mode, $description, $default, $suggestedValues);
return $this;
}
public function addOption(string $name, string|array $shortcut = null, int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
{
$this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);
return $this;
}
/**
* {@inheritdoc}
*
* Calling parent method is required to be used in {@see parent::run()}.
*/
public function setProcessTitle(string $title): static
{
$this->command->setProcessTitle($title);
return parent::setProcessTitle($title);
}
public function setHelp(string $help): static
{
$this->command->setHelp($help);
return $this;
}
public function getHelp(): string
{
return $this->command->getHelp();
}
public function getProcessedHelp(): string
{
return $this->command->getProcessedHelp();
}
public function getSynopsis(bool $short = false): string
{
return $this->command->getSynopsis($short);
}
public function addUsage(string $usage): static
{
$this->command->addUsage($usage);
return $this;
}
public function getUsages(): array
{
return $this->command->getUsages();
}
public function getHelper(string $name): HelperInterface
{
return $this->command->getHelper($name);
}
public function run(InputInterface $input, OutputInterface $output): int
{
$this->input = $input;
$this->output = $output;
$this->arguments = $input->getArguments();
$this->options = $input->getOptions();
$event = $this->stopwatch->start($this->getName(), 'command');
try {
$this->exitCode = parent::run($input, $output);
} finally {
$event->stop();
if ($output instanceof ConsoleOutputInterface && $output->isDebug()) {
$output->getErrorOutput()->writeln((string) $event);
}
$this->duration = $event->getDuration().' ms';
$this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB';
if ($this->isInteractive) {
$this->extractInteractiveInputs($input->getArguments(), $input->getOptions());
}
}
return $this->exitCode;
}
protected function initialize(InputInterface $input, OutputInterface $output): void
{
$event = $this->stopwatch->start($this->getName().'.init', 'command');
$this->command->initialize($input, $output);
$event->stop();
}
protected function interact(InputInterface $input, OutputInterface $output): void
{
if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) {
return;
}
$event = $this->stopwatch->start($this->getName().'.interact', 'command');
$this->command->interact($input, $output);
$event->stop();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$event = $this->stopwatch->start($this->getName().'.execute', 'command');
$exitCode = $this->command->execute($input, $output);
$event->stop();
return $exitCode;
}
private function extractInteractiveInputs(array $arguments, array $options): void
{
foreach ($arguments as $argName => $argValue) {
if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) {
continue;
}
$this->interactiveInputs[$argName] = $argValue;
}
foreach ($options as $optName => $optValue) {
if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) {
continue;
}
$this->interactiveInputs['--'.$optName] = $optValue;
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface CommandLoaderInterface
{
/**
* Loads a command.
*
* @throws CommandNotFoundException
*/
public function get(string $name): Command;
/**
* Checks if a command exists.
*/
public function has(string $name): bool;
/**
* @return string[]
*/
public function getNames(): array;
}

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* Loads commands from a PSR-11 container.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ContainerCommandLoader implements CommandLoaderInterface
{
private ContainerInterface $container;
private array $commandMap;
/**
* @param array $commandMap An array with command names as keys and service ids as values
*/
public function __construct(ContainerInterface $container, array $commandMap)
{
$this->container = $container;
$this->commandMap = $commandMap;
}
public function get(string $name): Command
{
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return $this->container->get($this->commandMap[$name]);
}
public function has(string $name): bool
{
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
}
public function getNames(): array
{
return array_keys($this->commandMap);
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* A simple command loader using factories to instantiate commands lazily.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private array $factories;
/**
* @param callable[] $factories Indexed by command names
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
public function has(string $name): bool
{
return isset($this->factories[$name]);
}
public function get(string $name): Command
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
$factory = $this->factories[$name];
return $factory();
}
public function getNames(): array
{
return array_keys($this->factories);
}
}

View file

@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* An input specialized for shell completion.
*
* This input allows unfinished option names or values and exposes what kind of
* completion is expected.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class CompletionInput extends ArgvInput
{
public const TYPE_ARGUMENT_VALUE = 'argument_value';
public const TYPE_OPTION_VALUE = 'option_value';
public const TYPE_OPTION_NAME = 'option_name';
public const TYPE_NONE = 'none';
private array $tokens;
private int $currentIndex;
private string $completionType;
private ?string $completionName = null;
private string $completionValue = '';
/**
* Converts a terminal string into tokens.
*
* This is required for shell completions without COMP_WORDS support.
*/
public static function fromString(string $inputStr, int $currentIndex): self
{
preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens);
return self::fromTokens($tokens[0], $currentIndex);
}
/**
* Create an input based on an COMP_WORDS token list.
*
* @param string[] $tokens the set of split tokens (e.g. COMP_WORDS or argv)
* @param $currentIndex the index of the cursor (e.g. COMP_CWORD)
*/
public static function fromTokens(array $tokens, int $currentIndex): self
{
$input = new self($tokens);
$input->tokens = $tokens;
$input->currentIndex = $currentIndex;
return $input;
}
public function bind(InputDefinition $definition): void
{
parent::bind($definition);
$relevantToken = $this->getRelevantToken();
if ('-' === $relevantToken[0]) {
// the current token is an input option: complete either option name or option value
[$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];
$option = $this->getOptionFromToken($optionToken);
if (null === $option && !$this->isCursorFree()) {
$this->completionType = self::TYPE_OPTION_NAME;
$this->completionValue = $relevantToken;
return;
}
if ($option?->acceptValue()) {
$this->completionType = self::TYPE_OPTION_VALUE;
$this->completionName = $option->getName();
$this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');
return;
}
}
$previousToken = $this->tokens[$this->currentIndex - 1];
if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
// check if previous option accepted a value
$previousOption = $this->getOptionFromToken($previousToken);
if ($previousOption?->acceptValue()) {
$this->completionType = self::TYPE_OPTION_VALUE;
$this->completionName = $previousOption->getName();
$this->completionValue = $relevantToken;
return;
}
}
// complete argument value
$this->completionType = self::TYPE_ARGUMENT_VALUE;
foreach ($this->definition->getArguments() as $argumentName => $argument) {
if (!isset($this->arguments[$argumentName])) {
break;
}
$argumentValue = $this->arguments[$argumentName];
$this->completionName = $argumentName;
if (\is_array($argumentValue)) {
$this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
} else {
$this->completionValue = $argumentValue;
}
}
if ($this->currentIndex >= \count($this->tokens)) {
if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {
$this->completionName = $argumentName;
$this->completionValue = '';
} else {
// we've reached the end
$this->completionType = self::TYPE_NONE;
$this->completionName = null;
$this->completionValue = '';
}
}
}
/**
* Returns the type of completion required.
*
* TYPE_ARGUMENT_VALUE when completing the value of an input argument
* TYPE_OPTION_VALUE when completing the value of an input option
* TYPE_OPTION_NAME when completing the name of an input option
* TYPE_NONE when nothing should be completed
*
* TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component.
*
* @return self::TYPE_*
*/
public function getCompletionType(): string
{
return $this->completionType;
}
/**
* The name of the input option or argument when completing a value.
*
* @return string|null returns null when completing an option name
*/
public function getCompletionName(): ?string
{
return $this->completionName;
}
/**
* The value already typed by the user (or empty string).
*/
public function getCompletionValue(): string
{
return $this->completionValue;
}
public function mustSuggestOptionValuesFor(string $optionName): bool
{
return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
}
public function mustSuggestArgumentValuesFor(string $argumentName): bool
{
return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
}
protected function parseToken(string $token, bool $parseOptions): bool
{
try {
return parent::parseToken($token, $parseOptions);
} catch (RuntimeException) {
// suppress errors, completed input is almost never valid
}
return $parseOptions;
}
private function getOptionFromToken(string $optionToken): ?InputOption
{
$optionName = ltrim($optionToken, '-');
if (!$optionName) {
return null;
}
if ('-' === ($optionToken[1] ?? ' ')) {
// long option name
return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
}
// short option name
return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
}
/**
* The token of the cursor, or the last token if the cursor is at the end of the input.
*/
private function getRelevantToken(): string
{
return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
}
/**
* Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
*/
private function isCursorFree(): bool
{
$nrOfTokens = \count($this->tokens);
if ($this->currentIndex > $nrOfTokens) {
throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
}
return $this->currentIndex >= $nrOfTokens;
}
public function __toString()
{
$str = '';
foreach ($this->tokens as $i => $token) {
$str .= $token;
if ($this->currentIndex === $i) {
$str .= '|';
}
$str .= ' ';
}
if ($this->currentIndex > $i) {
$str .= '|';
}
return rtrim($str);
}
}

View file

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion;
use Symfony\Component\Console\Input\InputOption;
/**
* Stores all completion suggestions for the current input.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class CompletionSuggestions
{
private array $valueSuggestions = [];
private array $optionSuggestions = [];
/**
* Add a suggested value for an input option or argument.
*
* @return $this
*/
public function suggestValue(string|Suggestion $value): static
{
$this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;
return $this;
}
/**
* Add multiple suggested values at once for an input option or argument.
*
* @param list<string|Suggestion> $values
*
* @return $this
*/
public function suggestValues(array $values): static
{
foreach ($values as $value) {
$this->suggestValue($value);
}
return $this;
}
/**
* Add a suggestion for an input option name.
*
* @return $this
*/
public function suggestOption(InputOption $option): static
{
$this->optionSuggestions[] = $option;
return $this;
}
/**
* Add multiple suggestions for input option names at once.
*
* @param InputOption[] $options
*
* @return $this
*/
public function suggestOptions(array $options): static
{
foreach ($options as $option) {
$this->suggestOption($option);
}
return $this;
}
/**
* @return InputOption[]
*/
public function getOptionSuggestions(): array
{
return $this->optionSuggestions;
}
/**
* @return Suggestion[]
*/
public function getValueSuggestions(): array
{
return $this->valueSuggestions;
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion\Output;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class BashCompletionOutput implements CompletionOutputInterface
{
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
{
$values = $suggestions->getValueSuggestions();
foreach ($suggestions->getOptionSuggestions() as $option) {
$values[] = '--'.$option->getName();
if ($option->isNegatable()) {
$values[] = '--no-'.$option->getName();
}
}
$output->writeln(implode("\n", $values));
}
}

View file

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion\Output;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Transforms the {@see CompletionSuggestions} object into output readable by the shell completion.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
interface CompletionOutputInterface
{
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion\Output;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Guillaume Aveline <guillaume.aveline@pm.me>
*/
class FishCompletionOutput implements CompletionOutputInterface
{
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
{
$values = $suggestions->getValueSuggestions();
foreach ($suggestions->getOptionSuggestions() as $option) {
$values[] = '--'.$option->getName();
if ($option->isNegatable()) {
$values[] = '--no-'.$option->getName();
}
}
$output->write(implode("\n", $values));
}
}

View file

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion\Output;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jitendra A <adhocore@gmail.com>
*/
class ZshCompletionOutput implements CompletionOutputInterface
{
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
{
$values = [];
foreach ($suggestions->getValueSuggestions() as $value) {
$values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
}
foreach ($suggestions->getOptionSuggestions() as $option) {
$values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
if ($option->isNegatable()) {
$values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
}
}
$output->write(implode("\n", $values)."\n");
}
}

View file

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Completion;
/**
* Represents a single suggested value.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class Suggestion implements \Stringable
{
public function __construct(
private readonly string $value,
private readonly string $description = ''
) {
}
public function getValue(): string
{
return $this->value;
}
public function getDescription(): string
{
return $this->description;
}
public function __toString(): string
{
return $this->getValue();
}
}

View file

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
/**
* Contains all events dispatched by an Application.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleEvents
{
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handed to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*/
public const COMMAND = 'console.command';
/**
* The SIGNAL event allows you to perform some actions
* after the command execution was interrupted.
*
* @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
*/
public const SIGNAL = 'console.signal';
/**
* The TERMINATE event allows you to attach listeners after a command is
* executed by the console.
*
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
*/
public const TERMINATE = 'console.terminate';
/**
* The ERROR event occurs when an uncaught exception or error appears.
*
* This event allows you to deal with the exception/error or
* to modify the thrown exception.
*
* @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
*/
public const ERROR = 'console.error';
/**
* Event aliases.
*
* These aliases can be consumed by RegisterListenersPass.
*/
public const ALIASES = [
ConsoleCommandEvent::class => self::COMMAND,
ConsoleErrorEvent::class => self::ERROR,
ConsoleSignalEvent::class => self::SIGNAL,
ConsoleTerminateEvent::class => self::TERMINATE,
];
}

204
vendor/symfony/console/Cursor.php vendored Normal file
View file

@ -0,0 +1,204 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Pierre du Plessis <pdples@gmail.com>
*/
final class Cursor
{
private OutputInterface $output;
/** @var resource */
private $input;
/**
* @param resource|null $input
*/
public function __construct(OutputInterface $output, $input = null)
{
$this->output = $output;
$this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
}
/**
* @return $this
*/
public function moveUp(int $lines = 1): static
{
$this->output->write(sprintf("\x1b[%dA", $lines));
return $this;
}
/**
* @return $this
*/
public function moveDown(int $lines = 1): static
{
$this->output->write(sprintf("\x1b[%dB", $lines));
return $this;
}
/**
* @return $this
*/
public function moveRight(int $columns = 1): static
{
$this->output->write(sprintf("\x1b[%dC", $columns));
return $this;
}
/**
* @return $this
*/
public function moveLeft(int $columns = 1): static
{
$this->output->write(sprintf("\x1b[%dD", $columns));
return $this;
}
/**
* @return $this
*/
public function moveToColumn(int $column): static
{
$this->output->write(sprintf("\x1b[%dG", $column));
return $this;
}
/**
* @return $this
*/
public function moveToPosition(int $column, int $row): static
{
$this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));
return $this;
}
/**
* @return $this
*/
public function savePosition(): static
{
$this->output->write("\x1b7");
return $this;
}
/**
* @return $this
*/
public function restorePosition(): static
{
$this->output->write("\x1b8");
return $this;
}
/**
* @return $this
*/
public function hide(): static
{
$this->output->write("\x1b[?25l");
return $this;
}
/**
* @return $this
*/
public function show(): static
{
$this->output->write("\x1b[?25h\x1b[?0c");
return $this;
}
/**
* Clears all the output from the current line.
*
* @return $this
*/
public function clearLine(): static
{
$this->output->write("\x1b[2K");
return $this;
}
/**
* Clears all the output from the current line after the current position.
*/
public function clearLineAfter(): self
{
$this->output->write("\x1b[K");
return $this;
}
/**
* Clears all the output from the cursors' current position to the end of the screen.
*
* @return $this
*/
public function clearOutput(): static
{
$this->output->write("\x1b[0J");
return $this;
}
/**
* Clears the entire screen.
*
* @return $this
*/
public function clearScreen(): static
{
$this->output->write("\x1b[2J");
return $this;
}
/**
* Returns the current cursor position as x,y coordinates.
*/
public function getCurrentPosition(): array
{
static $isTtySupported;
if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) {
return [1, 1];
}
$sttyMode = shell_exec('stty -g');
shell_exec('stty -icanon -echo');
@fwrite($this->input, "\033[6n");
$code = trim(fread($this->input, 1024));
shell_exec(sprintf('stty %s', $sttyMode));
sscanf($code, "\033[%d;%dR", $row, $col);
return [$col, $row];
}
}

View file

@ -0,0 +1,234 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\DataCollector;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Debug\CliRequest;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\VarDumper\Cloner\Data;
/**
* @internal
*
* @author Jules Pietri <jules@heahprod.com>
*/
final class CommandDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Throwable $exception = null): void
{
if (!$request instanceof CliRequest) {
return;
}
$command = $request->command;
$application = $command->getApplication();
$this->data = [
'command' => $this->cloneVar($command->command),
'exit_code' => $command->exitCode,
'interrupted_by_signal' => $command->interruptedBySignal,
'duration' => $command->duration,
'max_memory_usage' => $command->maxMemoryUsage,
'verbosity_level' => match ($command->output->getVerbosity()) {
OutputInterface::VERBOSITY_QUIET => 'quiet',
OutputInterface::VERBOSITY_NORMAL => 'normal',
OutputInterface::VERBOSITY_VERBOSE => 'verbose',
OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose',
OutputInterface::VERBOSITY_DEBUG => 'debug',
},
'interactive' => $command->isInteractive,
'validate_input' => !$command->ignoreValidation,
'enabled' => $command->isEnabled(),
'visible' => !$command->isHidden(),
'input' => $this->cloneVar($command->input),
'output' => $this->cloneVar($command->output),
'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs),
'signalable' => $command->getSubscribedSignals(),
'handled_signals' => $command->handledSignals,
'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())),
];
$baseDefinition = $application->getDefinition();
foreach ($command->arguments as $argName => $argValue) {
if ($baseDefinition->hasArgument($argName)) {
$this->data['application_inputs'][$argName] = $this->cloneVar($argValue);
} else {
$this->data['arguments'][$argName] = $this->cloneVar($argValue);
}
}
foreach ($command->options as $optName => $optValue) {
if ($baseDefinition->hasOption($optName)) {
$this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue);
} else {
$this->data['options'][$optName] = $this->cloneVar($optValue);
}
}
}
public function getName(): string
{
return 'command';
}
/**
* @return array{
* class?: class-string,
* executor?: string,
* file: string,
* line: int,
* }
*/
public function getCommand(): array
{
$class = $this->data['command']->getType();
$r = new \ReflectionMethod($class, 'execute');
if (Command::class !== $r->getDeclaringClass()) {
return [
'executor' => $class.'::'.$r->name,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
];
}
$r = new \ReflectionClass($class);
return [
'class' => $class,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
];
}
public function getInterruptedBySignal(): ?string
{
if (isset($this->data['interrupted_by_signal'])) {
return sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']);
}
return null;
}
public function getDuration(): string
{
return $this->data['duration'];
}
public function getMaxMemoryUsage(): string
{
return $this->data['max_memory_usage'];
}
public function getVerbosityLevel(): string
{
return $this->data['verbosity_level'];
}
public function getInteractive(): bool
{
return $this->data['interactive'];
}
public function getValidateInput(): bool
{
return $this->data['validate_input'];
}
public function getEnabled(): bool
{
return $this->data['enabled'];
}
public function getVisible(): bool
{
return $this->data['visible'];
}
public function getInput(): Data
{
return $this->data['input'];
}
public function getOutput(): Data
{
return $this->data['output'];
}
/**
* @return Data[]
*/
public function getArguments(): array
{
return $this->data['arguments'] ?? [];
}
/**
* @return Data[]
*/
public function getOptions(): array
{
return $this->data['options'] ?? [];
}
/**
* @return Data[]
*/
public function getApplicationInputs(): array
{
return $this->data['application_inputs'] ?? [];
}
/**
* @return Data[]
*/
public function getInteractiveInputs(): array
{
return $this->data['interactive_inputs'] ?? [];
}
public function getSignalable(): array
{
return array_map(
static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
$this->data['signalable']
);
}
public function getHandledSignals(): array
{
$keys = array_map(
static fn (int $signal): string => sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
array_keys($this->data['handled_signals'])
);
return array_combine($keys, array_values($this->data['handled_signals']));
}
/**
* @return Data[]
*/
public function getHelperSet(): array
{
return $this->data['helper_set'] ?? [];
}
public function reset(): void
{
$this->data = [];
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Debug;
use Symfony\Component\Console\Command\TraceableCommand;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @internal
*/
final class CliRequest extends Request
{
public function __construct(
public readonly TraceableCommand $command,
) {
parent::__construct(
attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'],
server: $_SERVER,
);
}
// Methods below allow to populate a profile, thus enable search and filtering
public function getUri(): string
{
if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) {
$binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console';
} else {
$binary = $this->server->get('argv')[0];
}
return $binary.' '.$this->command->input;
}
public function getMethod(): string
{
return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH';
}
public function getResponse(): Response
{
return new class($this->command->exitCode) extends Response {
public function __construct(private readonly int $exitCode)
{
parent::__construct();
}
public function getStatusCode(): int
{
return $this->exitCode;
}
};
}
public function getClientIp(): string
{
$application = $this->command->getApplication();
return $application->getName().' '.$application->getVersion();
}
}

View file

@ -0,0 +1,134 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\DependencyInjection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Registers console commands.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds('console.command', true);
$lazyCommandMap = [];
$lazyCommandRefs = [];
$serviceIds = [];
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->addTag('container.no_preload');
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (isset($tags[0]['command'])) {
$aliases = $tags[0]['command'];
} else {
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
}
$aliases = str_replace('%', '%%', $class::getDefaultName() ?? '');
}
$aliases = explode('|', $aliases ?? '');
$commandName = array_shift($aliases);
if ($isHidden = '' === $commandName) {
$commandName = array_shift($aliases);
}
if (null === $commandName) {
if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) {
$commandId = 'console.command.public_alias.'.$id;
$container->setAlias($commandId, $id)->setPublic(true);
$id = $commandId;
}
$serviceIds[] = $id;
continue;
}
$description = $tags[0]['description'] ?? null;
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
foreach ($aliases as $alias) {
$lazyCommandMap[$alias] = $id;
}
foreach ($tags as $tag) {
if (isset($tag['command'])) {
$aliases[] = $tag['command'];
$lazyCommandMap[$tag['command']] = $id;
}
$description ??= $tag['description'] ?? null;
}
$definition->addMethodCall('setName', [$commandName]);
if ($aliases) {
$definition->addMethodCall('setAliases', [$aliases]);
}
if ($isHidden) {
$definition->addMethodCall('setHidden', [true]);
}
if (!$description) {
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
}
$description = str_replace('%', '%%', $class::getDefaultDescription() ?? '');
}
if ($description) {
$definition->addMethodCall('setDescription', [$description]);
$container->register('.'.$id.'.lazy', LazyCommand::class)
->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);
$lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
}
}
$container
->register('console.command_loader', ContainerCommandLoader::class)
->setPublic(true)
->addTag('container.no_preload')
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
$container->setParameter('console.command.ids', $serviceIds);
}
}

View file

@ -0,0 +1,139 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ApplicationDescription
{
public const GLOBAL_NAMESPACE = '_global';
private Application $application;
private ?string $namespace;
private bool $showHidden;
private array $namespaces;
/**
* @var array<string, Command>
*/
private array $commands;
/**
* @var array<string, Command>
*/
private array $aliases = [];
public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
{
$this->application = $application;
$this->namespace = $namespace;
$this->showHidden = $showHidden;
}
public function getNamespaces(): array
{
if (!isset($this->namespaces)) {
$this->inspectApplication();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands(): array
{
if (!isset($this->commands)) {
$this->inspectApplication();
}
return $this->commands;
}
/**
* @throws CommandNotFoundException
*/
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return $this->commands[$name] ?? $this->aliases[$name];
}
private function inspectApplication(): void
{
$this->commands = [];
$this->namespaces = [];
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
private function sortCommands(array $commands): array
{
$namespacedCommands = [];
$globalCommands = [];
$sortedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
$globalCommands[$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
if ($globalCommands) {
ksort($globalCommands);
$sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
}
if ($namespacedCommands) {
ksort($namespacedCommands, \SORT_STRING);
foreach ($namespacedCommands as $key => $commandsSet) {
ksort($commandsSet);
$sortedCommands[$key] = $commandsSet;
}
}
return $sortedCommands;
}
}

View file

@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
protected OutputInterface $output;
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$this->output = $output;
match (true) {
$object instanceof InputArgument => $this->describeInputArgument($object, $options),
$object instanceof InputOption => $this->describeInputOption($object, $options),
$object instanceof InputDefinition => $this->describeInputDefinition($object, $options),
$object instanceof Command => $this->describeCommand($object, $options),
$object instanceof Application => $this->describeApplication($object, $options),
default => throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
};
}
protected function write(string $content, bool $decorated = false): void
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
/**
* Describes an InputArgument instance.
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;
/**
* Describes an InputOption instance.
*/
abstract protected function describeInputOption(InputOption $option, array $options = []): void;
/**
* Describes an InputDefinition instance.
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;
/**
* Describes a Command instance.
*/
abstract protected function describeCommand(Command $command, array $options = []): void;
/**
* Describes an Application instance.
*/
abstract protected function describeApplication(Application $application, array $options = []): void;
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Descriptor interface.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
interface DescriptorInterface
{
/**
* @return void
*/
public function describe(OutputInterface $output, object $object, array $options = []);
}

View file

@ -0,0 +1,166 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* JSON descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
$this->writeData($this->getInputOptionData($option), $options);
if ($option->isNegatable()) {
$this->writeData($this->getInputOptionData($option, true), $options);
}
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
protected function describeCommand(Command $command, array $options = []): void
{
$this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
}
protected function describeApplication(Application $application, array $options = []): void
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace, true);
$commands = [];
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command, $options['short'] ?? false);
}
$data = [];
if ('UNKNOWN' !== $application->getName()) {
$data['application']['name'] = $application->getName();
if ('UNKNOWN' !== $application->getVersion()) {
$data['application']['version'] = $application->getVersion();
}
}
$data['commands'] = $commands;
if ($describedNamespace) {
$data['namespace'] = $describedNamespace;
} else {
$data['namespaces'] = array_values($description->getNamespaces());
}
$this->writeData($data, $options);
}
/**
* Writes data as json.
*/
private function writeData(array $data, array $options): void
{
$flags = $options['json_encoding'] ?? 0;
$this->write(json_encode($data, $flags));
}
private function getInputArgumentData(InputArgument $argument): array
{
return [
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
];
}
private function getInputOptionData(InputOption $option, bool $negated = false): array
{
return $negated ? [
'name' => '--no-'.$option->getName(),
'shortcut' => '',
'accept_value' => false,
'is_value_required' => false,
'is_multiple' => false,
'description' => 'Negate the "--'.$option->getName().'" option',
'default' => false,
] : [
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
];
}
private function getInputDefinitionData(InputDefinition $definition): array
{
$inputArguments = [];
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = [];
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
if ($option->isNegatable()) {
$inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
}
}
return ['arguments' => $inputArguments, 'options' => $inputOptions];
}
private function getCommandData(Command $command, bool $short = false): array
{
$data = [
'name' => $command->getName(),
'description' => $command->getDescription(),
];
if ($short) {
$data += [
'usage' => $command->getAliases(),
];
} else {
$command->mergeApplicationDefinition(false);
$data += [
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getDefinition()),
];
}
$data['hidden'] = $command->isHidden();
return $data;
}
}

View file

@ -0,0 +1,173 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Markdown descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class MarkdownDescriptor extends Descriptor
{
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
protected function write(string $content, bool $decorated = true): void
{
parent::write($content, $decorated);
}
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
);
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
$name = '--'.$option->getName();
if ($option->isNegatable()) {
$name .= '|--no-'.$option->getName();
}
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
}
$this->write(
'#### `'.$name.'`'."\n\n"
.($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments');
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
$this->describeInputArgument($argument);
}
}
if (\count($definition->getOptions()) > 0) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write('### Options');
foreach ($definition->getOptions() as $option) {
$this->write("\n\n");
$this->describeInputOption($option);
}
}
}
protected function describeCommand(Command $command, array $options = []): void
{
if ($options['short'] ?? false) {
$this->write(
'`'.$command->getName()."`\n"
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
);
return;
}
$command->mergeApplicationDefinition(false);
$this->write(
'`'.$command->getName()."`\n"
.str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->write("\n\n");
$this->describeInputDefinition($definition);
}
}
protected function describeApplication(Application $application, array $options = []): void
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat('=', Helper::width($title)));
foreach ($description->getNamespaces() as $namespace) {
if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->write("\n\n");
$this->write('**'.$namespace['id'].':**');
}
$this->write("\n\n");
$this->write(implode("\n", array_map(fn ($commandName) => sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$this->write("\n\n");
$this->describeCommand($command, $options);
}
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' !== $application->getName()) {
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
return 'Console Tool';
}
}

View file

@ -0,0 +1,272 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\String\UnicodeString;
class ReStructuredTextDescriptor extends Descriptor
{
// <h1>
private string $partChar = '=';
// <h2>
private string $chapterChar = '-';
// <h3>
private string $sectionChar = '~';
// <h4>
private string $subsectionChar = '.';
// <h5>
private string $subsubsectionChar = '^';
// <h6>
private string $paragraphsChar = '"';
private array $visibleNamespaces = [];
public function describe(OutputInterface $output, object $object, array $options = []): void
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
/**
* Override parent method to set $decorated = true.
*/
protected function write(string $content, bool $decorated = true): void
{
parent::write($content, $decorated);
}
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->write(
$argument->getName() ?: '<none>'."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``'
);
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
$name = '\-\-'.$option->getName();
if ($option->isNegatable()) {
$name .= '|\-\-no-'.$option->getName();
}
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut());
}
$optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
$optionDescription = (new UnicodeString($optionDescription))->ascii();
$this->write(
$name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
.$optionDescription
.'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n"
.'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n"
.'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n"
);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
if ($showArguments = ((bool) $definition->getArguments())) {
$this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n";
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
$this->describeInputArgument($argument);
}
}
if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
foreach ($nonDefaultOptions as $option) {
$this->describeInputOption($option);
$this->write("\n");
}
}
}
protected function describeCommand(Command $command, array $options = []): void
{
if ($options['short'] ?? false) {
$this->write(
'``'.$command->getName()."``\n"
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
.array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
);
return;
}
$command->mergeApplicationDefinition(false);
foreach ($command->getAliases() as $alias) {
$this->write('.. _'.$alias.":\n\n");
}
$this->write(
$command->getName()."\n"
.str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->write("\n\n");
$this->describeInputDefinition($definition);
}
}
protected function describeApplication(Application $application, array $options = []): void
{
$description = new ApplicationDescription($application, $options['namespace'] ?? null);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
$this->createTableOfContents($description, $application);
$this->describeCommands($application, $options);
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' === $application->getName()) {
return 'Console Tool';
}
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
private function describeCommands($application, array $options): void
{
$title = 'Commands';
$this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
foreach ($this->visibleNamespaces as $namespace) {
if ('_global' === $namespace) {
$commands = $application->all('');
$this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
} else {
$commands = $application->all($namespace);
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
}
foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
$this->describeCommand($command, $options);
$this->write("\n\n");
}
}
}
private function createTableOfContents(ApplicationDescription $description, Application $application): void
{
$this->setVisibleNamespaces($description);
$chapterTitle = 'Table of Contents';
$this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
foreach ($this->visibleNamespaces as $namespace) {
if ('_global' === $namespace) {
$commands = $application->all('');
} else {
$commands = $application->all($namespace);
$this->write("\n\n");
$this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
}
$commands = $this->removeAliasesAndHiddenCommands($commands);
$this->write("\n\n");
$this->write(implode("\n", array_map(static fn ($commandName) => sprintf('- `%s`_', $commandName), array_keys($commands))));
}
}
private function getNonDefaultOptions(InputDefinition $definition): array
{
$globalOptions = [
'help',
'quiet',
'verbose',
'version',
'ansi',
'no-interaction',
];
$nonDefaultOptions = [];
foreach ($definition->getOptions() as $option) {
// Skip global options.
if (!\in_array($option->getName(), $globalOptions)) {
$nonDefaultOptions[] = $option;
}
}
return $nonDefaultOptions;
}
private function setVisibleNamespaces(ApplicationDescription $description): void
{
$commands = $description->getCommands();
foreach ($description->getNamespaces() as $namespace) {
try {
$namespaceCommands = $namespace['commands'];
foreach ($namespaceCommands as $key => $commandName) {
if (!\array_key_exists($commandName, $commands)) {
// If the array key does not exist, then this is an alias.
unset($namespaceCommands[$key]);
} elseif ($commands[$commandName]->isHidden()) {
unset($namespaceCommands[$key]);
}
}
if (!$namespaceCommands) {
// If the namespace contained only aliases or hidden commands, skip the namespace.
continue;
}
} catch (\Exception) {
}
$this->visibleNamespaces[] = $namespace['id'];
}
}
private function removeAliasesAndHiddenCommands(array $commands): array
{
foreach ($commands as $key => $command) {
if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
unset($commands[$key]);
}
}
unset($commands['completion']);
return $commands;
}
}

View file

@ -0,0 +1,317 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* Text descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class TextDescriptor extends Descriptor
{
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
$spacingWidth = $totalWidth - \strlen($argument->getName());
$this->writeText(sprintf(' <info>%s</info> %s%s%s',
$argument->getName(),
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
$default
), $options);
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '='.strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '['.$value.']';
}
}
$totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
);
$spacingWidth = $totalWidth - Helper::width($synopsis);
$this->writeText(sprintf(' <info>%s</info> %s%s%s%s',
$synopsis,
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
$default,
$option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
), $options);
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, Helper::width($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (\strlen($option->getShortcut() ?? '') > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
protected function describeCommand(Command $command, array $options = []): void
{
$command->mergeApplicationDefinition(false);
if ($description = $command->getDescription()) {
$this->writeText('<comment>Description:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.$description);
$this->writeText("\n\n");
}
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' '.OutputFormatter::escape($usage), $options);
}
$this->writeText("\n");
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
$this->writeText("\n");
}
$help = $command->getProcessedHelp();
if ($help && $help !== $description) {
$this->writeText("\n");
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
protected function describeApplication(Application $application, array $options = []): void
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $application->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$commands = $description->getCommands();
$namespaces = $description->getNamespaces();
if ($describedNamespace && $namespaces) {
// make sure all alias commands are included when describing a specific namespace
$describedNamespaceInfo = reset($namespaces);
foreach ($describedNamespaceInfo['commands'] as $name) {
$commands[$name] = $description->getCommand($name);
}
}
// calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
foreach ($namespaces as $namespace) {
$namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name]));
if (!$namespace['commands']) {
continue;
}
if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - Helper::width($name);
$command = $commands[$name];
$commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
$this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
private function writeText(string $content, array $options = []): void
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
/**
* Formats command aliases to show them in the command description.
*/
private function getCommandAliasesText(Command $command): string
{
$text = '';
$aliases = $command->getAliases();
if ($aliases) {
$text = '['.implode('|', $aliases).'] ';
}
return $text;
}
/**
* Formats input option/argument default value.
*/
private function formatDefaultValue(mixed $default): string
{
if (\INF === $default) {
return 'INF';
}
if (\is_string($default)) {
$default = OutputFormatter::escape($default);
} elseif (\is_array($default)) {
foreach ($default as $key => $value) {
if (\is_string($value)) {
$default[$key] = OutputFormatter::escape($value);
}
}
}
return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
/**
* @param array<Command|string> $commands
*/
private function getColumnWidth(array $commands): int
{
$widths = [];
foreach ($commands as $command) {
if ($command instanceof Command) {
$widths[] = Helper::width($command->getName());
foreach ($command->getAliases() as $alias) {
$widths[] = Helper::width($alias);
}
} else {
$widths[] = Helper::width($command);
}
}
return $widths ? max($widths) + 2 : 0;
}
/**
* @param InputOption[] $options
*/
private function calculateTotalWidthForOptions(array $options): int
{
$totalWidth = 0;
foreach ($options as $option) {
// "-" + shortcut + ", --" + name
$nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName());
if ($option->isNegatable()) {
$nameLength += 6 + Helper::width($option->getName()); // |--no- + name
} elseif ($option->acceptValue()) {
$valueLength = 1 + Helper::width($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}

View file

@ -0,0 +1,232 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* XML descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class XmlDescriptor extends Descriptor
{
public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
return $dom;
}
public function getCommandDocument(Command $command, bool $short = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
$commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);
$commandXML->appendChild($usagesXML = $dom->createElement('usages'));
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
if ($short) {
foreach ($command->getAliases() as $usage) {
$usagesXML->appendChild($dom->createElement('usage', $usage));
}
} else {
$command->mergeApplicationDefinition(false);
foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
$usagesXML->appendChild($dom->createElement('usage', $usage));
}
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
$definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
}
return $dom;
}
public function getApplicationDocument(Application $application, string $namespace = null, bool $short = false): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
if ('UNKNOWN' !== $application->getName()) {
$rootXml->setAttribute('name', $application->getName());
if ('UNKNOWN' !== $application->getVersion()) {
$rootXml->setAttribute('version', $application->getVersion());
}
}
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$description = new ApplicationDescription($application, $namespace, true);
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->getCommandDocument($command, $short));
}
if (!$namespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespaceDescription) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
foreach ($namespaceDescription['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $dom;
}
protected function describeInputArgument(InputArgument $argument, array $options = []): void
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
protected function describeInputOption(InputOption $option, array $options = []): void
{
$this->writeDocument($this->getInputOptionDocument($option));
}
protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
protected function describeCommand(Command $command, array $options = []): void
{
$this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
}
protected function describeApplication(Application $application, array $options = []): void
{
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
}
/**
* Appends document children to parent node.
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Writes DOM document.
*/
private function writeDocument(\DOMDocument $dom): void
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('argument'));
$objectXML->setAttribute('name', $argument->getName());
$objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
$objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
return $dom;
}
private function getInputOptionDocument(InputOption $option): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('option'));
$objectXML->setAttribute('name', '--'.$option->getName());
$pos = strpos($option->getShortcut() ?? '', '|');
if (false !== $pos) {
$objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
$objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
} else {
$objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
}
$objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
$objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
$objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptValue()) {
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
if (!empty($defaults)) {
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
}
if ($option->isNegatable()) {
$dom->appendChild($objectXML = $dom->createElement('option'));
$objectXML->setAttribute('name', '--no-'.$option->getName());
$objectXML->setAttribute('shortcut', '');
$objectXML->setAttribute('accept_value', 0);
$objectXML->setAttribute('is_value_required', 0);
$objectXML->setAttribute('is_multiple', 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
}
return $dom;
}
}

View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
/**
* Allows to do things before the command is executed, like skipping the command or executing code before the command is
* going to be executed.
*
* Changing the input arguments will have no effect.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
public const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped.
*/
private bool $commandShouldRun = true;
/**
* Disables the command, so it won't be run.
*/
public function disableCommand(): bool
{
return $this->commandShouldRun = false;
}
public function enableCommand(): bool
{
return $this->commandShouldRun = true;
}
/**
* Returns true if the command is runnable, false otherwise.
*/
public function commandShouldRun(): bool
{
return $this->commandShouldRun;
}
}

View file

@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to handle throwables thrown while running a command.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class ConsoleErrorEvent extends ConsoleEvent
{
private \Throwable $error;
private int $exitCode;
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
{
parent::__construct($command, $input, $output);
$this->error = $error;
}
public function getError(): \Throwable
{
return $this->error;
}
public function setError(\Throwable $error): void
{
$this->error = $error;
}
public function setExitCode(int $exitCode): void
{
$this->exitCode = $exitCode;
$r = new \ReflectionProperty($this->error, 'code');
$r->setValue($this->error, $this->exitCode);
}
public function getExitCode(): int
{
return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
}
}

View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Allows to inspect input and output of a command.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleEvent extends Event
{
protected $command;
private InputInterface $input;
private OutputInterface $output;
public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
$this->output = $output;
}
/**
* Gets the command that is executed.
*/
public function getCommand(): ?Command
{
return $this->command;
}
/**
* Gets the input instance.
*/
public function getInput(): InputInterface
{
return $this->input;
}
/**
* Gets the output instance.
*/
public function getOutput(): OutputInterface
{
return $this->output;
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author marie <marie@users.noreply.github.com>
*/
final class ConsoleSignalEvent extends ConsoleEvent
{
private int $handlingSignal;
private int|false $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0)
{
parent::__construct($command, $input, $output);
$this->handlingSignal = $handlingSignal;
$this->exitCode = $exitCode;
}
public function getHandlingSignal(): int
{
return $this->handlingSignal;
}
public function setExitCode(int $exitCode): void
{
if ($exitCode < 0 || $exitCode > 255) {
throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
}
$this->exitCode = $exitCode;
}
public function abortExit(): void
{
$this->exitCode = false;
}
public function getExitCode(): int|false
{
return $this->exitCode;
}
}

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