From b63c45b5a4262040f71a8bc2566a92cebd3e4277 Mon Sep 17 00:00:00 2001 From: Sys <28715512+sys-001@users.noreply.github.com> Date: Fri, 15 Apr 2022 23:33:46 +0200 Subject: [PATCH] Huge refactoring, custom schema changes, dump-schemas command --- CHANGELOG.md | 18 +- README.md | 10 + bin/tgscraper | 5 +- composer.json | 14 +- composer.lock | 950 ++++++++++----------------- docs/schema.json | 21 +- psalm.xml | 19 + src/Commands/Common.php | 33 + src/Commands/DumpSchemasCommand.php | 167 +++++ src/Commands/ExportSchemaCommand.php | 27 +- src/Common/OpenApiGenerator.php | 5 +- src/Common/SchemaExtractor.php | 296 +++------ src/Common/StubCreator.php | 102 +-- src/Constants/Versions.php | 12 +- src/Parsers/Field.php | 165 +++++ src/Parsers/FieldDescription.php | 67 ++ src/Parsers/ObjectDescription.php | 76 +++ src/TgScraper.php | 39 +- 18 files changed, 1119 insertions(+), 907 deletions(-) create mode 100644 psalm.xml create mode 100644 src/Commands/Common.php create mode 100644 src/Commands/DumpSchemasCommand.php create mode 100644 src/Parsers/Field.php create mode 100644 src/Parsers/FieldDescription.php create mode 100644 src/Parsers/ObjectDescription.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 0486857..89ec363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.0] - 2022-04-15 +### Added +- Support for bot API 5.6.0 and 5.7.0 +- New `app:dump-schemas` command, used to generate schemas for all bot API versions. +- New `default` property for fields, it contains their default values when they're unspecified. + +### Changed +- (**Breaking change**) Array format in custom schema has been changed from `Array` to `Array`. +- Updated dependencies to their latest available version. + +### Fixed +- Increased speed dramatically by replacing the DOM parser, it's a lot faster now! +- Huge refactoring, improved code quality and readability. +- Some minor bug fixes. + ## [3.0.3] - 2021-12-11 ### Added - Support for bot API 5.5.0. @@ -150,7 +165,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - The parser is now more reliable, it no longer needs to be updated at every bot API release! -[Unreleased]: https://github.com/Sysbot-org/tgscraper/compare/3.0.3...HEAD +[Unreleased]: https://github.com/Sysbot-org/tgscraper/compare/4.0...HEAD +[4.0.0]: https://github.com/Sysbot-org/tgscraper/compare/3.0.3...4.0 [3.0.3]: https://github.com/Sysbot-org/tgscraper/compare/3.0.2...3.0.3 [3.0.2]: https://github.com/Sysbot-org/tgscraper/compare/3.0.1...3.0.2 [3.0.1]: https://github.com/Sysbot-org/tgscraper/compare/3.0...3.0.1 diff --git a/README.md b/README.md index 3335993..7477460 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,16 @@ Create stubs in the `out/` directory using `Sysbot\Telegram` as namespace prefix $ vendor/bin/tgscraper app:create-stubs --namespace-prefix "Sysbot\Telegram" out ``` +### All versions + +If you want to generate all schemas and stubs for every Bot API version, you can! + +Here's an example on how to export everything to the `out/` directory, with schemas in human-readable format and using `Sysbot\Telegram` as namespace prefix for the stubs: + +```bash + $ vendor/bin/tgscraper app:dump-schemas -r --namespace-prefix "Sysbot\Telegram" out +``` + ## Custom format If you're interested in the custom format generated by TGScraper, you can find its schema [here](docs/schema.json). \ No newline at end of file diff --git a/bin/tgscraper b/bin/tgscraper index 703361a..2874a56 100644 --- a/bin/tgscraper +++ b/bin/tgscraper @@ -1,9 +1,11 @@ #!/usr/bin/env php add(new CreateStubsCommand()); $application->add(new ExportSchemaCommand()); +$application->add(new DumpSchemasCommand()); try { $exitCode = $application->run(); -} catch (Exception $e) { +} catch (Throwable $e) { echo $e->getMessage() . PHP_EOL; } diff --git a/composer.json b/composer.json index 364bddd..6649d28 100644 --- a/composer.json +++ b/composer.json @@ -6,11 +6,12 @@ "php": ">=8.0", "ext-json": "*", "composer-runtime-api": "^2.0", - "nette/php-generator": "^3.5", - "paquettg/php-html-parser": "^3.1", + "guzzlehttp/guzzle": "^7.0", + "nette/php-generator": "^4.0", "psr/log": "^1.1", - "symfony/console": "^5.3", - "symfony/yaml": "^5.3" + "symfony/console": "^6.0", + "symfony/yaml": "^6.0", + "voku/simple_html_dom": "^4.7" }, "suggest": { "sysbot/tgscraper-cache": "To speed up schema fetching and generation." @@ -20,6 +21,11 @@ "TgScraper\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "TgScraper\\Tests\\": "tests/" + } + }, "bin": [ "bin/tgscraper" ], diff --git a/composer.lock b/composer.lock index df29cee..ea37370 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e8ae86c9b854e3496c7ed12c9ea0d6c4", + "content-hash": "52f401fba041fc275647f7845809f7b1", "packages": [ { "name": "guzzlehttp/guzzle", - "version": "7.4.1", + "version": "7.4.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4", + "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4", "shasum": "" }, "require": { @@ -50,12 +50,12 @@ } }, "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -112,7 +112,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.1" + "source": "https://github.com/guzzle/guzzle/tree/7.4.2" }, "funding": [ { @@ -128,7 +128,7 @@ "type": "tidelift" } ], - "time": "2021-12-06T18:43:05+00:00" + "time": "2022-03-20T14:16:28+00:00" }, { "name": "guzzlehttp/promises", @@ -157,12 +157,12 @@ } }, "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -216,29 +216,32 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.8.3", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85" + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", - "reference": "1afdd860a2566ed3c2b0b4a3de6e23434a79ec85", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2", + "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -246,16 +249,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "2.2-dev" } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -291,6 +291,11 @@ "name": "Tobias Schultze", "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -306,7 +311,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.8.3" + "source": "https://github.com/guzzle/psr7/tree/2.2.1" }, "funding": [ { @@ -322,99 +327,39 @@ "type": "tidelift" } ], - "time": "2021-10-05T13:56:00+00:00" - }, - { - "name": "myclabs/php-enum", - "version": "1.8.3", - "source": { - "type": "git", - "url": "https://github.com/myclabs/php-enum.git", - "reference": "b942d263c641ddb5190929ff840c68f78713e937" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937", - "reference": "b942d263c641ddb5190929ff840c68f78713e937", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "1.*", - "vimeo/psalm": "^4.6.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "MyCLabs\\Enum\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP Enum contributors", - "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" - } - ], - "description": "PHP Enum implementation", - "homepage": "http://github.com/myclabs/php-enum", - "keywords": [ - "enum" - ], - "support": { - "issues": "https://github.com/myclabs/php-enum/issues", - "source": "https://github.com/myclabs/php-enum/tree/1.8.3" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", - "type": "tidelift" - } - ], - "time": "2021-07-05T08:18:36+00:00" + "time": "2022-03-20T21:55:58+00:00" }, { "name": "nette/php-generator", - "version": "v3.6.5", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "9370403f9d9c25b51c4596ded1fbfe70347f7c82" + "reference": "23110ddbcdc7723f5ae06e94a535bbd01b72bfdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/9370403f9d9c25b51c4596ded1fbfe70347f7c82", - "reference": "9370403f9d9c25b51c4596ded1fbfe70347f7c82", + "url": "https://api.github.com/repos/nette/php-generator/zipball/23110ddbcdc7723f5ae06e94a535bbd01b72bfdc", + "reference": "23110ddbcdc7723f5ae06e94a535bbd01b72bfdc", "shasum": "" }, "require": { - "nette/utils": "^3.1.2", - "php": ">=7.2 <8.2" + "nette/utils": "^3.2.7 || ^4.0", + "php": ">=8.0 <8.2" }, "require-dev": { "nette/tester": "^2.4", "nikic/php-parser": "^4.13", - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.8" }, "suggest": { - "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()" + "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -448,22 +393,22 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v3.6.5" + "source": "https://github.com/nette/php-generator/tree/v4.0.1" }, - "time": "2021-11-24T16:23:44+00:00" + "time": "2022-03-10T02:24:09+00:00" }, { "name": "nette/utils", - "version": "v3.2.6", + "version": "v3.2.7", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "2f261e55bd6a12057442045bf2c249806abc1d02" + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/2f261e55bd6a12057442045bf2c249806abc1d02", - "reference": "2f261e55bd6a12057442045bf2c249806abc1d02", + "url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99", + "reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99", "shasum": "" }, "require": { @@ -533,246 +478,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.6" + "source": "https://github.com/nette/utils/tree/v3.2.7" }, - "time": "2021-11-24T15:47:23+00:00" - }, - { - "name": "paquettg/php-html-parser", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/paquettg/php-html-parser.git", - "reference": "4e01a438ad5961cc2d7427eb9798d213c8a12629" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paquettg/php-html-parser/zipball/4e01a438ad5961cc2d7427eb9798d213c8a12629", - "reference": "4e01a438ad5961cc2d7427eb9798d213c8a12629", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-mbstring": "*", - "ext-zlib": "*", - "guzzlehttp/guzzle": "^7.0", - "guzzlehttp/psr7": "^1.6", - "myclabs/php-enum": "^1.7", - "paquettg/string-encode": "~1.0.0", - "php": ">=7.2", - "php-http/httplug": "^2.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "infection/infection": "^0.13.4", - "mockery/mockery": "^1.2", - "phan/phan": "^2.4", - "phpunit/phpunit": "^7.5.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPHtmlParser\\": "src/PHPHtmlParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gilles Paquette", - "email": "paquettg@gmail.com", - "homepage": "http://gillespaquette.ca" - } - ], - "description": "An HTML DOM parser. It allows you to manipulate HTML. Find tags on an HTML page with selectors just like jQuery.", - "homepage": "https://github.com/paquettg/php-html-parser", - "keywords": [ - "dom", - "html", - "parser" - ], - "support": { - "issues": "https://github.com/paquettg/php-html-parser/issues", - "source": "https://github.com/paquettg/php-html-parser/tree/3.1.1" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/paquettg/php-html-parser", - "type": "tidelift" - } - ], - "time": "2020-11-01T20:34:43+00:00" - }, - { - "name": "paquettg/string-encode", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/paquettg/string-encoder.git", - "reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paquettg/string-encoder/zipball/a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee", - "reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.5.1" - }, - "type": "library", - "autoload": { - "psr-0": { - "stringEncode": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gilles Paquette", - "email": "paquettg@gmail.com", - "homepage": "http://gillespaquette.ca" - } - ], - "description": "Facilitating the process of altering string encoding in PHP.", - "homepage": "https://github.com/paquettg/string-encoder", - "keywords": [ - "charset", - "encoding", - "string" - ], - "support": { - "issues": "https://github.com/paquettg/string-encoder/issues", - "source": "https://github.com/paquettg/string-encoder/tree/1.0.1" - }, - "time": "2018-12-21T02:25:09+00:00" - }, - { - "name": "php-http/httplug", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/httplug.git", - "reference": "191a0a1b41ed026b717421931f8d3bd2514ffbf9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/191a0a1b41ed026b717421931f8d3bd2514ffbf9", - "reference": "191a0a1b41ed026b717421931f8d3bd2514ffbf9", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0", - "php-http/promise": "^1.1", - "psr/http-client": "^1.0", - "psr/http-message": "^1.0" - }, - "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.1", - "phpspec/phpspec": "^5.1 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eric GELOEN", - "email": "geloen.eric@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "HTTPlug, the HTTP client abstraction for PHP", - "homepage": "http://httplug.io", - "keywords": [ - "client", - "http" - ], - "support": { - "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/master" - }, - "time": "2020-07-13T15:43:23+00:00" - }, - { - "name": "php-http/promise", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Joel Wurtz", - "email": "joel.wurtz@gmail.com" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Promise used for asynchronous HTTP requests", - "homepage": "http://httplug.io", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" - }, - "time": "2020-07-07T09:29:14+00:00" + "time": "2022-01-24T11:29:14+00:00" }, { "name": "psr/container", @@ -879,6 +587,61 @@ }, "time": "2020-06-29T06:28:15+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -1028,46 +791,42 @@ }, { "name": "symfony/console", - "version": "v5.4.1", + "version": "v6.0.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4" + "reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", - "reference": "9130e1a0fc93cb0faadca4ee917171bd2ca9e5f4", + "url": "https://api.github.com/repos/symfony/console/zipball/70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e", + "reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.0.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/string": "^5.4|^6.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "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" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -1107,7 +866,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.1" + "source": "https://github.com/symfony/console/tree/v6.0.7" }, "funding": [ { @@ -1123,20 +882,85 @@ "type": "tidelift" } ], - "time": "2021-12-09T11:22:43+00:00" + "time": "2022-03-31T17:18:25+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.0.0", + "name": "symfony/css-selector", + "version": "v6.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" + "url": "https://github.com/symfony/css-selector.git", + "reference": "1955d595c12c111629cc814d3f2a2ff13580508a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1955d595c12c111629cc814d3f2a2ff13580508a", + "reference": "1955d595c12c111629cc814d3f2a2ff13580508a", + "shasum": "" + }, + "require": { + "php": ">=8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:55:41+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { @@ -1174,7 +998,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" }, "funding": [ { @@ -1190,25 +1014,28 @@ "type": "tidelift" } ], - "time": "2021-11-01T23:48:49+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + "reference": "30885182c981ab175d4d034db0f6f469898070ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", - "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-ctype": "*" + }, "suggest": { "ext-ctype": "For best performance" }, @@ -1223,12 +1050,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1253,7 +1080,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" }, "funding": [ { @@ -1269,20 +1096,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-10-20T20:35:02+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.1", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", - "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", "shasum": "" }, "require": { @@ -1302,12 +1129,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1334,7 +1161,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0" }, "funding": [ { @@ -1350,11 +1177,11 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" + "time": "2021-11-23T21:10:46+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.23.0", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -1383,12 +1210,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -1418,7 +1245,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0" }, "funding": [ { @@ -1438,21 +1265,24 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.1", + "version": "v1.25.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", - "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, "suggest": { "ext-mbstring": "For best performance" }, @@ -1467,12 +1297,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1498,7 +1328,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0" }, "funding": [ { @@ -1514,182 +1344,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T12:26:48+00:00" - }, - { - "name": "symfony/polyfill-php73", - "version": "v1.23.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", - "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "files": [ - "bootstrap.php" - ], - "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 backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.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": "2021-02-19T12:13:01+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.23.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", - "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.23-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2021-07-28T13:41:28+00:00" + "time": "2021-11-30T18:21:41+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603" + "reference": "e517458f278c2131ca9f262f8fbaf01410f2c65c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603", - "reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e517458f278c2131ca9f262f8fbaf01410f2c65c", + "reference": "e517458f278c2131ca9f262f8fbaf01410f2c65c", "shasum": "" }, "require": { @@ -1742,7 +1410,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.0.1" }, "funding": [ { @@ -1758,20 +1426,20 @@ "type": "tidelift" } ], - "time": "2021-11-04T17:53:12+00:00" + "time": "2022-03-13T20:10:05+00:00" }, { "name": "symfony/string", - "version": "v6.0.1", + "version": "v6.0.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32" + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32", - "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32", + "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", + "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", "shasum": "" }, "require": { @@ -1792,12 +1460,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -1827,7 +1495,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.1" + "source": "https://github.com/symfony/string/tree/v6.0.3" }, "funding": [ { @@ -1843,32 +1511,31 @@ "type": "tidelift" } ], - "time": "2021-12-08T15:13:44+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.0", + "version": "v6.0.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "034ccc0994f1ae3f7499fa5b1f2e75d5e7a94efc" + "reference": "e77f3ea0b21141d771d4a5655faa54f692b34af5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/034ccc0994f1ae3f7499fa5b1f2e75d5e7a94efc", - "reference": "034ccc0994f1ae3f7499fa5b1f2e75d5e7a94efc", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e77f3ea0b21141d771d4a5655faa54f692b34af5", + "reference": "e77f3ea0b21141d771d4a5655faa54f692b34af5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.0.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" + "symfony/console": "^5.4|^6.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -1902,7 +1569,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.0" + "source": "https://github.com/symfony/yaml/tree/v6.0.3" }, "funding": [ { @@ -1918,7 +1585,88 @@ "type": "tidelift" } ], - "time": "2021-11-28T15:25:38+00:00" + "time": "2022-01-26T17:23:29+00:00" + }, + { + "name": "voku/simple_html_dom", + "version": "4.8.5", + "source": { + "type": "git", + "url": "https://github.com/voku/simple_html_dom.git", + "reference": "77df6b0bd33845f61ec8b07328c463b725bdfced" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/simple_html_dom/zipball/77df6b0bd33845f61ec8b07328c463b725bdfced", + "reference": "77df6b0bd33845f61ec8b07328c463b725bdfced", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=7.0.0", + "symfony/css-selector": "~3.0 || ~4.0 || ~5.0 || ~6.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "voku/portable-utf8": "If you need e.g. UTF-8 fixed output." + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\helper\\": "src/voku/helper/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "dimabdc", + "email": "support@titor.ru", + "homepage": "http://github.com/dimabdc", + "role": "Developer" + }, + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/", + "role": "Developer" + } + ], + "description": "Simple HTML DOM package.", + "homepage": "http://simplehtmldom.sourceforge.net/", + "keywords": [ + "HTML Parser", + "dom", + "php dom" + ], + "support": { + "issues": "https://github.com/voku/simple_html_dom/issues", + "source": "https://github.com/voku/simple_html_dom/tree/4.8.5" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/simple_html_dom", + "type": "tidelift" + } + ], + "time": "2022-03-13T22:36:51+00:00" } ], "packages-dev": [], @@ -1933,5 +1681,5 @@ "composer-runtime-api": "^2.0" }, "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.3.0" } diff --git a/docs/schema.json b/docs/schema.json index 316ee06..039fade 100644 --- a/docs/schema.json +++ b/docs/schema.json @@ -61,7 +61,8 @@ "name", "types", "optional", - "description" + "description", + "default" ], "properties": { "name": { @@ -73,11 +74,27 @@ "type": "string" } }, - "required": { + "optional": { "type": "boolean" }, "description": { "type": "string" + }, + "default": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "object" + }, + { + "type": "string" + } + ] } } }, diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..7642191 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/src/Commands/Common.php b/src/Commands/Common.php new file mode 100644 index 0000000..c159107 --- /dev/null +++ b/src/Commands/Common.php @@ -0,0 +1,33 @@ +critical($prefix . 'Unable to save file to ' . $destination); + return Command::FAILURE; + } + if ($log) { + $logger->info($prefix . 'Done!'); + return Command::SUCCESS; + } + $output->writeln($prefix . 'Done!'); + return Command::SUCCESS; + } + +} \ No newline at end of file diff --git a/src/Commands/DumpSchemasCommand.php b/src/Commands/DumpSchemasCommand.php new file mode 100644 index 0000000..fca1b0e --- /dev/null +++ b/src/Commands/DumpSchemasCommand.php @@ -0,0 +1,167 @@ +isDir() ? 'rmdir' : 'unlink'); + $todo($fileInfo->getRealPath()); + } + rmdir($directory); + } + + protected function configure(): void + { + $this + ->setDescription('Export all schemas and stubs to a directory.') + ->setHelp('This command allows you to generate the schemas for all versions of the Telegram bot API.') + ->addArgument('destination', InputArgument::REQUIRED, 'Destination directory') + ->addOption( + 'namespace-prefix', + null, + InputOption::VALUE_REQUIRED, + 'Namespace prefix for stubs', + 'TelegramApi' + ) + ->addOption( + 'readable', + 'r', + InputOption::VALUE_NONE, + 'Generate human-readable files' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $versionReplacer = function (string $ver) { + /** @noinspection PhpUndefinedFieldInspection */ + $this->version = $ver; + }; + $logger = new ConsoleLogger($output); + $destination = $input->getArgument('destination'); + $readable = $input->getOption('readable'); + $output->writeln('Creating directory tree...'); + try { + $destination = TgScraper::getTargetDirectory($destination); + mkdir($destination . '/custom/json', 0755, true); + mkdir($destination . '/custom/yaml', 0755, true); + mkdir($destination . '/postman', 0755, true); + mkdir($destination . '/openapi/json', 0755, true); + mkdir($destination . '/openapi/yaml', 0755, true); + mkdir($destination . '/stubs', 0755, true); + } catch (Exception $e) { + $logger->critical((string)$e); + return Command::FAILURE; + } + $versions = array_keys( + (new ReflectionClass(Versions::class)) + ->getConstants()['URLS'] + ); + $versions = array_diff($versions, ['latest']); + foreach ($versions as $version) { + $output->writeln(sprintf('Generating v%s schemas...', $version)); + $filename = 'v' . str_replace('.', '', $version); + try { + $logger->info($version . ': Fetching data...'); + $generator = TgScraper::fromVersion($logger, $version); + } catch (Throwable $e) { + $logger->critical((string)$e); + return Command::FAILURE; + } + $versionReplacer->call($generator, $version); + $custom = $generator->toArray(); + $postman = $generator->toPostman(); + $openapi = $generator->toOpenApi(); + try { + $logger->info($version . ': Creating stubs...'); + $generator->toStubs("$destination/tmp", $input->getOption('namespace-prefix')); + } catch (Exception) { + $logger->critical($version . ': Could not create stubs.'); + return Command::FAILURE; + } + $logger->info($version . ': Compressing stubs...'); + $zip = new PharData("$destination/stubs/$filename.zip"); + $zip->buildFromDirectory("$destination/tmp"); + self::rrmdir("$destination/tmp"); + $logger->info($version . ': Saving schemas...'); + if ($this->saveFile( + $logger, + $output, + "$destination/custom/json/$filename.json", + Encoder::toJson($custom, readable: $readable), + sprintf('v%s custom (JSON): ', $version) + ) !== Command::SUCCESS) { + return Command::FAILURE; + } + if ($this->saveFile( + $logger, + $output, + "$destination/custom/yaml/$filename.yaml", + Encoder::toYaml($custom), + sprintf('v%s custom (YAML): ', $version) + ) !== Command::SUCCESS) { + return Command::FAILURE; + } + if ($this->saveFile( + $logger, + $output, + "$destination/postman/$filename.json", + Encoder::toJson($postman, readable: $readable), + sprintf('v%s Postman: ', $version) + ) !== Command::SUCCESS) { + return Command::FAILURE; + } + if ($this->saveFile( + $logger, + $output, + "$destination/openapi/json/$filename.json", + Encoder::toJson($openapi, readable: $readable), + sprintf('v%s OpenAPI (JSON): ', $version) + ) !== Command::SUCCESS) { + return Command::FAILURE; + } + if ($this->saveFile( + $logger, + $output, + "$destination/openapi/yaml/$filename.yaml", + Encoder::toYaml($openapi), + sprintf('v%s OpenAPI (YAML): ', $version) + ) !== Command::SUCCESS) { + return Command::FAILURE; + } + $logger->info($version . ': Done!'); + } + $output->writeln('Done!'); + return Command::SUCCESS; + } + +} \ No newline at end of file diff --git a/src/Commands/ExportSchemaCommand.php b/src/Commands/ExportSchemaCommand.php index 9aa9925..39b7048 100644 --- a/src/Commands/ExportSchemaCommand.php +++ b/src/Commands/ExportSchemaCommand.php @@ -5,7 +5,6 @@ namespace TgScraper\Commands; use Exception; -use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -20,6 +19,8 @@ use Throwable; class ExportSchemaCommand extends Command { + use Common; + protected static $defaultName = 'app:export-schema'; protected function configure(): void @@ -64,17 +65,6 @@ class ExportSchemaCommand extends Command ); } - private function saveFile(ConsoleLogger $logger, OutputInterface $output, string $destination, string $data): int - { - $result = file_put_contents($destination, $data); - if (false === $result) { - $logger->critical('Unable to save file to ' . $destination); - return Command::FAILURE; - } - $output->writeln('Done!'); - return Command::SUCCESS; - } - protected function execute(InputInterface $input, OutputInterface $output): int { $logger = new ConsoleLogger($output); @@ -86,7 +76,8 @@ class ExportSchemaCommand extends Command try { $output->writeln('Fetching data for version...'); $generator = TgScraper::fromVersion($logger, $version); - } catch (Throwable) { + } catch (Throwable $e) { + $logger->critical((string)$e); return Command::FAILURE; } $output->writeln('Exporting schema from data...'); @@ -105,19 +96,19 @@ class ExportSchemaCommand extends Command if ($input->getOption('openapi')) { $data = $generator->toOpenApi(); if ($useYaml) { - return $this->saveFile($logger, $output, $destination, Encoder::toYaml($data, $inline, $indent, $options)); + return $this->saveFile($logger, $output, $destination, Encoder::toYaml($data, $inline, $indent, $options), log: false); } - return $this->saveFile($logger, $output, $destination, Encoder::toJson($data, $options | JSON_UNESCAPED_SLASHES, $readable)); + return $this->saveFile($logger, $output, $destination, Encoder::toJson($data, $options | JSON_UNESCAPED_SLASHES, $readable), log: false); } if ($input->getOption('postman')) { $data = $generator->toPostman(); - return $this->saveFile($logger, $output, $destination, Encoder::toJson($data, $options, $readable)); + return $this->saveFile($logger, $output, $destination, Encoder::toJson($data, $options, $readable), log: false); } $data = $generator->toArray(); if ($useYaml) { - return $this->saveFile($logger, $output, $destination, Encoder::toYaml($data, $inline, $indent, $options)); + return $this->saveFile($logger, $output, $destination, Encoder::toYaml($data, $inline, $indent, $options), log: false); } - return $this->saveFile($logger, $output, $destination, Encoder::toJson($data, $options, $readable)); + return $this->saveFile($logger, $output, $destination, Encoder::toJson($data, $options, $readable), log: false); } } \ No newline at end of file diff --git a/src/Common/OpenApiGenerator.php b/src/Common/OpenApiGenerator.php index ef4b85e..cbe86fb 100644 --- a/src/Common/OpenApiGenerator.php +++ b/src/Common/OpenApiGenerator.php @@ -80,6 +80,9 @@ class OpenApiGenerator $schema['required'][] = $name; } $schema['properties'][$name] = self::parsePropertyTypes($field['types']); + if (!empty($field['default'] ?? null)) { + $schema['properties'][$name]['default'] = $field['default']; + } } return $schema; } @@ -116,7 +119,7 @@ class OpenApiGenerator if (preg_match('/Array<(.+)>/', $type, $matches) === 1) { return [ 'type' => 'array', - 'items' => self::parsePropertyTypes(explode(',', $matches[1])) + 'items' => self::parsePropertyTypes(explode('|', $matches[1])) ]; } return []; diff --git a/src/Common/SchemaExtractor.php b/src/Common/SchemaExtractor.php index 58af61d..a978784 100644 --- a/src/Common/SchemaExtractor.php +++ b/src/Common/SchemaExtractor.php @@ -1,25 +1,23 @@ version = $this->parseVersion(); $this->logger->info('Bot API version: ' . $this->version); @@ -60,13 +47,17 @@ class SchemaExtractor * @param string $version * @return SchemaExtractor * @throws OutOfBoundsException - * @throws Throwable + * @throws Exception + * @throws GuzzleException */ public static function fromVersion(LoggerInterface $logger, string $version = Versions::LATEST): SchemaExtractor { if (InstalledVersions::isInstalled('sysbot/tgscraper-cache') and class_exists('\TgScraper\Cache\CacheLoader')) { $logger->info('Cache package detected, searching for a cached version.'); try { + /** @noinspection PhpFullyQualifiedNameUsageInspection */ + /** @noinspection PhpUndefinedNamespaceInspection */ + /** @psalm-suppress UndefinedClass */ $path = \TgScraper\Cache\CacheLoader::getCachedVersion($version); $logger->info('Cached version found.'); return self::fromFile($logger, $path); @@ -83,20 +74,20 @@ class SchemaExtractor * @param LoggerInterface $logger * @param string $path * @return SchemaExtractor - * @throws Throwable + * @throws InvalidArgumentException + * @throws RuntimeException */ public static function fromFile(LoggerInterface $logger, string $path): SchemaExtractor { - $dom = new Dom; - if (!file_exists($path)) { + if (!file_exists($path) or is_dir($path)) { throw new InvalidArgumentException('File not found'); } $path = realpath($path); try { $logger->info(sprintf('Loading data from file "%s".', $path)); - $dom->loadFromFile($path); + $dom = HtmlDomParser::file_get_html($path); $logger->info('Data loaded.'); - } catch (Throwable $e) { + } catch (RuntimeException $e) { $logger->critical(sprintf('Unable to load data from "%s": %s', $path, $e->getMessage())); throw $e; } @@ -107,20 +98,15 @@ class SchemaExtractor * @param LoggerInterface $logger * @param string $url * @return SchemaExtractor - * @throws ChildNotFoundException - * @throws CircularException - * @throws ClientExceptionInterface - * @throws ContentLengthException - * @throws LogicalException - * @throws StrictException - * @throws NotLoadedException + * @throws GuzzleException */ public static function fromUrl(LoggerInterface $logger, string $url): SchemaExtractor { - $dom = new Dom; + $client = new Client(); try { - $dom->loadFromURL($url); - } catch (Throwable $e) { + $html = $client->get($url)->getBody(); + $dom = HtmlDomParser::str_get_html((string)$html); + } catch (GuzzleException $e) { $logger->critical(sprintf('Unable to load data from URL "%s": %s', $url, $e->getMessage())); throw $e; } @@ -129,57 +115,56 @@ class SchemaExtractor } /** - * @throws ParentNotFoundException - * @throws ChildNotFoundException + * @param SimpleHtmlDomInterface $node + * @return array{description: string, table: ?SimpleHtmlDomNodeInterface, extended_by: string[]} */ - #[ArrayShape(['description' => "string", 'table' => "mixed", 'extended_by' => "array"])] - private static function parseNode(Dom\Node\AbstractNode $node): ?array + private static function parseNode(SimpleHtmlDomInterface $node): array { $description = ''; $table = null; $extendedBy = []; $tag = ''; $sibling = $node; - while (!str_starts_with($tag, 'h')) { - $sibling = $sibling->nextSibling(); - $tag = $sibling?->tag?->name(); - if (empty($node->text()) or empty($tag) or $tag == 'text') { + while (!str_starts_with($tag ?? '', 'h')) { + $sibling = $sibling?->nextSibling(); + $tag = $sibling?->tag; + if (empty($node->text()) or empty($tag) or $tag == 'text' or empty($sibling)) { continue; - } elseif ($tag == 'p') { - $description .= PHP_EOL . $sibling->innerHtml(); - } elseif ($tag == 'ul') { - $items = $sibling->find('li'); - /* @var Dom\Node\AbstractNode $item */ - foreach ($items as $item) { - $extendedBy[] = $item->innerText; - } - break; - } elseif ($tag == 'table') { - $table = $sibling->find('tbody')->find('tr'); - break; + } + switch ($tag) { + case 'p': + $description .= PHP_EOL . $sibling->innerHtml(); + break; + case 'ul': + $items = $sibling->findMulti('li'); + foreach ($items as $item) { + $extendedBy[] = $item->text(); + } + break 2; + case 'table': + /** @var SimpleHtmlDomNodeInterface $table */ + $table = $sibling->findOne('tbody')->findMulti('tr'); + break 2; } } return ['description' => $description, 'table' => $table, 'extended_by' => $extendedBy]; } /** - * @throws ChildNotFoundException - * @throws NotLoadedException + * @return string */ private function parseVersion(): string { - /** @var Dom\Node\AbstractNode $element */ - $element = $this->dom->find('h3')[0]; + $element = $this->dom->findOne('h3'); $tag = ''; - while ($tag != 'p') { - try { - $element = $element->nextSibling(); - } catch (ChildNotFoundException | ParentNotFoundException) { - continue; - } - $tag = $element->tag->name(); + while ($tag != 'p' and !empty($element)) { + $element = $element->nextSibling(); + $tag = $element?->tag; } - $versionNumbers = explode('.', str_replace('Bot API ', '', $element->innerText)); + if (empty($element)) { + return '1.0.0'; + } + $versionNumbers = explode('.', str_replace('Bot API ', '', $element->text())); return sprintf( '%s.%s.%s', $versionNumbers[0] ?? '1', @@ -189,22 +174,26 @@ class SchemaExtractor } /** - * @return array - * @throws Throwable + * @return string + */ + public function getVersion(): string + { + return $this->version; + } + + /** + * @return array{version: string, methods: array, types: array} + * @throws Exception */ - #[ArrayShape(['version' => "string", 'methods' => "array", 'types' => "array"])] public function extract(): array { - try { - $elements = $this->dom->find('h4'); - } catch (Throwable $e) { - $this->logger->critical(sprintf('Unable to parse data: %s', $e->getMessage())); - throw $e; + $elements = $this->dom->findMultiOrFalse('h4'); + if (false === $elements) { + throw new Exception('Unable to fetch required DOM nodes'); } - $data = ['version' => $this->version]; - /* @var Dom\Node\AbstractNode $element */ + $data = ['version' => $this->version, 'methods' => [], 'types' => []]; foreach ($elements as $element) { - if (!str_contains($name = $element->text, ' ')) { + if (!str_contains($name = $element->text(), ' ')) { $isMethod = lcfirst($name) == $name; $path = $isMethod ? 'methods' : 'types'; ['description' => $description, 'table' => $table, 'extended_by' => $extendedBy] = self::parseNode( @@ -225,21 +214,15 @@ class SchemaExtractor /** * @param string $name * @param string $description - * @param Dom\Node\Collection|null $unparsedFields + * @param SimpleHtmlDomNodeInterface|null $unparsedFields * @param array $extendedBy * @param bool $isMethod * @return array - * @throws ChildNotFoundException - * @throws CircularException - * @throws ContentLengthException - * @throws LogicalException - * @throws NotLoadedException - * @throws StrictException */ private static function generateElement( string $name, string $description, - ?Dom\Node\Collection $unparsedFields, + ?SimpleHtmlDomNodeInterface $unparsedFields, array $extendedBy, bool $isMethod ): array { @@ -250,10 +233,8 @@ class SchemaExtractor 'fields' => $fields ]; if ($isMethod) { - $returnTypes = self::parseReturnTypes($description); - if (empty($returnTypes) and in_array($name, self::BOOL_RETURNS)) { - $returnTypes[] = 'bool'; - } + $description = new ObjectDescription($description); + $returnTypes = $description->getTypes(); $result['return_types'] = $returnTypes; return $result; } @@ -262,126 +243,33 @@ class SchemaExtractor } /** - * @param Dom\Node\Collection|null $fields + * @param SimpleHtmlDomNodeInterface|null $fields * @param bool $isMethod * @return array */ - private static function parseFields(?Dom\Node\Collection $fields, bool $isMethod): array + private static function parseFields(?SimpleHtmlDomNodeInterface $fields, bool $isMethod): array { $parsedFields = []; - $fields = $fields ?? []; + $fields ??= []; + /** @var SimpleHtmlDomInterface $field */ foreach ($fields as $field) { - /* @var Dom\Node\AbstractNode $fieldData */ - $fieldData = $field->find('td'); - $name = $fieldData[0]->text; + /** @var SimpleHtmlDomNode $fieldData */ + $fieldData = $field->findMulti('td'); + $name = $fieldData[0]->text(); if (empty($name)) { continue; } - $parsedData = [ - 'name' => $name, - 'type' => strip_tags($fieldData[1]->innerHtml) - ]; - $parsedData['types'] = self::parseFieldTypes($parsedData['type']); - unset($parsedData['type']); + $types = $fieldData[1]->text(); if ($isMethod) { - $parsedData['optional'] = $fieldData[2]->text != 'Yes'; - $parsedData['description'] = htmlspecialchars_decode( - strip_tags($fieldData[3]->innerHtml ?? $fieldData[3]->text ?? ''), - ENT_QUOTES - ); + $optional = $fieldData[2]->text() != 'Yes'; + $description = $fieldData[3]->innerHtml(); } else { - $description = htmlspecialchars_decode(strip_tags($fieldData[2]->innerHtml), ENT_QUOTES); - $parsedData['optional'] = str_starts_with($description, 'Optional.'); - $parsedData['description'] = $description; + $description = $fieldData[2]->innerHtml(); + $optional = str_starts_with($fieldData[2]->text(), 'Optional.'); } - $parsedFields[] = $parsedData; + $field = new Field($name, $types, $optional, $description); + $parsedFields[] = $field->toArray(); } return $parsedFields; } - - /** - * @param string $rawType - * @return array - */ - private static function parseFieldTypes(string $rawType): array - { - $types = []; - foreach (explode(' or ', $rawType) as $rawOrType) { - if (stripos($rawOrType, 'array') === 0) { - $types[] = str_replace(' and', ',', $rawOrType); - continue; - } - foreach (explode(' and ', $rawOrType) as $unparsedType) { - $types[] = $unparsedType; - } - } - $parsedTypes = []; - foreach ($types as $type) { - $type = trim(str_replace(['number', 'of'], '', $type)); - $multiplesCount = substr_count(strtolower($type), 'array'); - $parsedType = trim( - str_replace( - ['Array', 'Integer', 'String', 'Boolean', 'Float', 'True'], - ['', 'int', 'string', 'bool', 'float', 'bool'], - $type - ) - ); - for ($i = 0; $i < $multiplesCount; $i++) { - $parsedType = sprintf('Array<%s>', $parsedType); - } - $parsedTypes[] = $parsedType; - } - return $parsedTypes; - } - - /** - * @param string $description - * @return array - * @throws ChildNotFoundException - * @throws CircularException - * @throws NotLoadedException - * @throws StrictException - * @throws ContentLengthException - * @throws LogicalException - * @noinspection PhpUndefinedFieldInspection - */ - private static function parseReturnTypes(string $description): array - { - $returnTypes = []; - $phrases = explode('.', $description); - $phrases = array_filter( - $phrases, - function ($phrase) { - return (false !== stripos($phrase, 'returns') or false !== stripos($phrase, 'is returned')); - } - ); - foreach ($phrases as $phrase) { - $dom = new Dom; - $dom->loadStr($phrase); - $a = $dom->find('a'); - $em = $dom->find('em'); - foreach ($a as $element) { - if ($element->text == 'Messages') { - $returnTypes[] = 'Array'; - continue; - } - - $multiplesCount = substr_count(strtolower($phrase), 'array'); - $returnType = $element->text; - for ($i = 0; $i < $multiplesCount; $i++) { - $returnType = sprintf('Array<%s>', $returnType); - } - $returnTypes[] = $returnType; - } - foreach ($em as $element) { - if (in_array($element->text, ['False', 'force', 'Array'])) { - continue; - } - $type = str_replace(['True', 'Int', 'String'], ['bool', 'int', 'string'], $element->text); - $returnTypes[] = $type; - } - } - return $returnTypes; - } - -} \ No newline at end of file +} diff --git a/src/Common/StubCreator.php b/src/Common/StubCreator.php index cc7905c..8bf74a0 100644 --- a/src/Common/StubCreator.php +++ b/src/Common/StubCreator.php @@ -1,12 +1,12 @@ schema['types'] as $type) { if (!empty($type['extended_by'])) { @@ -85,10 +86,6 @@ class StubCreator * @param PhpNamespace $phpNamespace * @return array */ - #[ArrayShape([ - 'types' => "string", - 'comments' => "string" - ])] private function parseFieldTypes(array $fieldTypes, PhpNamespace $phpNamespace): array { $types = []; @@ -116,10 +113,6 @@ class StubCreator * @param PhpNamespace $phpNamespace * @return array */ - #[ArrayShape([ - 'types' => "string", - 'comments' => "string" - ])] private function parseApiFieldTypes(array $apiTypes, PhpNamespace $phpNamespace): array { $types = []; @@ -128,6 +121,17 @@ class StubCreator $comments[] = $apiType; if (str_starts_with($apiType, 'Array')) { $types[] = 'array'; + $text = $apiType; + while (preg_match('/Array<(.+)>/', $text, $matches) === 1) { + $text = $matches[1]; + } + $subTypes = explode('|', $text); + foreach ($subTypes as $subType) { + if (ucfirst($subType) == $subType) { + $subType = $this->namespace . '\\Types\\' . $subType; + $phpNamespace->addUse($subType); + } + } continue; } if (ucfirst($apiType) == $apiType) { @@ -136,7 +140,7 @@ class StubCreator } $types[] = $apiType; } - $comments = empty($comments) ? '' : sprintf('@var %s', implode('|', $comments)); + $comments = empty($comments) ? '' : sprintf('@param %s', implode('|', $comments)); return [ 'types' => implode('|', $types), 'comments' => $comments @@ -147,10 +151,6 @@ class StubCreator * @param string $namespace * @return PhpFile[] */ - #[ArrayShape([ - 'Response' => "\Nette\PhpGenerator\PhpFile", - 'TypeInterface' => "\Nette\PhpGenerator\ClassType" - ])] private function generateDefaultTypes(string $namespace): array { $interfaceFile = new PhpFile; @@ -158,25 +158,30 @@ class StubCreator $interfaceNamespace->addInterface('TypeInterface'); $responseFile = new PhpFile; $responseNamespace = $responseFile->addNamespace($namespace); - $response = $responseNamespace->addClass('Response') - ->setType('class'); + $responseNamespace->addUse('stdClass'); + $response = $responseNamespace->addClass('Response'); $response->addProperty('ok') ->setPublic() ->setType(Type::BOOL); $response->addProperty('result') ->setPublic() - ->setType(Type::MIXED) - ->setNullable(true) + ->setType(sprintf('stdClass|%s\\TypeInterface|array|int|string|bool', $namespace)) + ->setNullable() ->setValue(null); $response->addProperty('errorCode') ->setPublic() ->setType(Type::INT) - ->setNullable(true) + ->setNullable() ->setValue(null); $response->addProperty('description') ->setPublic() ->setType(Type::STRING) - ->setNullable(true) + ->setNullable() + ->setValue(null); + $response->addProperty('parameters') + ->setPublic() + ->setType(sprintf('stdClass|%s\\ResponseParameters', $namespace)) + ->setNullable() ->setValue(null); $response->addImplement($namespace . '\\TypeInterface'); return [ @@ -195,13 +200,12 @@ class StubCreator foreach ($this->schema['types'] as $type) { $file = new PhpFile; $phpNamespace = $file->addNamespace($namespace); - $typeClass = $phpNamespace->addClass($type['name']) - ->setType('class'); + $typeClass = $phpNamespace->addClass($type['name']); if (in_array($type['name'], $this->abstractClasses)) { $typeClass->setAbstract(); } if (array_key_exists($type['name'], $this->extendedClasses)) { - $typeClass->addExtend($namespace . '\\' . $this->extendedClasses[$type['name']]); + $typeClass->setExtends($namespace . '\\' . $this->extendedClasses[$type['name']]); } else { $typeClass->addImplement($namespace . '\\TypeInterface'); } @@ -214,12 +218,19 @@ class StubCreator $typeProperty = $typeClass->addProperty($fieldName) ->setPublic() ->setType($fieldTypes); + $default = $field['default'] ?? null; + if (!empty($default)) { + $typeProperty->setValue($default); + } if ($field['optional']) { - $typeProperty->setNullable(true) - ->setValue(null); + $typeProperty->setNullable(); + if (!$typeProperty->isInitialized()) { + $typeProperty->setValue(null); + } $fieldComments .= '|null'; } if (!empty($fieldComments)) { + $fieldComments .= ' ' . $field['description']; $typeProperty->addComment($fieldComments); } } @@ -229,16 +240,15 @@ class StubCreator } /** - * @return string + * @return PhpFile */ - private function generateApi(): string + private function generateApi(): PhpFile { $file = new PhpFile; $file->addComment('@noinspection PhpUnused'); $file->addComment('@noinspection PhpUnusedParameterInspection'); $phpNamespace = $file->addNamespace($this->namespace); - $apiClass = $phpNamespace->addClass('API') - ->setTrait(); + $apiClass = $phpNamespace->addTrait('API'); $sendRequest = $apiClass->addMethod('sendRequest') ->setPublic() ->setAbstract() @@ -252,6 +262,7 @@ class StubCreator ->setPublic() ->addBody('$args = get_defined_vars();') ->addBody('return $this->sendRequest(__FUNCTION__, $args);'); + $function->addComment($method['description']); $fields = $method['fields']; usort( $fields, @@ -260,28 +271,37 @@ class StubCreator } ); foreach ($fields as $field) { - $types = $this->parseApiFieldTypes($field['types'], $phpNamespace)['types']; + ['types' => $types, 'comments' => $comment] = $this->parseApiFieldTypes($field['types'], $phpNamespace); $fieldName = self::toCamelCase($field['name']); $parameter = $function->addParameter($fieldName) ->setType($types); - if ($field['optional']) { - $parameter->setNullable() - ->setDefaultValue(null); + $default = $field['default'] ?? null; + if (!empty($default) and (!is_string($default) or lcfirst($default) == $default)) { + $parameter->setDefaultValue($default); } + if ($field['optional']) { + $parameter->setNullable(); + if (!$parameter->hasDefaultValue()) { + $parameter->setDefaultValue(null); + } + $comment .= '|null'; + } + $comment .= sprintf(' $%s %s', $fieldName, $field['description']); + $function->addComment($comment); } - $returnTypes = $this->parseApiFieldTypes($method['return_types'], $phpNamespace)['types']; + ['types' => $returnTypes, 'comments' => $returnComment] = $this->parseApiFieldTypes( + $method['return_types'], + $phpNamespace + ); $function->setReturnType($returnTypes); + $function->addComment(str_replace('param', 'return', $returnComment)); } return $file; } /** - * @return array + * @return array{types: PhpFile[], api: PhpFile} */ - #[ArrayShape([ - 'types' => "\Nette\PhpGenerator\PhpFile[]", - 'api' => "string" - ])] public function generateCode(): array { return [ diff --git a/src/Constants/Versions.php b/src/Constants/Versions.php index 1b8e71c..b938899 100644 --- a/src/Constants/Versions.php +++ b/src/Constants/Versions.php @@ -1,12 +1,9 @@ 'https://web.archive.org/web/20150714025308id_/https://core.telegram.org/bots/api/', @@ -83,6 +82,8 @@ class Versions self::V530 => 'https://web.archive.org/web/20210626142851id_/https://core.telegram.org/bots/api', self::V540 => 'https://web.archive.org/web/20211105152638id_/https://core.telegram.org/bots/api', self::V550 => 'https://web.archive.org/web/20211211002657id_/https://core.telegram.org/bots/api', + self::V560 => 'https://web.archive.org/web/20220105131529id_/https://core.telegram.org/bots/api', + self::V570 => 'https://web.archive.org/web/20220206103922id_/https://core.telegram.org/bots/api', self::LATEST => 'https://core.telegram.org/bots/api' ]; @@ -101,5 +102,4 @@ class Versions $version = self::getVersionFromText($text); return self::URLS[$version] ?? self::URLS[self::LATEST]; } - -} \ No newline at end of file +} diff --git a/src/Parsers/Field.php b/src/Parsers/Field.php new file mode 100644 index 0000000..6d8ddb3 --- /dev/null +++ b/src/Parsers/Field.php @@ -0,0 +1,165 @@ + 'int', + 'Float' => 'float', + 'String' => 'string', + 'Boolean' => 'bool', + 'True' => 'bool', + 'False' => 'bool' + ]; + + /** + * @var string + */ + private string $name; + /** + * @var array + */ + private array $types; + /** + * @var FieldDescription + */ + private FieldDescription $description; + /** + * @var bool + */ + private bool $optional; + /** + * @var mixed + */ + private mixed $defaultValue; + + /** + * @param string $name + * @param string $types + * @param bool $optional + * @param string $description + */ + public function __construct(string $name, string $types, bool $optional, string $description) + { + $this->name = $name; + $this->types = $this->parseTypesString($types); + $this->optional = $optional; + $this->description = new FieldDescription($description); + } + + /** + * @param string $type + * @return string + */ + private function parseTypeString(string $type): string + { + if ($type == 'True') { + $this->defaultValue = true; + return self::TYPES['Boolean']; + } elseif ($type == 'False') { + $this->defaultValue = false; + return self::TYPES['Boolean']; + } + $type = trim(str_replace('number', '', $type)); + return trim(str_replace(array_keys(self::TYPES), array_values(self::TYPES), $type)); + } + + /** + * @param string $text + * @return array + */ + private function parseTypesString(string $text): array + { + $types = []; + $parts = explode(' or ', $text); + foreach ($parts as $part) { + $part = trim(str_replace(' and', ',', $part)); + $arrays = 0; + while (stripos($part, 'array of') === 0) { + $part = substr($part, 9); + $arrays++; + } + $pieces = explode(',', $part); + foreach ($pieces as $index => $piece) { + $pieces[$index] = $this->parseTypeString($piece); + } + $type = implode('|', $pieces); + for ($i = 0; $i < $arrays; $i++) { + $type = sprintf('Array<%s>', $type); + } + $types[] = $type; + } + return $types; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * @return bool + */ + public function isOptional(): bool + { + return $this->optional; + } + + /** + * @return mixed + */ + public function getDefaultValue(): mixed + { + if (!isset($this->defaultValue)) { + $this->defaultValue = $this->description->getDefaultValue(); + } + return $this->defaultValue; + } + + /** + * @return array + */ + #[ArrayShape([ + 'name' => "string", + 'types' => "array", + 'optional' => "bool", + 'description' => "string", + 'default' => "mixed" + ])] public function toArray(): array + { + $result = [ + 'name' => $this->name, + 'types' => $this->types, + 'optional' => $this->optional, + 'description' => (string)$this->description, + ]; + $defaultValue = $this->getDefaultValue(); + if (null !== $defaultValue) { + $result['default'] = $defaultValue; + } + return $result; + } + +} \ No newline at end of file diff --git a/src/Parsers/FieldDescription.php b/src/Parsers/FieldDescription.php new file mode 100644 index 0000000..bcfa19b --- /dev/null +++ b/src/Parsers/FieldDescription.php @@ -0,0 +1,67 @@ +dom = HtmlDomParser::str_get_html($description); + foreach ($this->dom->find('.emoji') as $emoji) { + $emoji->outerhtml .= $emoji->getAttribute('alt'); + } + } + + public function __toString() + { + return htmlspecialchars_decode($this->dom->text(), ENT_QUOTES); + } + + public function getDefaultValue(): mixed + { + $description = (string)$this; + if (stripos($description, 'must be') !== false) { + $text = explode('must be ', $this->dom->html())[1] ?? ''; + if (!empty($text)) { + $text = explode(' ', $text)[0]; + $dom = HtmlDomParser::str_get_html($text); + $element = $dom->findOneOrFalse('em'); + if ($element !== false) { + return $element->text(); + } + } + } + $offset = stripos($description, 'defaults to'); + if ($offset === false) { + return null; + } + $description = substr($description, $offset + 12); + $parts = explode(' ', $description, 2); + $value = $parts[0]; + if (str_ends_with($value, '.') or str_ends_with($value, ',')) { + $value = substr($value, 0, -1); + } + if (str_starts_with($value, '“') and str_ends_with($value, '”')) { + return str_replace(['“', '”'], ['', ''], $value); + } + if (is_numeric($value)) { + return (int)$value; + } + if (strtolower($value) == 'true') { + return true; + } + if (strtolower($value) == 'false') { + return false; + } + if ($value === ucfirst($value)) { + return $value; + } + return null; + } + +} \ No newline at end of file diff --git a/src/Parsers/ObjectDescription.php b/src/Parsers/ObjectDescription.php new file mode 100644 index 0000000..39932cd --- /dev/null +++ b/src/Parsers/ObjectDescription.php @@ -0,0 +1,76 @@ +types = self::parseReturnTypes($description); + } + + /** + * @param string $description + * @return array + */ + private static function parseReturnTypes(string $description): array + { + $returnTypes = []; + $phrases = explode('.', $description); + $phrases = array_filter( + $phrases, + function ($phrase) { + return (false !== stripos($phrase, 'returns') or false !== stripos($phrase, 'is returned')); + } + ); + foreach ($phrases as $phrase) { + $dom = HtmlDomParser::str_get_html($phrase); + $a = $dom->findMulti('a'); + $em = $dom->findMulti('em'); + foreach ($a as $element) { + if ($element->text() == 'Messages') { + $returnTypes[] = 'Array'; + continue; + } + $arrays = substr_count(strtolower($phrase), 'array'); + $returnType = $element->text(); + for ($i = 0; $i < $arrays; $i++) { + $returnType = sprintf('Array<%s>', $returnType); + } + $returnTypes[] = $returnType; + } + foreach ($em as $element) { + if (in_array($element->text(), ['False', 'force', 'Array'])) { + continue; + } + $type = str_replace(['True', 'Int', 'String'], ['bool', 'int', 'string'], $element->text()); + $returnTypes[] = $type; + } + } + return $returnTypes; + } + + /** + * @return array + */ + public function getTypes(): array + { + return $this->types; + } + +} \ No newline at end of file diff --git a/src/TgScraper.php b/src/TgScraper.php index 3b5cc50..6bf8339 100644 --- a/src/TgScraper.php +++ b/src/TgScraper.php @@ -3,17 +3,10 @@ namespace TgScraper; use Exception; +use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; use JetBrains\PhpStorm\ArrayShape; use JsonException; -use PHPHtmlParser\Exceptions\ChildNotFoundException; -use PHPHtmlParser\Exceptions\CircularException; -use PHPHtmlParser\Exceptions\ContentLengthException; -use PHPHtmlParser\Exceptions\LogicalException; -use PHPHtmlParser\Exceptions\NotLoadedException; -use PHPHtmlParser\Exceptions\ParentNotFoundException; -use PHPHtmlParser\Exceptions\StrictException; -use Psr\Http\Client\ClientExceptionInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Yaml\Yaml; use TgScraper\Common\OpenApiGenerator; @@ -65,15 +58,7 @@ class TgScraper /** * @param LoggerInterface $logger * @param string $url - * @return static - * @throws ChildNotFoundException - * @throws CircularException - * @throws ClientExceptionInterface - * @throws ContentLengthException - * @throws LogicalException - * @throws NotLoadedException - * @throws ParentNotFoundException - * @throws StrictException + * @return self * @throws Throwable */ public static function fromUrl(LoggerInterface $logger, string $url): self @@ -86,16 +71,9 @@ class TgScraper /** * @param LoggerInterface $logger * @param string $version - * @return static - * @throws ChildNotFoundException - * @throws CircularException - * @throws ClientExceptionInterface - * @throws ContentLengthException - * @throws LogicalException - * @throws NotLoadedException - * @throws ParentNotFoundException - * @throws StrictException - * @throws Throwable + * @return self + * @throws Exception + * @throws GuzzleException */ public static function fromVersion(LoggerInterface $logger, string $version = Versions::LATEST): self { @@ -243,7 +221,7 @@ class TgScraper $formData = []; if (!empty($method['fields'])) { foreach ($method['fields'] as $field) { - $formData[] = [ + $data = [ 'key' => $field['name'], 'disabled' => $field['optional'], 'description' => sprintf( @@ -253,6 +231,11 @@ class TgScraper ), 'type' => 'text' ]; + $default = $field['default'] ?? null; + if (!empty($default)) { + $data['value'] = (string)$default; + } + $formData[] = $data; } } $result['item'][] = [