From 8bf40aad60593b91a71091f974d820ee202dad0a Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:09:06 +0400 Subject: [PATCH 01/33] New package. --- composer.json | 12 +++-- packages/documentator/CHANGELOG.md | 3 ++ packages/documentator/LICENSE | 21 ++++++++ packages/documentator/README.md | 7 +++ packages/documentator/composer.json | 48 +++++++++++++++++++ packages/documentator/metadata.json | 3 ++ packages/documentator/phpunit.xml | 29 +++++++++++ packages/documentator/src/Package.php | 7 +++ .../documentator/src/PackageException.php | 12 +++++ packages/documentator/src/Provider.php | 9 ++++ phpunit.xml | 5 ++ 11 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 packages/documentator/CHANGELOG.md create mode 100644 packages/documentator/LICENSE create mode 100644 packages/documentator/README.md create mode 100644 packages/documentator/composer.json create mode 100644 packages/documentator/metadata.json create mode 100644 packages/documentator/phpunit.xml create mode 100644 packages/documentator/src/Package.php create mode 100644 packages/documentator/src/PackageException.php create mode 100644 packages/documentator/src/Provider.php diff --git a/composer.json b/composer.json index 638206fa4..6089e3244 100644 --- a/composer.json +++ b/composer.json @@ -100,7 +100,8 @@ "LastDragon_ru\\LaraASP\\Spa\\": "packages/spa/src/", "LastDragon_ru\\LaraASP\\GraphQL\\": "packages/graphql/src/", "LastDragon_ru\\LaraASP\\GraphQLPrinter\\": "packages/graphql-printer/src/", - "LastDragon_ru\\LaraASP\\Serializer\\": "packages/serializer/src/" + "LastDragon_ru\\LaraASP\\Serializer\\": "packages/serializer/src/", + "LastDragon_ru\\LaraASP\\Documentator\\": "packages/documentator/src/" }, "exclude-from-classmap": [ "packages/core/src/**Test.php", @@ -112,7 +113,8 @@ "packages/spa/src/**Test.php", "packages/graphql/src/**Test.php", "packages/graphql-printer/src/**Test.php", - "packages/serializer/src/**Test.php" + "packages/serializer/src/**Test.php", + "packages/documentator/src/**Test.php" ] }, "autoload-dev": { @@ -128,7 +130,8 @@ "LastDragon_ru\\LaraASP\\Spa\\Provider", "LastDragon_ru\\LaraASP\\GraphQL\\Provider", "LastDragon_ru\\LaraASP\\Queue\\Provider", - "LastDragon_ru\\LaraASP\\Serializer\\Provider" + "LastDragon_ru\\LaraASP\\Serializer\\Provider", + "LastDragon_ru\\LaraASP\\Documentator\\Provider" ] } }, @@ -178,6 +181,7 @@ "lastdragon-ru/lara-asp-spa": "self.version", "lastdragon-ru/lara-asp-graphql": "self.version", "lastdragon-ru/lara-asp-graphql-printer": "self.version", - "lastdragon-ru/lara-asp-serializer": "self.version" + "lastdragon-ru/lara-asp-serializer": "self.version", + "lastdragon-ru/lara-asp-documentator": "self.version" } } diff --git a/packages/documentator/CHANGELOG.md b/packages/documentator/CHANGELOG.md new file mode 100644 index 000000000..8a5f44710 --- /dev/null +++ b/packages/documentator/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +Please see [Releases](https://github.com/LastDragon-ru/lara-asp/releases). diff --git a/packages/documentator/LICENSE b/packages/documentator/LICENSE new file mode 100644 index 000000000..e970b27ef --- /dev/null +++ b/packages/documentator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Aleksei Lebedev + +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. diff --git a/packages/documentator/README.md b/packages/documentator/README.md new file mode 100644 index 000000000..29dd8798c --- /dev/null +++ b/packages/documentator/README.md @@ -0,0 +1,7 @@ +# The Documentator + +> This package is the part of Awesome Set of Packages for Laravel. +> +> [Read more](https://github.com/LastDragon-ru/lara-asp). + +This package provides various utilities for documentation generation. diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json new file mode 100644 index 000000000..7aa5444af --- /dev/null +++ b/packages/documentator/composer.json @@ -0,0 +1,48 @@ +{ + "name": "lastdragon-ru/lara-asp-documentator", + "homepage": "https://github.com/LastDragon-ru/lara-asp", + "description": "The Awesome Set of Packages for Laravel - The Documentator.", + "readme": "README.md", + "license": "MIT", + "type": "library", + "keywords": [ + "documetation", + "utils", + "laravel-package", + "laravel", + "php" + ], + "support": { + "issues": "https://github.com/LastDragon-ru/lara-asp/issues", + "source": "https://github.com/LastDragon-ru/lara-asp", + "forum": "https://github.com/LastDragon-ru/lara-asp/discussions" + }, + "require": { + "php": "^8.1|^8.2", + "laravel/framework": "^9.21.0|^10.0.0" + }, + "require-dev": { + "lastdragon-ru/lara-asp-testing": "self.version", + "orchestra/testbench": "^7.0.0|^8.0.0", + "phpunit/phpunit": "^9.5.0|^10.1.0" + }, + "autoload": { + "psr-4": { + "LastDragon_ru\\LaraASP\\Documentator\\": "src/" + }, + "exclude-from-classmap": [ + "src/**Test.php" + ] + }, + "extra": { + "laravel": { + "providers": [ + "LastDragon_ru\\LaraASP\\Documentator\\Provider" + ] + } + }, + "config": { + "sort-packages": true, + "optimize-autoloader": true + } +} diff --git a/packages/documentator/metadata.json b/packages/documentator/metadata.json new file mode 100644 index 000000000..1be1b18fe --- /dev/null +++ b/packages/documentator/metadata.json @@ -0,0 +1,3 @@ +{ + "version": "0.0.0" +} diff --git a/packages/documentator/phpunit.xml b/packages/documentator/phpunit.xml new file mode 100644 index 000000000..34fd5cfb9 --- /dev/null +++ b/packages/documentator/phpunit.xml @@ -0,0 +1,29 @@ + + + + + ./src + + + + + ./src + + + ./src + + + + + + + + + + + + diff --git a/packages/documentator/src/Package.php b/packages/documentator/src/Package.php new file mode 100644 index 000000000..a3e040924 --- /dev/null +++ b/packages/documentator/src/Package.php @@ -0,0 +1,7 @@ + ./packages/serializer/src + + ./packages/documentator/src + @@ -49,6 +52,7 @@ ./packages/graphql/src ./packages/graphql-printer/src ./packages/serializer/src + ./packages/documentator/src ./packages/core/src @@ -61,6 +65,7 @@ ./packages/graphql/src ./packages/graphql-printer/src ./packages/serializer/src + ./packages/documentator/src From 89d4d696673870ca765a6f6ae68b1d19d2119e60 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:10:28 +0400 Subject: [PATCH 02/33] PHPUnit: test suit names fix. --- phpunit.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index b0e41dd5d..55f87ad78 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,37 +6,37 @@ executionOrder="random" colors="true"> - + ./packages/core/src - + ./packages/migrator/src - + ./packages/queue/src - + ./packages/testing/src - + ./packages/eloquent/src - + ./packages/formatter/src - + ./packages/spa/src - + ./packages/graphql/src - + ./packages/graphql-printer/src - + ./packages/serializer/src - + ./packages/documentator/src From 1aabea095e5cc3ab27329030c5d8b502a859ca2c Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:46:40 +0400 Subject: [PATCH 03/33] Minimal setup to run `artisan`. --- .gitignore | 2 + artisan | 38 ++++++++++++ config/app.php | 162 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100755 artisan create mode 100644 config/app.php diff --git a/.gitignore b/.gitignore index 68e5d56fa..457e35ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ Thumbs.db # Project composer.lock Vagrant.yml +bootstrap +storage diff --git a/artisan b/artisan new file mode 100755 index 000000000..8ba7a81ed --- /dev/null +++ b/artisan @@ -0,0 +1,38 @@ +#!/usr/bin/env php +singleton( + ConsoleKernelContract::class, + ConsoleKernel::class, +); +$app->singleton( + ExceptionHandlerContract::class, + ExceptionHandler::class +); + +// Run +$input = new ArgvInput(); +$kernel = $app->make(ConsoleKernelContract::class); +$status = $kernel->handle($input, new ConsoleOutput()); + +// Shutdown +$kernel->terminate($input, $status); + +exit($status); diff --git a/config/app.php b/config/app.php new file mode 100644 index 000000000..10dc6eb49 --- /dev/null +++ b/config/app.php @@ -0,0 +1,162 @@ + env('APP_NAME', 'LaraAsp'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + 'url' => env('APP_URL', 'http://localhost'), + 'asset_url' => env('ASSET_URL'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + 'locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Faker Locale + |-------------------------------------------------------------------------- + | + | This locale will be used by the Faker PHP library when generating fake + | data for your database seeds. For example, this will be used to get + | localized telephone numbers, street address information and more. + | + */ + 'faker_locale' => 'en_US', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + 'key' => env('APP_KEY'), + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + 'maintenance' => [ + 'driver' => 'file', + // 'store' => 'redis', + ], + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + 'providers' => ServiceProvider::defaultProviders()->merge([ + DocumentatorProvider::class, + ])->toArray(), + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + 'aliases' => Facade::defaultAliases()->merge([ + // 'Example' => App\Facades\Example::class, + ])->toArray(), +]; From 0df24b5dbb9d5063aeb78e4e4492cd608c7257bb Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 18 Aug 2023 13:08:29 +0400 Subject: [PATCH 04/33] ci: version will be dumped into root `metadata.json` too. --- .release-it/config.js | 2 +- metadata.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 metadata.json diff --git a/.release-it/config.js b/.release-it/config.js index 8e0f2ec32..c4f155f61 100644 --- a/.release-it/config.js +++ b/.release-it/config.js @@ -146,7 +146,7 @@ module.exports = { plugins: { '@release-it/bumper': { out: { - file: 'packages/*/metadata.json', + file: ['metadata.json', 'packages/*/metadata.json'], }, }, '@release-it/conventional-changelog': { diff --git a/metadata.json b/metadata.json new file mode 100644 index 000000000..4e2c1114d --- /dev/null +++ b/metadata.json @@ -0,0 +1,3 @@ +{ + "version": "0.15.0" +} From a86500c37878775c267b65fa68c2675860e7f6f3 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 18 Aug 2023 18:26:36 +0400 Subject: [PATCH 05/33] `Version` helper. --- packages/documentator/composer.json | 2 + .../src/Testing/Package/TestCase.php | 20 ++++++ packages/documentator/src/Utils/Version.php | 63 +++++++++++++++++++ .../documentator/src/Utils/VersionTest.php | 40 ++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 packages/documentator/src/Testing/Package/TestCase.php create mode 100644 packages/documentator/src/Utils/Version.php create mode 100644 packages/documentator/src/Utils/VersionTest.php diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index 7aa5444af..c9216ea98 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -19,6 +19,8 @@ }, "require": { "php": "^8.1|^8.2", + "ext-mbstring": "*", + "composer/semver": "^3.2", "laravel/framework": "^9.21.0|^10.0.0" }, "require-dev": { diff --git a/packages/documentator/src/Testing/Package/TestCase.php b/packages/documentator/src/Testing/Package/TestCase.php new file mode 100644 index 000000000..413565e85 --- /dev/null +++ b/packages/documentator/src/Testing/Package/TestCase.php @@ -0,0 +1,20 @@ +0|[1-9]\d*) + \. + (?P0|[1-9]\d*) + \. + (?P0|[1-9]\d*) + (?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) + (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* + ))? + (?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + /x + SEMVER; + + public static function isVersion(string $string): bool { + return static::isSemver($string) + || (str_starts_with($string, 'v') && static::isSemver(static::normalize($string))); + } + + public static function isSemver(string $string): bool { + return (bool) preg_match(static::SEMVER, $string); + } + + public static function compare(string $a, string $b): int { + $r = 0; + + if ($a !== $b) { + $a = static::normalize($a); + $b = static::normalize($b); + $r = $a !== $b + ? (Comparator::greaterThan($a, $b) ? 1 : -1) + : 0; + } + + return $r; + } + + public static function normalize(string $version): string { + if (str_starts_with($version, 'v')) { + $version = mb_substr($version, 1); + } elseif (str_starts_with($version, 'dev-')) { + $version = '9999999-dev'; + } else { + // empty + } + + return $version; + } +} diff --git a/packages/documentator/src/Utils/VersionTest.php b/packages/documentator/src/Utils/VersionTest.php new file mode 100644 index 000000000..652daf5c0 --- /dev/null +++ b/packages/documentator/src/Utils/VersionTest.php @@ -0,0 +1,40 @@ + Date: Sat, 19 Aug 2023 09:07:16 +0400 Subject: [PATCH 06/33] `Process` & `Git` helpers. --- composer.json | 1 + packages/documentator/composer.json | 3 +- packages/documentator/src/Utils/Git.php | 37 +++++++++++++++++++++ packages/documentator/src/Utils/Process.php | 29 ++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/documentator/src/Utils/Git.php create mode 100644 packages/documentator/src/Utils/Process.php diff --git a/composer.json b/composer.json index 6089e3244..4a0e268b8 100644 --- a/composer.json +++ b/composer.json @@ -55,6 +55,7 @@ "symfony/finder": "^6.3.0", "symfony/http-foundation": "^6.3.0", "symfony/mime": "^6.3.0", + "symfony/process": "^6.3.0", "symfony/property-access": "^6.3.0", "symfony/property-info": "^6.3.0", "symfony/psr-http-message-bridge": "^2.0.0", diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index c9216ea98..7e6a724ae 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -21,7 +21,8 @@ "php": "^8.1|^8.2", "ext-mbstring": "*", "composer/semver": "^3.2", - "laravel/framework": "^9.21.0|^10.0.0" + "laravel/framework": "^9.21.0|^10.0.0", + "symfony/process": "^6.3.0" }, "require-dev": { "lastdragon-ru/lara-asp-testing": "self.version", diff --git a/packages/documentator/src/Utils/Git.php b/packages/documentator/src/Utils/Git.php new file mode 100644 index 000000000..efa47592a --- /dev/null +++ b/packages/documentator/src/Utils/Git.php @@ -0,0 +1,37 @@ + + */ + public function getTags(callable $filter = null, string $root = null): array { + $tags = $this->process->run(['git', 'tag', '--list'], $root); + $tags = explode("\n", $tags); + $tags = $filter ? array_filter($tags, $filter) : $tags; + $tags = array_values($tags); + + return $tags; + } + + public function getFile(string $path, string $revision = 'HEAD', string $root = null): string { + return $this->process->run(['git', 'show', "{$revision}:{$path}"], $root); + } + + public function getBranch(string $root = null): string { + return $this->process->run(['git', 'rev-parse', '--abbrev-ref=HEAD'], $root); + } +} diff --git a/packages/documentator/src/Utils/Process.php b/packages/documentator/src/Utils/Process.php new file mode 100644 index 000000000..c41df8e0d --- /dev/null +++ b/packages/documentator/src/Utils/Process.php @@ -0,0 +1,29 @@ + $command + */ + public function run(array $command, string $cwd = null): string { + $process = new SymfonyProcess($command, $cwd); + + $process->run(); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return trim($process->getOutput()); + } +} From d4b427cd2fb4e90858c3667e91ae0cc277a39959 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 19 Aug 2023 09:16:00 +0400 Subject: [PATCH 07/33] Classes to cache metadata. --- packages/documentator/composer.json | 3 +- .../documentator/src/Metadata/Metadata.php | 20 +++++ .../documentator/src/Metadata/Storage.php | 80 +++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 packages/documentator/src/Metadata/Metadata.php create mode 100644 packages/documentator/src/Metadata/Storage.php diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index 7e6a724ae..707dbdd00 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -22,7 +22,8 @@ "ext-mbstring": "*", "composer/semver": "^3.2", "laravel/framework": "^9.21.0|^10.0.0", - "symfony/process": "^6.3.0" + "symfony/process": "^6.3.0", + "lastdragon-ru/lara-asp-serializer": "self.version" }, "require-dev": { "lastdragon-ru/lara-asp-testing": "self.version", diff --git a/packages/documentator/src/Metadata/Metadata.php b/packages/documentator/src/Metadata/Metadata.php new file mode 100644 index 000000000..94749a09e --- /dev/null +++ b/packages/documentator/src/Metadata/Metadata.php @@ -0,0 +1,20 @@ +>> $requirements + */ + public function __construct( + public string $version = '0.0.0', + public array $requirements = [], + ) { + // empty + } +} diff --git a/packages/documentator/src/Metadata/Storage.php b/packages/documentator/src/Metadata/Storage.php new file mode 100644 index 000000000..f3d738389 --- /dev/null +++ b/packages/documentator/src/Metadata/Storage.php @@ -0,0 +1,80 @@ +path}/metadata.json"; + } + + public function load(): Metadata { + $metadata = is_file($this->getPath()) ? file_get_contents($this->getPath()) : false; + $metadata = $metadata !== false + ? $this->serializer->deserialize(Metadata::class, $metadata, self::Format) + : new Metadata(); + + return $metadata; + } + + public function save(Metadata $metadata): bool { + $metadata = $this->normalize($metadata); + $context = (new JsonEncoderContextBuilder())->withEncodeOptions(self::Options)->toArray(); + $content = $this->serializer->serialize($metadata, self::Format, $context); + $result = file_put_contents($this->getPath(), $content) !== false; + + return $result; + } + + protected function normalize(Metadata $metadata): Metadata { + $comparator = static fn($a, $b) => -Version::compare($a, $b); + + uksort($metadata->requirements, $comparator); + + foreach ($metadata->requirements as &$requirement) { + uksort($requirement, strcmp(...)); + + foreach ($requirement as &$versions) { + usort($versions, $comparator); + } + } + + return $metadata; + } +} From ed0d61d89f3fb5cf86eaaf19cefd463e7fd28061 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 19 Aug 2023 11:48:24 +0400 Subject: [PATCH 08/33] Artisan app setup fix. --- .gitignore | 1 - config/view.php | 32 ++++++++++++++++++++++++++++++ storage/framework/views/.gitignore | 2 ++ storage/logs/.gitignore | 2 ++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 config/view.php create mode 100644 storage/framework/views/.gitignore create mode 100644 storage/logs/.gitignore diff --git a/.gitignore b/.gitignore index 457e35ad5..b70ecd80f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,3 @@ Thumbs.db composer.lock Vagrant.yml bootstrap -storage diff --git a/config/view.php b/config/view.php new file mode 100644 index 000000000..1143b9220 --- /dev/null +++ b/config/view.php @@ -0,0 +1,32 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')), + ), +]; diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 2842250b5f6b10c43cb8f30fd98931438dd36049 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Sat, 19 Aug 2023 14:46:24 +0400 Subject: [PATCH 09/33] feat(documentator): Command `lara-asp-documentator:requirements` to generate a table with the required versions of PHP/Laravel in Markdown format. --- config/app.php | 2 + metadata.json | 414 +++++++++++++++++- packages/documentator/composer.json | 1 + .../views/requirements/markdown.blade.php | 15 + .../src/Commands/Requirements.php | 208 +++++++++ .../src/Commands/RequirementsTest.json | 413 +++++++++++++++++ .../src/Commands/RequirementsTest.php | 102 +++++ .../documentator/src/Metadata/Storage.php | 1 + packages/documentator/src/Provider.php | 15 +- .../src/Testing/Package/TestCase.php | 2 + packages/documentator/src/Utils/Version.php | 2 +- phpcs.xml | 1 + 12 files changed, 1172 insertions(+), 4 deletions(-) create mode 100644 packages/documentator/resources/views/requirements/markdown.blade.php create mode 100644 packages/documentator/src/Commands/Requirements.php create mode 100644 packages/documentator/src/Commands/RequirementsTest.json create mode 100644 packages/documentator/src/Commands/RequirementsTest.php diff --git a/config/app.php b/config/app.php index 10dc6eb49..3db8236ad 100644 --- a/config/app.php +++ b/config/app.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Facade; use Illuminate\Support\ServiceProvider; use LastDragon_ru\LaraASP\Documentator\Provider as DocumentatorProvider; +use LastDragon_ru\LaraASP\Serializer\Provider as SerializerProvider; return [ /* @@ -143,6 +144,7 @@ | */ 'providers' => ServiceProvider::defaultProviders()->merge([ + SerializerProvider::class, DocumentatorProvider::class, ])->toArray(), diff --git a/metadata.json b/metadata.json index 4e2c1114d..8908753f7 100644 --- a/metadata.json +++ b/metadata.json @@ -1,3 +1,413 @@ { - "version": "0.15.0" -} + "version": "0.15.0", + "requirements": { + "HEAD": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1" + ] + }, + "5.0.0-beta.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1" + ] + }, + "4.5.2": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.5.1": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.5.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.4.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.3.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.2.1": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.2.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.1.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.0.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "3.0.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.1.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.3": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.2": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "1.1.2": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.1.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.1.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.4": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.3": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.2": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.15.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.14.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.14.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.13.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.12.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.11.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.10.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.9.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.8.1": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.8.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.7.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.6.1": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.6.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.5.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.4.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.3.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=7.4.0" + ] + }, + "0.2.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=7.4.0" + ] + }, + "0.1.0": { + "laravel/framework": [ + "^8.0" + ], + "php": [ + ">=7.4.0" + ] + } + } +} \ No newline at end of file diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index 707dbdd00..9659555cc 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -23,6 +23,7 @@ "composer/semver": "^3.2", "laravel/framework": "^9.21.0|^10.0.0", "symfony/process": "^6.3.0", + "lastdragon-ru/lara-asp-core": "self.version", "lastdragon-ru/lara-asp-serializer": "self.version" }, "require-dev": { diff --git a/packages/documentator/resources/views/requirements/markdown.blade.php b/packages/documentator/resources/views/requirements/markdown.blade.php new file mode 100644 index 000000000..861e6e892 --- /dev/null +++ b/packages/documentator/resources/views/requirements/markdown.blade.php @@ -0,0 +1,15 @@ + $packages Package name (key) and title (value) + * @var array>> $requirements + */ + +?> +| Requirement | Constraint | Supported by | +|--------------|---------------------|------------------| +@foreach ($packages as $key => $title) +@foreach ($requirements[$key] as $constraint => $versions) +| @if ($loop->first) {{$title}} @endif | `{!!$constraint!!}` | @foreach($versions as $version) @if(is_string($version))`{{$version}}` @else `{{$version[0]}} ⋯ {{$version[1]}}` @endif @if (!$loop->last), @endif +@endforeach | +@endforeach +@endforeach diff --git a/packages/documentator/src/Commands/Requirements.php b/packages/documentator/src/Commands/Requirements.php new file mode 100644 index 000000000..aedd8b4bf --- /dev/null +++ b/packages/documentator/src/Commands/Requirements.php @@ -0,0 +1,208 @@ +argument('cwd') ?? getcwd()); + $tags = $git->getTags(Version::isVersion(...), $cwd); + $tags[] = 'HEAD'; + + // Collect requirements + $storage = new Storage($serializer, $cwd); + $metadata = $storage->load(); + $packages = [ + 'php' => 'PHP', + 'laravel/framework' => 'Laravel', + ]; + + foreach ($tags as $tag) { + // Cached? + if ($tag !== 'HEAD' && array_keys($metadata->requirements[$tag] ?? []) === array_keys($packages)) { + continue; + } + + // Load + $package = $this->getPackageInfo($git, $tag, $cwd); + + if (!$package) { + continue; + } + + // Update + $metadata->requirements[$tag] = []; + + foreach ($packages as $key => $title) { + $metadata->requirements[$tag][$key] = explode('|', Cast::toString($package['require'][$key] ?? '')); + } + } + + // Cleanup + $metadata->requirements = array_intersect_key( + $metadata->requirements, + array_fill_keys($tags, null), + ); + + // Save + $storage->save($metadata); + + // Prepare + $requirements = $this->getRequirements($packages, $metadata); + + // Render + $package = Package::Name; + $output = view("{$package}::requirements.markdown", [ + 'packages' => $packages, + 'requirements' => $requirements, + ])->render(); + + $this->output->writeln($output); + } + + /** + * @return array|null + */ + protected function getPackageInfo(Git $git, string $tag, string $cwd): ?array { + try { + $package = $git->getFile('composer.json', $tag, $cwd); + $package = json_decode($package, true, flags: JSON_THROW_ON_ERROR); + + assert(is_array($package)); + } catch (Exception) { + $package = null; + } + + return $package; + } + + /** + * @param array $packages + * + * @return array>> + */ + protected function getRequirements(array $packages, Metadata $metadata): array { + // Extract + $requirements = []; + + foreach ($metadata->requirements as $versionName => $versionPackages) { + foreach ($versionPackages as $packageName => $packageVersions) { + foreach ($packageVersions as $packageVersion) { + $requirements[$packageName][$packageVersion] ??= []; + $requirements[$packageName][$packageVersion][] = $versionName; + } + } + } + + // Sort + $priorities = array_combine(array_keys($packages), range(0, count($packages) - 1)); + + uksort($requirements, static function (string $a, string $b) use ($priorities): int { + return $priorities[$a] <=> $priorities[$b]; + }); + + // Merge + $versions = array_keys($metadata->requirements); + + foreach ($requirements as &$packageVersions) { + foreach ($packageVersions as &$versionNames) { + $versionNames = $this->getMergedVersions($versions, $versionNames); + } + } + + // Return + return $requirements; + } + + /** + * @param list $versions + * @param list $merge + * + * @return list + */ + protected function getMergedVersions(array $versions, array $merge): array { + // Group + $ranges = []; + $current = []; + + foreach ($merge as $key => $version) { + $index = array_search($version, $versions, true); + $nextMerge = $merge[$key + 1] ?? null; + $nextVersion = $index !== false ? ($versions[$index + 1] ?? null) : null; + + $current[] = $version; + + if ($nextMerge !== $nextVersion) { + $ranges[] = $current; + $current = []; + } + } + + if ($current) { + $ranges[] = $current; + } + + // Merge + $merged = []; + + foreach ($ranges as $range) { + if (count($range) > 2) { + $merged[] = [reset($range), end($range)]; + } elseif (count($range) === 2) { + $merged[] = reset($range); + $merged[] = end($range); + } else { + $merged[] = reset($range); + } + } + + return $merged; + } +} diff --git a/packages/documentator/src/Commands/RequirementsTest.json b/packages/documentator/src/Commands/RequirementsTest.json new file mode 100644 index 000000000..2a786e3b0 --- /dev/null +++ b/packages/documentator/src/Commands/RequirementsTest.json @@ -0,0 +1,413 @@ +{ + "version": "0.15.0", + "requirements": { + "HEAD": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1" + ] + }, + "5.0.0-beta.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1" + ] + }, + "4.5.2": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.5.1": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.5.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.4.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.3.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.2.1": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.2.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.1.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "4.0.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "3.0.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.1.0": { + "laravel/framework": [ + "^10.0.0", + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.3": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.2": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "2.0.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.2", + "^8.1", + "^8.0" + ] + }, + "1.1.2": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.1.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.1.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.4": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.3": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.2": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "1.0.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.15.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.14.1": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.14.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.13.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.12.0": { + "laravel/framework": [ + "^9.0.0", + "^8.22.1" + ], + "php": [ + "^8.0.0" + ] + }, + "0.11.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.10.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.9.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.8.1": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.8.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.7.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.6.1": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.6.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.5.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.4.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=8.0.0" + ] + }, + "0.3.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=7.4.0" + ] + }, + "0.2.0": { + "laravel/framework": [ + "^8.22.1" + ], + "php": [ + ">=7.4.0" + ] + }, + "0.1.0": { + "laravel/framework": [ + "^8.0" + ], + "php": [ + ">=7.4.0" + ] + } + } +} diff --git a/packages/documentator/src/Commands/RequirementsTest.php b/packages/documentator/src/Commands/RequirementsTest.php new file mode 100644 index 000000000..d65abfd45 --- /dev/null +++ b/packages/documentator/src/Commands/RequirementsTest.php @@ -0,0 +1,102 @@ +getMergedVersions($versions, $versions), + ); + self::assertEquals( + [ + '0.1.0', + ], + $command->getMergedVersions($versions, ['0.1.0']), + ); + self::assertEquals( + [ + '0.1.0', + '0.3.0-beta.1', + '1.1.0', + ], + $command->getMergedVersions($versions, ['0.1.0', '0.3.0-beta.1', '1.1.0']), + ); + self::assertEquals( + [ + ['0.1.0', '0.3.0-beta.0'], + '1.0.0', + '1.2.1-rc.0', + '1.2.1', + ], + $command->getMergedVersions($versions, ['0.1.0', '0.2.0', '0.3.0-beta.0', '1.0.0', '1.2.1-rc.0', '1.2.1']), + ); + } + + public function testGetRequirements(): void { + $metadata = self::getTestData()->content('.json'); + $metadata = $this->app->make(Serializer::class)->deserialize(Metadata::class, $metadata); + $packages = [ + 'laravel/framework' => 'Laravel', + 'php' => 'PHP', + ]; + $command = new class() extends Requirements { + /** + * @inheritDoc + */ + public function getRequirements(array $packages, Metadata $metadata): array { + return parent::getRequirements($packages, $metadata); + } + }; + + self::assertEquals( + [ + 'laravel/framework' => [ + '^10.0.0' => [['HEAD', '2.1.0']], + '^9.0.0' => [['HEAD', '0.12.0']], + '^8.22.1' => [['3.0.0', '0.2.0']], + '^8.0' => ['0.1.0'], + ], + 'php' => [ + '^8.2' => [['HEAD', '2.0.0']], + '^8.1' => [['HEAD', '2.0.0']], + '^8.0' => [['4.5.2', '2.0.0']], + '^8.0.0' => [['1.1.2', '0.12.0']], + '>=8.0.0' => [['0.11.0', '0.4.0']], + '>=7.4.0' => [['0.3.0', '0.1.0']], + ], + ], + $command->getRequirements($packages, $metadata), + ); + } +} diff --git a/packages/documentator/src/Metadata/Storage.php b/packages/documentator/src/Metadata/Storage.php index f3d738389..655116c8f 100644 --- a/packages/documentator/src/Metadata/Storage.php +++ b/packages/documentator/src/Metadata/Storage.php @@ -9,6 +9,7 @@ use function file_get_contents; use function file_put_contents; use function is_file; +use function strcmp; use function uksort; use function usort; diff --git a/packages/documentator/src/Provider.php b/packages/documentator/src/Provider.php index e4490b647..b748d29e8 100644 --- a/packages/documentator/src/Provider.php +++ b/packages/documentator/src/Provider.php @@ -3,7 +3,20 @@ namespace LastDragon_ru\LaraASP\Documentator; use Illuminate\Support\ServiceProvider; +use LastDragon_ru\LaraASP\Core\Concerns\ProviderWithViews; +use LastDragon_ru\LaraASP\Documentator\Commands\Requirements; class Provider extends ServiceProvider { - // empty + use ProviderWithViews; + + public function boot(): void { + $this->bootViews(); + $this->commands( + Requirements::class, + ); + } + + protected function getName(): string { + return Package::Name; + } } diff --git a/packages/documentator/src/Testing/Package/TestCase.php b/packages/documentator/src/Testing/Package/TestCase.php index 413565e85..c1e2d233b 100644 --- a/packages/documentator/src/Testing/Package/TestCase.php +++ b/packages/documentator/src/Testing/Package/TestCase.php @@ -3,6 +3,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Testing\Package; use LastDragon_ru\LaraASP\Documentator\Provider; +use LastDragon_ru\LaraASP\Serializer\Provider as SerializerProvider; use LastDragon_ru\LaraASP\Testing\Package\TestCase as PackageTestCase; /** @@ -15,6 +16,7 @@ class TestCase extends PackageTestCase { protected function getPackageProviders(mixed $app): array { return [ Provider::class, + SerializerProvider::class, ]; } } diff --git a/packages/documentator/src/Utils/Version.php b/packages/documentator/src/Utils/Version.php index 4bbc36b92..74685c34d 100644 --- a/packages/documentator/src/Utils/Version.php +++ b/packages/documentator/src/Utils/Version.php @@ -52,7 +52,7 @@ public static function compare(string $a, string $b): int { public static function normalize(string $version): string { if (str_starts_with($version, 'v')) { $version = mb_substr($version, 1); - } elseif (str_starts_with($version, 'dev-')) { + } elseif ($version === 'HEAD' || str_starts_with($version, 'dev-')) { $version = '9999999-dev'; } else { // empty diff --git a/phpcs.xml b/phpcs.xml index 938c2321b..470c87b1b 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -5,6 +5,7 @@ Lara Asp coding standard, based on PSR12 ./packages + *.blade.php From 4e67b4adbf15f8b17dcaf9493a28e7d0d85d3c86 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Mon, 21 Aug 2023 13:36:51 +0400 Subject: [PATCH 10/33] `Preprocessor`. --- packages/documentator/composer.json | 1 + .../Exceptions/PreprocessFailed.php | 9 ++ .../src/Preprocessor/Instruction.php | 9 ++ .../Instructions/IncludeCommand.php | 36 +++++ .../Instructions/IncludeCommandTest.php | 32 +++++ .../Instructions/IncludeExample.php | 97 +++++++++++++ .../Instructions/IncludeExampleTest.php | 108 +++++++++++++++ .../IncludeExampleTest~example.md | 3 + .../IncludeExampleTest~runnable.md | 3 + .../IncludeExampleTest~runnable.run | 1 + .../Preprocessor/Instructions/IncludeFile.php | 36 +++++ .../Instructions/IncludeFileTest.md | 3 + .../Instructions/IncludeFileTest.php | 33 +++++ .../src/Preprocessor/Preprocessor.php | 130 ++++++++++++++++++ .../src/Preprocessor/PreprocessorTest.php | 62 +++++++++ 15 files changed, 563 insertions(+) create mode 100644 packages/documentator/src/Preprocessor/Exceptions/PreprocessFailed.php create mode 100644 packages/documentator/src/Preprocessor/Instruction.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeExample.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~example.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.run create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeFile.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php create mode 100644 packages/documentator/src/Preprocessor/Preprocessor.php create mode 100644 packages/documentator/src/Preprocessor/PreprocessorTest.php diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index 9659555cc..b26a2ff62 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -22,6 +22,7 @@ "ext-mbstring": "*", "composer/semver": "^3.2", "laravel/framework": "^9.21.0|^10.0.0", + "symfony/filesystem": "^6.3.0", "symfony/process": "^6.3.0", "lastdragon-ru/lara-asp-core": "self.version", "lastdragon-ru/lara-asp-serializer": "self.version" diff --git a/packages/documentator/src/Preprocessor/Exceptions/PreprocessFailed.php b/packages/documentator/src/Preprocessor/Exceptions/PreprocessFailed.php new file mode 100644 index 000000000..3859397c7 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Exceptions/PreprocessFailed.php @@ -0,0 +1,9 @@ +process->run([$target], $path); + } catch (Exception $exception) { + throw new PreprocessFailed( + sprintf( + 'Failed to execute command `%s`.', + $target, + ), + $exception, + ); + } + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php new file mode 100644 index 000000000..e84ef473d --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php @@ -0,0 +1,32 @@ +shouldReceive('run') + ->with([$command], $path) + ->once() + ->andReturn($expected); + + $instance = $this->app->make(IncludeCommand::class, [ + 'process' => $process, + ]); + + self::assertEquals($expected, $instance->process($path, $command)); + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php new file mode 100644 index 000000000..4a8f5fe97 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -0,0 +1,97 @@ +getLanguage($path, $target); + $content = trim(parent::process($path, $target)); + $content = <<getCommand($path, $target); + + if ($command) { + try { + $output = $this->process->run($command, $path); + } catch (Exception $exception) { + throw new PreprocessFailed( + sprintf( + 'Failed to execute command `%s`.', + implode(' ', $command), + ), + $exception, + ); + } + + if (preg_match_all('/\R/u', $output) > static::Limit) { + $output = <<Output + + ```plain + $output + ``` + + + CODE; + } else { + $output = << + */ + protected function getCommand(string $path, string $target): ?array { + $info = pathinfo($target); + $file = isset($info['dirname']) + ? "{$info['dirname']}/{$info['filename']}.run" + : "{$info['filename']}.run"; + $path = (new Filesystem())->isAbsolutePath($file) ? $file : "{$path}/{$file}"; + $command = is_file($path) ? [$file] : null; + + return $command; + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php new file mode 100644 index 000000000..55b405c42 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php @@ -0,0 +1,108 @@ +path('~example.md')); + $file = basename(self::getTestData()->path('~example.md')); + $expected = trim(self::getTestData()->content('~example.md')); + $process = Mockery::mock(Process::class); + $process + ->shouldReceive('run') + ->never(); + + $instance = $this->app->make(IncludeExample::class, [ + 'process' => $process, + ]); + + self::assertEquals( + <<process($path, $file), + ); + } + + public function testProcess(): void { + $path = dirname(self::getTestData()->path('~runnable.md')); + $file = self::getTestData()->path('~runnable.md'); + $expected = trim(self::getTestData()->content('~runnable.md')); + $output = 'command output'; + $process = Mockery::mock(Process::class); + $process + ->shouldReceive('run') + ->with([self::getTestData()->path('~runnable.run')], $path) + ->once() + ->andReturn($output); + + $instance = $this->app->make(IncludeExample::class, [ + 'process' => $process, + ]); + + self::assertEquals( + <<process($path, $file), + ); + } + + public function testProcessLongOutput(): void { + $path = dirname(self::getTestData()->path('~runnable.md')); + $file = self::getTestData()->path('~runnable.md'); + $expected = trim(self::getTestData()->content('~runnable.md')); + $output = implode("\n", range(0, IncludeExample::Limit + 1)); + $process = Mockery::mock(Process::class); + $process + ->shouldReceive('run') + ->with([self::getTestData()->path('~runnable.run')], $path) + ->once() + ->andReturn($output); + + $instance = $this->app->make(IncludeExample::class, [ + 'process' => $process, + ]); + + self::assertEquals( + <<Output + + ```plain + {$output} + ``` + + + EXPECTED, + $instance->process($path, $file), + ); + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~example.md b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~example.md new file mode 100644 index 000000000..a6886c0f7 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~example.md @@ -0,0 +1,3 @@ +# File + +content of the file diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.md b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.md new file mode 100644 index 000000000..a6886c0f7 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.md @@ -0,0 +1,3 @@ +# File + +content of the file diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.run b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.run new file mode 100644 index 000000000..8eca9947a --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest~runnable.run @@ -0,0 +1 @@ +/command diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php new file mode 100644 index 000000000..d80acd811 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php @@ -0,0 +1,36 @@ +isAbsolutePath($target) ? $target : "{$path}/{$target}"; + $content = file_get_contents($file); + + if ($content === false) { + throw new PreprocessFailed( + sprintf( + 'Failed to include `%s`.', + $target, + ), + ); + } + + return $content; + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.md b/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.md new file mode 100644 index 000000000..a6886c0f7 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.md @@ -0,0 +1,3 @@ +# File + +content of the file diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php new file mode 100644 index 000000000..0d7840f56 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php @@ -0,0 +1,33 @@ +path('.md')); + $file = basename(self::getTestData()->path('.md')); + $expected = self::getTestData()->content('.md'); + $instance = $this->app->make(IncludeFile::class); + + self::assertEquals($expected, $instance->process($path, $file)); + } + + public function testProcessAbsolute(): void { + $path = 'invalid/directory'; + $file = self::getTestData()->path('.md'); + $expected = self::getTestData()->content('.md'); + $instance = $this->app->make(IncludeFile::class); + + self::assertEquals($expected, $instance->process($path, $file)); + } +} diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php new file mode 100644 index 000000000..1f6d638e3 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -0,0 +1,130 @@ + "") + * + * Supported instructions: + * + * | `` | `` | Description | + * |-------------------|--------------------------------|----------------------------------------------------------| + * | `include:file` | path to the file | Include content of the file as is. | + * | `include:command` | the command to execute | Execute the command and include output. | + * | `include:example` | path to the example file | Include file in the code block + its output if possible. | + * + * Limitations: + * - `` will be processed everywhere in the file (eg within the code + * block) and may give unpredictable results. + * - `` cannot be inside text. + * - Nested `` doesn't supported. + */ +class Preprocessor { + protected const Warning = 'Generated automatically. Do not edit.'; + protected const Regexp = <<<'REGEXP' + /^ + (?P\[[^]]*?\]\((?P(?P.+?)\s+\"(?P[^"]+?)\")\)) + (?P\ + + {$content} + + + RESULT; + } else { + $content = $matches['preprocess']; + } + + return $content; + }, + subject : $string, + flags : PREG_UNMATCHED_AS_NULL, + ); + + if ($result === null) { + throw new PreprocessFailed('Unexpected error.'); + } + + return $result; + } + + protected function getHash(string $identifier): string { + return sha1($identifier); + } +} diff --git a/packages/documentator/src/Preprocessor/PreprocessorTest.php b/packages/documentator/src/Preprocessor/PreprocessorTest.php new file mode 100644 index 000000000..b97bddb41 --- /dev/null +++ b/packages/documentator/src/Preprocessor/PreprocessorTest.php @@ -0,0 +1,62 @@ + + + outdated + + + MARKDOWN; + $instruction = Mockery::mock(Instruction::class); + $instruction + ->shouldReceive('getName') + ->once() + ->andReturn('test'); + $instruction + ->shouldReceive('process') + ->with('path', './path/to/file') + ->once() + ->andReturn('content'); + + $preprocessor = (new Preprocessor()) + ->addInstruction($instruction); + + self::assertEquals( + <<<'MARKDOWN' + Bla bla bla [Link](./path/to/file "test") should be ignored. + + [Link](./path/to/file "unknown") + + [Link](./path/to/file "test") + + content + + + + [Link](./path/to/file "test") + + content + + + MARKDOWN, + $preprocessor->process('path', $content), + ); + } +} From 0fa1e6ef60055c18c38e6e65ed7fb4d07be8e3f5 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:37:04 +0400 Subject: [PATCH 11/33] feat(documentator): Command `lara-asp-documentator:preprocess` to preprocess Markdown files. --- packages/documentator/composer.json | 1 + .../documentator/src/Commands/Preprocess.php | 55 +++++++++++++++++++ packages/documentator/src/Provider.php | 2 + 3 files changed, 58 insertions(+) create mode 100644 packages/documentator/src/Commands/Preprocess.php diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index b26a2ff62..1435530da 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -23,6 +23,7 @@ "composer/semver": "^3.2", "laravel/framework": "^9.21.0|^10.0.0", "symfony/filesystem": "^6.3.0", + "symfony/finder": "^6.3.0", "symfony/process": "^6.3.0", "lastdragon-ru/lara-asp-core": "self.version", "lastdragon-ru/lara-asp-serializer": "self.version" diff --git a/packages/documentator/src/Commands/Preprocess.php b/packages/documentator/src/Commands/Preprocess.php new file mode 100644 index 000000000..519628d25 --- /dev/null +++ b/packages/documentator/src/Commands/Preprocess.php @@ -0,0 +1,55 @@ +argument('path') ?? $cwd); + $finder = Finder::create() + ->ignoreVCSIgnored(true) + ->in($path) + ->exclude('vendor') + ->exclude('node_modules') + ->files() + ->name('*.md'); + + foreach ($finder as $file) { + $this->components->task($file->getPathname(), static function () use ($preprocessor, $file): bool { + $path = $file->getPath(); + $content = $file->getContents(); + $result = $preprocessor->process($path, $content); + + return $content === $result + || file_put_contents($file->getPathname(), $result) !== false; + }); + } + } +} diff --git a/packages/documentator/src/Provider.php b/packages/documentator/src/Provider.php index b748d29e8..f921d0898 100644 --- a/packages/documentator/src/Provider.php +++ b/packages/documentator/src/Provider.php @@ -4,6 +4,7 @@ use Illuminate\Support\ServiceProvider; use LastDragon_ru\LaraASP\Core\Concerns\ProviderWithViews; +use LastDragon_ru\LaraASP\Documentator\Commands\Preprocess; use LastDragon_ru\LaraASP\Documentator\Commands\Requirements; class Provider extends ServiceProvider { @@ -13,6 +14,7 @@ public function boot(): void { $this->bootViews(); $this->commands( Requirements::class, + Preprocess::class, ); } From 980f393a6150de0b37b7f68871ede54a9f79146c Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:10:54 +0400 Subject: [PATCH 12/33] Preprocessor fixes. --- .../src/Preprocessor/Instructions/IncludeCommand.php | 6 +++++- .../src/Preprocessor/Instructions/IncludeCommandTest.php | 2 +- packages/documentator/src/Preprocessor/Preprocessor.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php b/packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php index a382c6721..72da5ed48 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php @@ -7,6 +7,7 @@ use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Process; +use function explode; use function sprintf; class IncludeCommand implements Instruction { @@ -22,7 +23,10 @@ public static function getName(): string { public function process(string $path, string $target): string { try { - return $this->process->run([$target], $path); + return $this->process->run( + explode(' ', $target, 2), // todo(documentator): Probably we need to parse args? + $path, + ); } catch (Exception $exception) { throw new PreprocessFailed( sprintf( diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php index e84ef473d..0d2d5f3db 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php @@ -19,7 +19,7 @@ public function testProcess(): void { $process = Mockery::mock(Process::class); $process ->shouldReceive('run') - ->with([$command], $path) + ->with(['command', 'to execute'], $path) ->once() ->andReturn($expected); diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 1f6d638e3..f51b87ee1 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -11,6 +11,7 @@ use function preg_replace_callback; use function sha1; use function trim; +use function urldecode; use const PREG_UNMATCHED_AS_NULL; @@ -93,7 +94,8 @@ public function process(string $path, string $string): string { if ($content === null) { $instruction = $this->getInstruction($matches['instruction']); - $content = trim($instruction?->process($path, $matches['target']) ?? ''); + $target = urldecode($matches['target']); + $content = trim($instruction?->process($path, $target) ?? ''); $this->cache[$hash] = $content; } From 9068359215dcc263c4cc2c5d4b80b96ff4e8c902 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 08:19:16 +0400 Subject: [PATCH 13/33] `Preprocessor` will use Link reference definitions --- .../src/Preprocessor/Preprocessor.php | 27 ++++++++++++------- .../src/Preprocessor/PreprocessorTest.php | 27 +++++++++++-------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index f51b87ee1..72e0bd81f 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -18,7 +18,8 @@ /** * Replaces special instructions in Markdown. * - * [Link]( "") + * []: + * [=name]: * * Supported instructions: * @@ -38,10 +39,16 @@ class Preprocessor { protected const Warning = 'Generated automatically. Do not edit.'; protected const Regexp = <<<'REGEXP' /^ - (?P\[[^]]*?\]\((?P(?P.+?)\s+\"(?P[^"]+?)\")\)) - (?P\ + {$matches['expression']} + [//]: # (start: {$hash}) + [//]: # (warning: {$warning}) {$content} - + [//]: # (end: {$hash}) RESULT; } else { - $content = $matches['preprocess']; + $content = $matches['expression']; } return $content; diff --git a/packages/documentator/src/Preprocessor/PreprocessorTest.php b/packages/documentator/src/Preprocessor/PreprocessorTest.php index b97bddb41..45a551779 100644 --- a/packages/documentator/src/Preprocessor/PreprocessorTest.php +++ b/packages/documentator/src/Preprocessor/PreprocessorTest.php @@ -12,17 +12,18 @@ class PreprocessorTest extends TestCase { public function testProcess(): void { $content = <<<'MARKDOWN' - Bla bla bla [Link](./path/to/file "test") should be ignored. + Bla bla bla [test]: ./path/to/file should be ignored. - [Link](./path/to/file "unknown") + [unknown]: ./path/to/file - [Link](./path/to/file "test") + [test]: ./path/to/file - [Link](./path/to/file "test") + [test]: ./path/to/file + [//]: # (start: hash) outdated - + [//]: # (end: hash) MARKDOWN; $instruction = Mockery::mock(Instruction::class); $instruction @@ -40,21 +41,25 @@ public function testProcess(): void { self::assertEquals( <<<'MARKDOWN' - Bla bla bla [Link](./path/to/file "test") should be ignored. + Bla bla bla [test]: ./path/to/file should be ignored. - [Link](./path/to/file "unknown") + [unknown]: ./path/to/file - [Link](./path/to/file "test") + [test]: ./path/to/file + [//]: # (start: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) + [//]: # (warning: Generated automatically. Do not edit.) content - + [//]: # (end: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) - [Link](./path/to/file "test") + [test]: ./path/to/file + [//]: # (start: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) + [//]: # (warning: Generated automatically. Do not edit.) content - + [//]: # (end: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) MARKDOWN, $preprocessor->process('path', $content), ); From 67f81a9ee153a4fda43cff923b6d80af38af232f Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 09:00:58 +0400 Subject: [PATCH 14/33] 'Path` helper. --- .../Instructions/IncludeExample.php | 4 +-- .../Preprocessor/Instructions/IncludeFile.php | 4 +-- packages/documentator/src/Utils/Path.php | 26 +++++++++++++++++++ packages/documentator/src/Utils/PathTest.php | 20 ++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/documentator/src/Utils/Path.php create mode 100644 packages/documentator/src/Utils/PathTest.php diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php index 4a8f5fe97..ac8025088 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -4,8 +4,8 @@ use Exception; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; +use LastDragon_ru\LaraASP\Documentator\Utils\Path; use LastDragon_ru\LaraASP\Documentator\Utils\Process; -use Symfony\Component\Filesystem\Filesystem; use function implode; use function is_file; @@ -89,7 +89,7 @@ protected function getCommand(string $path, string $target): ?array { $file = isset($info['dirname']) ? "{$info['dirname']}/{$info['filename']}.run" : "{$info['filename']}.run"; - $path = (new Filesystem())->isAbsolutePath($file) ? $file : "{$path}/{$file}"; + $path = Path::getPath($path, $file); $command = is_file($path) ? [$file] : null; return $command; diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php index d80acd811..2a5129df2 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php @@ -4,7 +4,7 @@ use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; -use Symfony\Component\Filesystem\Filesystem; +use LastDragon_ru\LaraASP\Documentator\Utils\Path; use function file_get_contents; use function sprintf; @@ -19,7 +19,7 @@ public static function getName(): string { } public function process(string $path, string $target): string { - $file = (new Filesystem())->isAbsolutePath($target) ? $target : "{$path}/{$target}"; + $file = Path::getPath($path, $target); $content = file_get_contents($file); if ($content === false) { diff --git a/packages/documentator/src/Utils/Path.php b/packages/documentator/src/Utils/Path.php new file mode 100644 index 000000000..9c322ec8e --- /dev/null +++ b/packages/documentator/src/Utils/Path.php @@ -0,0 +1,26 @@ + Date: Tue, 22 Aug 2023 09:37:54 +0400 Subject: [PATCH 15/33] `Preprocessor` will cache instructions only for current file. --- .../src/Preprocessor/Preprocessor.php | 23 ++++++++----------- .../src/Preprocessor/PreprocessorTest.php | 5 ++++ packages/documentator/src/Utils/Path.php | 13 +++++++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 72e0bd81f..3a1223465 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -7,6 +7,7 @@ use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeCommand; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeFile; +use LastDragon_ru\LaraASP\Documentator\Utils\Path; use function preg_replace_callback; use function sha1; @@ -55,11 +56,6 @@ class Preprocessor { */ private array $instructions = []; - /** - * @var array - */ - private array $cache = []; - public function __construct() { $this->addInstruction(IncludeFile::class); $this->addInstruction(IncludeCommand::class); @@ -92,18 +88,19 @@ protected function getInstruction(string $name): ?Instruction { } public function process(string $path, string $string): string { + $path = Path::normalize($path); + $cache = []; $result = preg_replace_callback( pattern : static::Regexp, - callback: function (array $matches) use ($path): string { - $identifier = $matches['instruction'].'='.$matches['target']; - $hash = $this->getHash($identifier); - $content = $this->cache[$hash] ?? null; + callback: function (array $matches) use (&$cache, $path): string { + $hash = $this->getHash("{$matches['instruction']}={$matches['target']}"); + $content = $cache[$hash] ?? null; if ($content === null) { - $instruction = $this->getInstruction($matches['instruction']); - $target = urldecode($matches['target']); - $content = trim($instruction?->process($path, $target) ?? ''); - $this->cache[$hash] = $content; + $instruction = $this->getInstruction($matches['instruction']); + $target = urldecode($matches['target']); + $content = trim($instruction?->process($path, $target) ?? ''); + $cache[$hash] = $content; } // Return diff --git a/packages/documentator/src/Preprocessor/PreprocessorTest.php b/packages/documentator/src/Preprocessor/PreprocessorTest.php index 45a551779..90d08e446 100644 --- a/packages/documentator/src/Preprocessor/PreprocessorTest.php +++ b/packages/documentator/src/Preprocessor/PreprocessorTest.php @@ -21,8 +21,13 @@ public function testProcess(): void { [test]: ./path/to/file [//]: # (start: hash) + [test]: ./path/to/file + [//]: # (start: nested-hash) + outdated + [//]: # (end: nested-hash) + [//]: # (end: hash) MARKDOWN; $instruction = Mockery::mock(Instruction::class); diff --git a/packages/documentator/src/Utils/Path.php b/packages/documentator/src/Utils/Path.php index 9c322ec8e..227229365 100644 --- a/packages/documentator/src/Utils/Path.php +++ b/packages/documentator/src/Utils/Path.php @@ -9,13 +9,22 @@ class Path { public static function getPath(string $root, string $path): string { - $root = is_file($root) ? dirname($root) : $root; - $path = static::isAbsolute($path) ? $path : "{$root}/{$path}"; + $path = static::isRelative($path) + ? SymfonyPath::join(static::getDirname($root), $path) + : $path; $path = static::normalize($path); return $path; } + public static function getDirname(string $path): string { + return is_file($path) ? dirname($path) : $path; + } + + public static function isRelative(string $path): bool { + return SymfonyPath::isRelative($path); + } + public static function isAbsolute(string $path): bool { return SymfonyPath::isAbsolute($path); } From f92cbc1bb602d432740e9b1fae0bdd4417f6eea8 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:16:12 +0400 Subject: [PATCH 16/33] `Markdown` helper. --- composer.json | 1 + packages/documentator/composer.json | 1 + packages/documentator/src/Utils/Markdown.php | 74 ++++++++++++ .../documentator/src/Utils/MarkdownTest.php | 112 ++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 packages/documentator/src/Utils/Markdown.php create mode 100644 packages/documentator/src/Utils/MarkdownTest.php diff --git a/composer.json b/composer.json index 4a0e268b8..1f7078858 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,7 @@ "guzzlehttp/psr7": "^1.9.1|^2.4.5", "http-interop/http-factory-guzzle": "^1.0.0", "laravel/framework": "^9.21.0|^10.0.0", + "league/commonmark": "^2.4", "nuwave/lighthouse": "^6.5.0", "opis/json-schema": "^2.3.0", "phpdocumentor/type-resolver": "^1.7", diff --git a/packages/documentator/composer.json b/packages/documentator/composer.json index 1435530da..7988f59e9 100644 --- a/packages/documentator/composer.json +++ b/packages/documentator/composer.json @@ -22,6 +22,7 @@ "ext-mbstring": "*", "composer/semver": "^3.2", "laravel/framework": "^9.21.0|^10.0.0", + "league/commonmark": "^2.4", "symfony/filesystem": "^6.3.0", "symfony/finder": "^6.3.0", "symfony/process": "^6.3.0", diff --git a/packages/documentator/src/Utils/Markdown.php b/packages/documentator/src/Utils/Markdown.php new file mode 100644 index 000000000..f0386bf41 --- /dev/null +++ b/packages/documentator/src/Utils/Markdown.php @@ -0,0 +1,74 @@ +next(); + $summary = $node instanceof Paragraph + ? static::getText($string, $node) + : null; + + return $summary; + } + + protected static function getDocumentNode(string $string): Document { + $converter = new GithubFlavoredMarkdownConverter(); + $environment = $converter->getEnvironment(); + $parser = new MarkdownParser($environment); + + return $parser->parse($string); + } + + protected static function getTitleNode(string $string): ?Heading { + $node = static::getDocumentNode($string)->firstChild(); + $header = $node instanceof Heading && $node->getLevel() === 1 + ? $node + : null; + + return $header; + } + + protected static function getText(string $string, ?AbstractBlock $node): ?string { + // todo(documentator): There is no way to convert AST back to Markdown yet + // https://github.com/thephpleague/commonmark/issues/419 + if (!$node || $node->getStartLine() === null || $node->getEndLine() === null) { + return null; + } + + $start = $node->getStartLine() - 1; + $end = $node->getEndLine() - 1; + $lines = (array) preg_split('/\R/u', $string); + $lines = array_slice($lines, $start, $end - $start + 1); + $text = trim(implode("\n", $lines)); + + return $text; + } +} diff --git a/packages/documentator/src/Utils/MarkdownTest.php b/packages/documentator/src/Utils/MarkdownTest.php new file mode 100644 index 000000000..7912aec32 --- /dev/null +++ b/packages/documentator/src/Utils/MarkdownTest.php @@ -0,0 +1,112 @@ + Not a paragraph + + fsdfsdfsdf + MARKDOWN, + ), + ); + self::assertEquals( + 'fsdfsdfsdf', + Markdown::getSummary( + <<<'MARKDOWN' + # + + fsdfsdfsdf + MARKDOWN, + ), + ); + self::assertEquals( + <<<'TEXT' + fsdfsdfsdf + fsdfsdfsdf + TEXT, + Markdown::getSummary( + <<<'MARKDOWN' + + # Header + + fsdfsdfsdf + fsdfsdfsdf + MARKDOWN, + ), + ); + } +} From 53b1fdadcf0b10a29dd8d5a5bd6ab02655d402ef Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:53:23 +0400 Subject: [PATCH 17/33] New instruction: `include:package-list`. --- .../views/package-list/markdown.blade.php | 18 +++ .../Instructions/IncludePackageList.php | 129 ++++++++++++++++++ .../Instructions/IncludePackageListTest.md | 11 ++ .../Instructions/IncludePackageListTest.php | 30 ++++ .../not a package/README.md | 3 + .../package custom readme/CUSTOM.md | 5 + .../package custom readme/README.md | 3 + .../package custom readme/composer.json | 5 + .../package no title/README.md | 3 + .../package no title/composer.json | 5 + .../IncludePackageListTest/package/README.md | 3 + .../package/composer.json | 5 + .../src/Preprocessor/Preprocessor.php | 2 + 13 files changed, 222 insertions(+) create mode 100644 packages/documentator/resources/views/package-list/markdown.blade.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/not a package/README.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/CUSTOM.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/README.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/composer.json create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/README.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/composer.json create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/README.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/composer.json diff --git a/packages/documentator/resources/views/package-list/markdown.blade.php b/packages/documentator/resources/views/package-list/markdown.blade.php new file mode 100644 index 000000000..d1c52da5d --- /dev/null +++ b/packages/documentator/resources/views/package-list/markdown.blade.php @@ -0,0 +1,18 @@ + $packages + */ + +?> +@foreach ($packages as $package) +## {{ $package['title'] }} +@if($package['summary']) + +{{ $package['summary'] }} +@endif + +[Read more](<{{ $package['readme'] }}>). +@if (!$loop->last) + +@endif +@endforeach diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php new file mode 100644 index 000000000..a9fc904da --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php @@ -0,0 +1,129 @@ + $packages */ + $packages = []; + $directories = Finder::create() + ->ignoreVCSIgnored(true) + ->in(Path::getPath($path, $target)) + ->depth(0) + ->exclude('vendor') + ->exclude('node_modules') + ->directories(); + + foreach ($directories as $directory) { + // Package? + $root = $directory->getPathname(); + $package = $this->getPackageInfo($root); + + if (!$package) { + continue; + } + + // Readme + $readme = $this->getPackageReadme($root, $package); + $content = $readme + ? file_get_contents("{$root}/{$readme}") + : false; + + if ($content === false) { + continue; + } + + // Extract + $title = Markdown::getTitle($content); + + if ($title) { + $path = Path::normalize("{$target}/{$directory->getFilename()}"); + + $packages[] = [ + 'path' => $path, + 'title' => $title, + 'summary' => Markdown::getSummary($content), + 'readme' => Path::normalize("{$path}/{$readme}"), + ]; + } + } + + // Packages? + if (!$packages) { + return ''; + } + + // Render + $package = Package::Name; + $list = view("{$package}::package-list.markdown", [ + 'packages' => $packages, + ])->render(); + + // Return + return $list; + } + + /** + * @return array|null + */ + protected function getPackageInfo(string $path): ?array { + try { + $file = "{$path}/composer.json"; + $package = is_file($file) ? file_get_contents($file) : false; + $package = $package !== false + ? json_decode($package, true, flags: JSON_THROW_ON_ERROR) + : null; + + assert(is_array($package)); + } catch (Exception) { + $package = null; + } + + return $package; + } + + /** + * @param array $package + */ + protected function getPackageReadme(string $path, array $package): ?string { + $readme = null; + $variants = [ + $package['readme'] ?? null, + 'README.md', + ]; + + foreach ($variants as $variant) { + if ($variant && is_string($variant) && is_file(Path::getPath($path, $variant))) { + $readme = $variant; + break; + } + } + + return $readme; + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md new file mode 100644 index 000000000..b7b49deaf --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md @@ -0,0 +1,11 @@ + + +## The Package + +Summary text. + +[Read more](). + +## The Package with custom readme + +[Read more](). diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php new file mode 100644 index 000000000..f5d7a0a6f --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php @@ -0,0 +1,30 @@ +path('/'); + $instance = $this->app->make(IncludePackageList::class); + $actual = $instance->process(dirname($path), basename($path)); + + self::assertEquals( + self::getTestData()->content('.md'), + << + + {$actual} + MARKDOWN, + ); + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/not a package/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/not a package/README.md new file mode 100644 index 000000000..3f6f7e12f --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/not a package/README.md @@ -0,0 +1,3 @@ +# No `composer.json` + +`composer.json` is required. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/CUSTOM.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/CUSTOM.md new file mode 100644 index 000000000..231405d90 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/CUSTOM.md @@ -0,0 +1,5 @@ +# The Package with custom readme + +## Section + +Text. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/README.md new file mode 100644 index 000000000..147bdcf3c --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/README.md @@ -0,0 +1,3 @@ +# Should not be used + +Should not be used. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/composer.json new file mode 100644 index 000000000..4b26f97e1 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/composer.json @@ -0,0 +1,5 @@ +{ + "name": "lastdragon-ru/lara-asp-documentator", + "homepage": "https://github.com/LastDragon-ru/lara-asp", + "readme": "CUSTOM.md" +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/README.md new file mode 100644 index 000000000..e600bbdef --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/README.md @@ -0,0 +1,3 @@ + + +Package readme. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/composer.json new file mode 100644 index 000000000..0a9113a4a --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/composer.json @@ -0,0 +1,5 @@ +{ + "name": "lastdragon-ru/lara-asp-documentator", + "homepage": "https://github.com/LastDragon-ru/lara-asp", + "readme": "README.md" +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/README.md new file mode 100644 index 000000000..26d9a5bb0 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/README.md @@ -0,0 +1,3 @@ +# The Package + +Summary text. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/composer.json new file mode 100644 index 000000000..0a9113a4a --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/composer.json @@ -0,0 +1,5 @@ +{ + "name": "lastdragon-ru/lara-asp-documentator", + "homepage": "https://github.com/LastDragon-ru/lara-asp", + "readme": "README.md" +} diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 3a1223465..ff08dfa11 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -7,6 +7,7 @@ use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeCommand; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeFile; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludePackageList; use LastDragon_ru\LaraASP\Documentator\Utils\Path; use function preg_replace_callback; @@ -60,6 +61,7 @@ public function __construct() { $this->addInstruction(IncludeFile::class); $this->addInstruction(IncludeCommand::class); $this->addInstruction(IncludeExample::class); + $this->addInstruction(IncludePackageList::class); } /** From 047501c19b07f11f8a7df477daed8ab9f6c43b4c Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:37:03 +0400 Subject: [PATCH 18/33] `Preprocessor` minor improvements. --- .../src/Preprocessor/Preprocessor.php | 35 +++++++++++++------ .../src/Preprocessor/PreprocessorTest.php | 28 ++++++++++++--- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index ff08dfa11..4762ca98f 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -11,9 +11,9 @@ use LastDragon_ru\LaraASP\Documentator\Utils\Path; use function preg_replace_callback; +use function rawurldecode; use function sha1; use function trim; -use function urldecode; use const PREG_UNMATCHED_AS_NULL; @@ -36,6 +36,8 @@ * block) and may give unpredictable results. * - `` cannot be inside text. * - Nested `` doesn't supported. + * + * @todo Use https://github.com/thephpleague/commonmark? */ class Preprocessor { protected const Warning = 'Generated automatically. Do not edit.'; @@ -95,27 +97,40 @@ public function process(string $path, string $string): string { $result = preg_replace_callback( pattern : static::Regexp, callback: function (array $matches) use (&$cache, $path): string { - $hash = $this->getHash("{$matches['instruction']}={$matches['target']}"); - $content = $cache[$hash] ?? null; + $hash = $this->getHash("{$matches['instruction']}={$matches['target']}"); + $content = $cache[$hash] ?? null; + $instruction = $this->getInstruction($matches['instruction']); if ($content === null) { - $instruction = $this->getInstruction($matches['instruction']); - $target = urldecode($matches['target']); + $target = rawurldecode($matches['target']); $content = trim($instruction?->process($path, $target) ?? ''); $cache[$hash] = $content; } // Return + $warning = static::Warning; + $prefix = <<shouldReceive('getName') ->once() ->andReturn('test'); - $instruction + $testInstruction ->shouldReceive('process') ->with('path', './path/to/file') ->once() ->andReturn('content'); + $emptyInstruction = new class() implements Instruction { + public static function getName(): string { + return 'empty'; + } + + public function process(string $path, string $target): string { + return ''; + } + }; $preprocessor = (new Preprocessor()) - ->addInstruction($instruction); + ->addInstruction($testInstruction) + ->addInstruction($emptyInstruction); self::assertEquals( <<<'MARKDOWN' @@ -50,6 +62,12 @@ public function testProcess(): void { [unknown]: ./path/to/file + [empty]: ./path/to/file + [//]: # (start: a3fab3c67a30d7c1ba21b17c4c0e9e609e78373c) + [//]: # (warning: Generated automatically. Do not edit.) + [//]: # (empty) + [//]: # (end: a3fab3c67a30d7c1ba21b17c4c0e9e609e78373c) + [test]: ./path/to/file [//]: # (start: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) [//]: # (warning: Generated automatically. Do not edit.) From 2198e55606736fae177f875590a3e8056849f441 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:47:40 +0400 Subject: [PATCH 19/33] `IncludeExample` minor fixes. --- .../src/Preprocessor/Instructions/IncludeExample.php | 1 + .../src/Preprocessor/Instructions/IncludeExampleTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php index ac8025088..78c74e9c7 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -65,6 +65,7 @@ public function process(string $path, string $target): string { } else { $output = << Date: Tue, 22 Aug 2023 16:51:47 +0400 Subject: [PATCH 20/33] `IncludePackageList` will sort packages by title. --- .../resources/views/package-list/markdown.blade.php | 4 ++-- .../Preprocessor/Instructions/IncludePackageList.php | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/documentator/resources/views/package-list/markdown.blade.php b/packages/documentator/resources/views/package-list/markdown.blade.php index d1c52da5d..777a99450 100644 --- a/packages/documentator/resources/views/package-list/markdown.blade.php +++ b/packages/documentator/resources/views/package-list/markdown.blade.php @@ -1,6 +1,6 @@ $packages + * @var list $packages */ ?> @@ -11,7 +11,7 @@ {{ $package['summary'] }} @endif -[Read more](<{{ $package['readme'] }}>). +[Read more](<{{ $package['path'] }}>). @if (!$loop->last) @endif diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php index a9fc904da..6ccbf1a62 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php @@ -15,6 +15,8 @@ use function is_file; use function is_string; use function json_decode; +use function strcmp; +use function usort; use function view; use const JSON_THROW_ON_ERROR; @@ -62,13 +64,10 @@ public function process(string $path, string $target): string { $title = Markdown::getTitle($content); if ($title) { - $path = Path::normalize("{$target}/{$directory->getFilename()}"); - $packages[] = [ - 'path' => $path, + 'path' => Path::normalize("{$target}/{$directory->getFilename()}/{$readme}"), 'title' => $title, 'summary' => Markdown::getSummary($content), - 'readme' => Path::normalize("{$path}/{$readme}"), ]; } } @@ -78,6 +77,11 @@ public function process(string $path, string $target): string { return ''; } + // Sort + usort($packages, static function (array $a, $b): int { + return strcmp($a['title'], $b['title']); + }); + // Render $package = Package::Name; $list = view("{$package}::package-list.markdown", [ From 6a9c2281d9f0fff4150cd73e89f5e93e1098e518 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:42:54 +0400 Subject: [PATCH 21/33] New instruction: `include:document-list`. --- .../views/document-list/markdown.blade.php | 18 +++++ .../Instructions/IncludeDocumentList.php | 73 +++++++++++++++++++ .../Instructions/IncludeDocumentListTest.md | 13 ++++ .../Instructions/IncludeDocumentListTest.php | 30 ++++++++ .../IncludeDocumentListTest/Document A.md | 5 ++ .../IncludeDocumentListTest/Document B.md | 5 ++ .../IncludeDocumentListTest/WithoutTitle.md | 3 + .../src/Preprocessor/Preprocessor.php | 2 + 8 files changed, 149 insertions(+) create mode 100644 packages/documentator/resources/views/document-list/markdown.blade.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document A.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/WithoutTitle.md diff --git a/packages/documentator/resources/views/document-list/markdown.blade.php b/packages/documentator/resources/views/document-list/markdown.blade.php new file mode 100644 index 000000000..7cfddaf71 --- /dev/null +++ b/packages/documentator/resources/views/document-list/markdown.blade.php @@ -0,0 +1,18 @@ + $documents + */ + +?> +@foreach ($documents as $document) +## {{ $document['title'] }} +@if($document['summary']) + +{{ $document['summary'] }} +@endif + +[Read more](<{{ $document['path'] }}>). +@if (!$loop->last) + +@endif +@endforeach diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php new file mode 100644 index 000000000..e99df3758 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php @@ -0,0 +1,73 @@ + $documents */ + $documents = []; + $files = Finder::create() + ->in(Path::getPath($path, $target)) + ->depth(0) + ->name('*.md') + ->files(); + + foreach ($files as $file) { + // Package? + $content = file_get_contents($file->getPathname()); + + if (!$content) { + continue; + } + + // Extract + $title = Markdown::getTitle($content); + + if ($title) { + $documents[] = [ + 'path' => Path::normalize("{$target}/{$file->getFilename()}"), + 'title' => $title, + 'summary' => Markdown::getSummary($content), + ]; + } + } + + // Empty? + if (!$documents) { + return ''; + } + + // Sort + usort($documents, static function (array $a, $b): int { + return strcmp($a['title'], $b['title']); + }); + + // Render + $package = Package::Name; + $list = view("{$package}::document-list.markdown", [ + 'documents' => $documents, + ])->render(); + + // Return + return $list; + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md new file mode 100644 index 000000000..b1052cd31 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md @@ -0,0 +1,13 @@ + + +## Document + +Summary text. + +[Read more](). + +## Document A + +Summary text. + +[Read more](). diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php new file mode 100644 index 000000000..d298a4225 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php @@ -0,0 +1,30 @@ +path('/'); + $instance = $this->app->make(IncludeDocumentList::class); + $actual = $instance->process(dirname($path), basename($path)); + + self::assertEquals( + self::getTestData()->content('.md'), + << + + {$actual} + MARKDOWN, + ); + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document A.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document A.md new file mode 100644 index 000000000..c4f2592b3 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document A.md @@ -0,0 +1,5 @@ +# Document A + +Summary text. + +Another text. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md new file mode 100644 index 000000000..a2b9c7123 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md @@ -0,0 +1,5 @@ +# Document + +Summary text. + +Another text. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/WithoutTitle.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/WithoutTitle.md new file mode 100644 index 000000000..0b64c6705 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/WithoutTitle.md @@ -0,0 +1,3 @@ + + +Document text. diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 4762ca98f..5058d2330 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -5,6 +5,7 @@ use Illuminate\Container\Container; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeCommand; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeDocumentList; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeFile; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludePackageList; @@ -64,6 +65,7 @@ public function __construct() { $this->addInstruction(IncludeCommand::class); $this->addInstruction(IncludeExample::class); $this->addInstruction(IncludePackageList::class); + $this->addInstruction(IncludeDocumentList::class); } /** From 4fa1a510c3e3b1eb2a1ce187d686cfb9fbdfa1ef Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:36:50 +0400 Subject: [PATCH 22/33] Improvements and fixes. --- .../documentator/src/Commands/Preprocess.php | 4 +-- .../documentator/src/Metadata/Storage.php | 3 +- .../Exceptions/InstructionFailed.php | 24 ++++++++++++++ .../Exceptions/TargetCommandFailed.php | 22 +++++++++++++ .../Exceptions/TargetIsNotDirectory.php | 22 +++++++++++++ .../Exceptions/TargetIsNotFile.php | 22 +++++++++++++ .../Instructions/IncludeCommand.php | 14 +++----- .../Instructions/IncludeCommandTest.php | 6 ++-- .../Instructions/IncludeDocumentList.php | 23 +++++++++++-- .../Instructions/IncludeDocumentListTest.php | 24 +++++++++++--- .../IncludeDocumentListTest/Document B.md | 2 +- .../IncludeDocumentListTest/Document.md | 3 ++ ...cludeDocumentListTest~AnotherDirectory.md} | 10 ++++-- .../IncludeDocumentListTest~SameDirectory.md | 13 ++++++++ .../Instructions/IncludeExample.php | 21 ++++-------- .../Instructions/IncludeExampleTest.php | 12 +++---- .../Preprocessor/Instructions/IncludeFile.php | 13 +++----- .../Instructions/IncludeFileTest.php | 8 ++--- .../Instructions/IncludePackageList.php | 32 ++++++++++++------- .../Instructions/IncludePackageListTest.php | 5 ++- packages/documentator/src/Utils/Path.php | 4 +++ 21 files changed, 212 insertions(+), 75 deletions(-) create mode 100644 packages/documentator/src/Preprocessor/Exceptions/InstructionFailed.php create mode 100644 packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php create mode 100644 packages/documentator/src/Preprocessor/Exceptions/TargetIsNotDirectory.php create mode 100644 packages/documentator/src/Preprocessor/Exceptions/TargetIsNotFile.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document.md rename packages/documentator/src/Preprocessor/Instructions/{IncludeDocumentListTest.md => IncludeDocumentListTest~AnotherDirectory.md} (69%) create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~SameDirectory.md diff --git a/packages/documentator/src/Commands/Preprocess.php b/packages/documentator/src/Commands/Preprocess.php index 519628d25..e23513ef6 100644 --- a/packages/documentator/src/Commands/Preprocess.php +++ b/packages/documentator/src/Commands/Preprocess.php @@ -43,12 +43,12 @@ public function __invoke(Preprocessor $preprocessor): void { foreach ($finder as $file) { $this->components->task($file->getPathname(), static function () use ($preprocessor, $file): bool { - $path = $file->getPath(); + $path = $file->getPathname(); $content = $file->getContents(); $result = $preprocessor->process($path, $content); return $content === $result - || file_put_contents($file->getPathname(), $result) !== false; + || file_put_contents($path, $result) !== false; }); } } diff --git a/packages/documentator/src/Metadata/Storage.php b/packages/documentator/src/Metadata/Storage.php index 655116c8f..96110ff27 100644 --- a/packages/documentator/src/Metadata/Storage.php +++ b/packages/documentator/src/Metadata/Storage.php @@ -2,6 +2,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Metadata; +use LastDragon_ru\LaraASP\Documentator\Utils\Path; use LastDragon_ru\LaraASP\Documentator\Utils\Version; use LastDragon_ru\LaraASP\Serializer\Contracts\Serializer; use Symfony\Component\Serializer\Context\Encoder\JsonEncoderContextBuilder; @@ -42,7 +43,7 @@ public function __construct( } protected function getPath(): string { - return "{$this->path}/metadata.json"; + return Path::join($this->path, 'metadata.json'); } public function load(): Metadata { diff --git a/packages/documentator/src/Preprocessor/Exceptions/InstructionFailed.php b/packages/documentator/src/Preprocessor/Exceptions/InstructionFailed.php new file mode 100644 index 000000000..08d12da20 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Exceptions/InstructionFailed.php @@ -0,0 +1,24 @@ +path; + } + + public function getTarget(): string { + return $this->target; + } +} diff --git a/packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php b/packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php new file mode 100644 index 000000000..2107a3de8 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php @@ -0,0 +1,22 @@ +process->run( explode(' ', $target, 2), // todo(documentator): Probably we need to parse args? - $path, + dirname($path), ); } catch (Exception $exception) { - throw new PreprocessFailed( - sprintf( - 'Failed to execute command `%s`.', - $target, - ), - $exception, - ); + throw new TargetCommandFailed($path, $target, $exception); } } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php index 0d2d5f3db..482384436 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php @@ -7,19 +7,21 @@ use Mockery; use PHPUnit\Framework\Attributes\CoversClass; +use function dirname; + /** * @internal */ #[CoversClass(IncludeFile::class)] class IncludeCommandTest extends TestCase { public function testProcess(): void { - $path = 'current/working/directory'; + $path = 'current/working/directory/file.md'; $expected = 'result'; $command = 'command to execute'; $process = Mockery::mock(Process::class); $process ->shouldReceive('run') - ->with(['command', 'to execute'], $path) + ->with(['command', 'to execute'], dirname($path)) ->once() ->andReturn($expected); diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php index e99df3758..3b2d62a2d 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php @@ -3,12 +3,16 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; use LastDragon_ru\LaraASP\Documentator\Package; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetIsNotDirectory; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Markdown; use LastDragon_ru\LaraASP\Documentator\Utils\Path; use Symfony\Component\Finder\Finder; +use function basename; +use function dirname; use function file_get_contents; +use function is_dir; use function strcmp; use function usort; use function view; @@ -23,16 +27,29 @@ public static function getName(): string { } public function process(string $path, string $target): string { + // Directory? + $root = Path::getPath(dirname($path), $target); + + if (!is_dir($root)) { + throw new TargetIsNotDirectory($path, $target); + } + /** @var list $documents */ $documents = []; + $target = Path::normalize($target); $files = Finder::create() - ->in(Path::getPath($path, $target)) + ->in($root) ->depth(0) ->name('*.md') ->files(); foreach ($files as $file) { - // Package? + // Same? + if ($target === '' && $file->getFilename() === basename($path)) { + continue; + } + + // Content? $content = file_get_contents($file->getPathname()); if (!$content) { @@ -44,7 +61,7 @@ public function process(string $path, string $target): string { if ($title) { $documents[] = [ - 'path' => Path::normalize("{$target}/{$file->getFilename()}"), + 'path' => Path::join($target, $file->getFilename()), 'title' => $title, 'summary' => Markdown::getSummary($content), ]; diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php index d298a4225..4d926a220 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php @@ -6,20 +6,19 @@ use PHPUnit\Framework\Attributes\CoversClass; use function basename; -use function dirname; /** * @internal */ #[CoversClass(IncludeDocumentList::class)] class IncludeDocumentListTest extends TestCase { - public function testProcess(): void { - $path = self::getTestData()->path('/'); + public function testProcessSameDirectory(): void { + $path = self::getTestData()->file('Document.md'); $instance = $this->app->make(IncludeDocumentList::class); - $actual = $instance->process(dirname($path), basename($path)); + $actual = $instance->process($path->getPathname(), './'); self::assertEquals( - self::getTestData()->content('.md'), + self::getTestData()->content('~SameDirectory.md'), << @@ -27,4 +26,19 @@ public function testProcess(): void { MARKDOWN, ); } + + public function testProcessAnotherDirectory(): void { + $path = self::getTestData()->file('~AnotherDirectory.md'); + $instance = $this->app->make(IncludeDocumentList::class); + $actual = $instance->process($path->getPathname(), basename(self::getTestData()->path('/'))); + + self::assertEquals( + self::getTestData()->content('~AnotherDirectory.md'), + << + + {$actual} + MARKDOWN, + ); + } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md index a2b9c7123..67675789d 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document B.md @@ -1,4 +1,4 @@ -# Document +# Document B Summary text. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document.md new file mode 100644 index 000000000..9a246aef7 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/Document.md @@ -0,0 +1,3 @@ +# Document + +Document summary. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~AnotherDirectory.md similarity index 69% rename from packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md rename to packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~AnotherDirectory.md index b1052cd31..57418b25f 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.md +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~AnotherDirectory.md @@ -2,12 +2,18 @@ ## Document -Summary text. +Document summary. -[Read more](). +[Read more](). ## Document A Summary text. [Read more](). + +## Document B + +Summary text. + +[Read more](). diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~SameDirectory.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~SameDirectory.md new file mode 100644 index 000000000..eeceb2f41 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest~SameDirectory.md @@ -0,0 +1,13 @@ + + +## Document A + +Summary text. + +[Read more](). + +## Document B + +Summary text. + +[Read more](). diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php index 78c74e9c7..130463951 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -3,15 +3,14 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; use Exception; -use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetCommandFailed; use LastDragon_ru\LaraASP\Documentator\Utils\Path; use LastDragon_ru\LaraASP\Documentator\Utils\Process; -use function implode; +use function dirname; use function is_file; use function pathinfo; use function preg_match_all; -use function sprintf; use function trim; use const PATHINFO_EXTENSION; @@ -41,15 +40,9 @@ public function process(string $path, string $target): string { if ($command) { try { - $output = $this->process->run($command, $path); + $output = $this->process->run($command, dirname($path)); } catch (Exception $exception) { - throw new PreprocessFailed( - sprintf( - 'Failed to execute command `%s`.', - implode(' ', $command), - ), - $exception, - ); + throw new TargetCommandFailed($path, $target, $exception); } if (preg_match_all('/\R/u', $output) > static::Limit) { @@ -88,10 +81,10 @@ protected function getLanguage(string $path, string $target): string { protected function getCommand(string $path, string $target): ?array { $info = pathinfo($target); $file = isset($info['dirname']) - ? "{$info['dirname']}/{$info['filename']}.run" + ? Path::join($info['dirname'], "{$info['filename']}.run") : "{$info['filename']}.run"; - $path = Path::getPath($path, $file); - $command = is_file($path) ? [$file] : null; + $command = Path::getPath(dirname($path), $file); + $command = is_file($command) ? [$command] : null; return $command; } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php index d5864dd67..083790dec 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExampleTest.php @@ -19,7 +19,7 @@ #[CoversClass(IncludeFile::class)] class IncludeExampleTest extends TestCase { public function testProcessNoRun(): void { - $path = dirname(self::getTestData()->path('~example.md')); + $path = self::getTestData()->path('~example.md'); $file = basename(self::getTestData()->path('~example.md')); $expected = trim(self::getTestData()->content('~example.md')); $process = Mockery::mock(Process::class); @@ -42,14 +42,14 @@ public function testProcessNoRun(): void { } public function testProcess(): void { - $path = dirname(self::getTestData()->path('~runnable.md')); - $file = self::getTestData()->path('~runnable.md'); + $path = self::getTestData()->path('~runnable.md'); + $file = basename(self::getTestData()->path('~runnable.md')); $expected = trim(self::getTestData()->content('~runnable.md')); $output = 'command output'; $process = Mockery::mock(Process::class); $process ->shouldReceive('run') - ->with([self::getTestData()->path('~runnable.run')], $path) + ->with([self::getTestData()->path('~runnable.run')], dirname($path)) ->once() ->andReturn($output); @@ -74,14 +74,14 @@ public function testProcess(): void { } public function testProcessLongOutput(): void { - $path = dirname(self::getTestData()->path('~runnable.md')); + $path = self::getTestData()->path('~runnable.md'); $file = self::getTestData()->path('~runnable.md'); $expected = trim(self::getTestData()->content('~runnable.md')); $output = implode("\n", range(0, IncludeExample::Limit + 1)); $process = Mockery::mock(Process::class); $process ->shouldReceive('run') - ->with([self::getTestData()->path('~runnable.run')], $path) + ->with([self::getTestData()->path('~runnable.run')], dirname($path)) ->once() ->andReturn($output); diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php index 2a5129df2..30cbd02ca 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php @@ -2,12 +2,12 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; -use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetIsNotFile; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Path; +use function dirname; use function file_get_contents; -use function sprintf; class IncludeFile implements Instruction { public function __construct() { @@ -19,16 +19,11 @@ public static function getName(): string { } public function process(string $path, string $target): string { - $file = Path::getPath($path, $target); + $file = Path::getPath(dirname($path), $target); $content = file_get_contents($file); if ($content === false) { - throw new PreprocessFailed( - sprintf( - 'Failed to include `%s`.', - $target, - ), - ); + throw new TargetIsNotFile($path, $target); } return $content; diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php index 0d7840f56..719d1b03f 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFileTest.php @@ -5,21 +5,17 @@ use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase; use PHPUnit\Framework\Attributes\CoversClass; -use function basename; -use function dirname; - /** * @internal */ #[CoversClass(IncludeFile::class)] class IncludeFileTest extends TestCase { public function testProcessRelative(): void { - $path = dirname(self::getTestData()->path('.md')); - $file = basename(self::getTestData()->path('.md')); + $file = self::getTestData()->file('.md'); $expected = self::getTestData()->content('.md'); $instance = $this->app->make(IncludeFile::class); - self::assertEquals($expected, $instance->process($path, $file)); + self::assertEquals($expected, $instance->process($file->getPathname(), $file->getFilename())); } public function testProcessAbsolute(): void { diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php index 6ccbf1a62..d9b8c7c29 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php @@ -4,14 +4,17 @@ use Exception; use LastDragon_ru\LaraASP\Documentator\Package; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetIsNotDirectory; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Markdown; use LastDragon_ru\LaraASP\Documentator\Utils\Path; use Symfony\Component\Finder\Finder; use function assert; +use function dirname; use function file_get_contents; use function is_array; +use function is_dir; use function is_file; use function is_string; use function json_decode; @@ -31,11 +34,18 @@ public static function getName(): string { } public function process(string $path, string $target): string { + // Directory? + $root = Path::getPath(dirname($path), $target); + + if (!is_dir($root)) { + throw new TargetIsNotDirectory($path, $target); + } + /** @var list $packages */ $packages = []; $directories = Finder::create() ->ignoreVCSIgnored(true) - ->in(Path::getPath($path, $target)) + ->in($root) ->depth(0) ->exclude('vendor') ->exclude('node_modules') @@ -43,20 +53,20 @@ public function process(string $path, string $target): string { foreach ($directories as $directory) { // Package? - $root = $directory->getPathname(); - $package = $this->getPackageInfo($root); + $packagePath = $directory->getPathname(); + $packageInfo = $this->getPackageInfo($packagePath); - if (!$package) { + if (!$packageInfo) { continue; } // Readme - $readme = $this->getPackageReadme($root, $package); + $readme = $this->getPackageReadme($packagePath, $packageInfo); $content = $readme - ? file_get_contents("{$root}/{$readme}") + ? file_get_contents(Path::join($packagePath, $readme)) : false; - if ($content === false) { + if (!$readme || $content === false) { continue; } @@ -65,7 +75,7 @@ public function process(string $path, string $target): string { if ($title) { $packages[] = [ - 'path' => Path::normalize("{$target}/{$directory->getFilename()}/{$readme}"), + 'path' => Path::join($target, $directory->getFilename(), $readme), 'title' => $title, 'summary' => Markdown::getSummary($content), ]; @@ -83,8 +93,8 @@ public function process(string $path, string $target): string { }); // Render - $package = Package::Name; - $list = view("{$package}::package-list.markdown", [ + $packageInfo = Package::Name; + $list = view("{$packageInfo}::package-list.markdown", [ 'packages' => $packages, ])->render(); @@ -97,7 +107,7 @@ public function process(string $path, string $target): string { */ protected function getPackageInfo(string $path): ?array { try { - $file = "{$path}/composer.json"; + $file = Path::join($path, 'composer.json'); $package = is_file($file) ? file_get_contents($file) : false; $package = $package !== false ? json_decode($package, true, flags: JSON_THROW_ON_ERROR) diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php index f5d7a0a6f..37e9912ca 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php @@ -6,7 +6,6 @@ use PHPUnit\Framework\Attributes\CoversClass; use function basename; -use function dirname; /** * @internal @@ -14,9 +13,9 @@ #[CoversClass(IncludePackageList::class)] class IncludePackageListTest extends TestCase { public function testProcess(): void { - $path = self::getTestData()->path('/'); + $path = self::getTestData()->file('.md'); $instance = $this->app->make(IncludePackageList::class); - $actual = $instance->process(dirname($path), basename($path)); + $actual = $instance->process($path->getPathname(), basename(self::getTestData()->path('/'))); self::assertEquals( self::getTestData()->content('.md'), diff --git a/packages/documentator/src/Utils/Path.php b/packages/documentator/src/Utils/Path.php index 227229365..ae5d06507 100644 --- a/packages/documentator/src/Utils/Path.php +++ b/packages/documentator/src/Utils/Path.php @@ -32,4 +32,8 @@ public static function isAbsolute(string $path): bool { public static function normalize(string $path): string { return SymfonyPath::canonicalize($path); } + + public static function join(string ...$paths): string { + return SymfonyPath::join(...$paths); + } } From ddf643df4b5b4c53d5609624ff9d186c2defb6d1 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 24 Aug 2023 09:13:02 +0400 Subject: [PATCH 23/33] `IncludeCommand` => `IncludeExec`. --- .../{TargetCommandFailed.php => TargetExecFailed.php} | 2 +- .../src/Preprocessor/Instructions/IncludeExample.php | 4 ++-- .../Instructions/{IncludeCommand.php => IncludeExec.php} | 8 ++++---- .../{IncludeCommandTest.php => IncludeExecTest.php} | 4 ++-- packages/documentator/src/Preprocessor/Preprocessor.php | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) rename packages/documentator/src/Preprocessor/Exceptions/{TargetCommandFailed.php => TargetExecFailed.php} (90%) rename packages/documentator/src/Preprocessor/Instructions/{IncludeCommand.php => IncludeExec.php} (83%) rename packages/documentator/src/Preprocessor/Instructions/{IncludeCommandTest.php => IncludeExecTest.php} (89%) diff --git a/packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php b/packages/documentator/src/Preprocessor/Exceptions/TargetExecFailed.php similarity index 90% rename from packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php rename to packages/documentator/src/Preprocessor/Exceptions/TargetExecFailed.php index 2107a3de8..84d25ae93 100644 --- a/packages/documentator/src/Preprocessor/Exceptions/TargetCommandFailed.php +++ b/packages/documentator/src/Preprocessor/Exceptions/TargetExecFailed.php @@ -6,7 +6,7 @@ use function sprintf; -class TargetCommandFailed extends InstructionFailed { +class TargetExecFailed extends InstructionFailed { public function __construct(string $path, string $target, Throwable $previous = null) { parent::__construct( $path, diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php index 130463951..10afb71ab 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -3,7 +3,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; use Exception; -use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetCommandFailed; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetExecFailed; use LastDragon_ru\LaraASP\Documentator\Utils\Path; use LastDragon_ru\LaraASP\Documentator\Utils\Process; @@ -42,7 +42,7 @@ public function process(string $path, string $target): string { try { $output = $this->process->run($command, dirname($path)); } catch (Exception $exception) { - throw new TargetCommandFailed($path, $target, $exception); + throw new TargetExecFailed($path, $target, $exception); } if (preg_match_all('/\R/u', $output) > static::Limit) { diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExec.php similarity index 83% rename from packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php rename to packages/documentator/src/Preprocessor/Instructions/IncludeExec.php index a83816cca..f681e8708 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeCommand.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExec.php @@ -3,14 +3,14 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; use Exception; -use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetCommandFailed; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetExecFailed; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Process; use function dirname; use function explode; -class IncludeCommand implements Instruction { +class IncludeExec implements Instruction { public function __construct( protected readonly Process $process, ) { @@ -18,7 +18,7 @@ public function __construct( } public static function getName(): string { - return 'include:command'; + return 'include:exec'; } public function process(string $path, string $target): string { @@ -28,7 +28,7 @@ public function process(string $path, string $target): string { dirname($path), ); } catch (Exception $exception) { - throw new TargetCommandFailed($path, $target, $exception); + throw new TargetExecFailed($path, $target, $exception); } } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExecTest.php similarity index 89% rename from packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php rename to packages/documentator/src/Preprocessor/Instructions/IncludeExecTest.php index 482384436..13dbc34c8 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeCommandTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExecTest.php @@ -13,7 +13,7 @@ * @internal */ #[CoversClass(IncludeFile::class)] -class IncludeCommandTest extends TestCase { +class IncludeExecTest extends TestCase { public function testProcess(): void { $path = 'current/working/directory/file.md'; $expected = 'result'; @@ -25,7 +25,7 @@ public function testProcess(): void { ->once() ->andReturn($expected); - $instance = $this->app->make(IncludeCommand::class, [ + $instance = $this->app->make(IncludeExec::class, [ 'process' => $process, ]); diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 5058d2330..9daabbc0f 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -4,9 +4,9 @@ use Illuminate\Container\Container; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; -use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeCommand; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeDocumentList; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExec; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeFile; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludePackageList; use LastDragon_ru\LaraASP\Documentator\Utils\Path; @@ -29,7 +29,7 @@ * | `` | `` | Description | * |-------------------|--------------------------------|----------------------------------------------------------| * | `include:file` | path to the file | Include content of the file as is. | - * | `include:command` | the command to execute | Execute the command and include output. | + * | `include:exec` | the command to execute | Execute the command and include output. | * | `include:example` | path to the example file | Include file in the code block + its output if possible. | * * Limitations: @@ -62,7 +62,7 @@ class Preprocessor { public function __construct() { $this->addInstruction(IncludeFile::class); - $this->addInstruction(IncludeCommand::class); + $this->addInstruction(IncludeExec::class); $this->addInstruction(IncludeExample::class); $this->addInstruction(IncludePackageList::class); $this->addInstruction(IncludeDocumentList::class); From 9b780a4d343dec8661c7092bdcb5a8fa9163c3e0 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:03:47 +0400 Subject: [PATCH 24/33] `IncludeDocumentList`/`IncludePackageList` will report error instead ignoring. --- .../Exceptions/DocumentTitleIsMissing.php | 31 +++++++++ .../PackageComposerJsonIsMissing.php | 31 +++++++++ .../Exceptions/PackageReadmeIsMissing.php | 31 +++++++++ .../Instructions/IncludeDocumentList.php | 3 + .../Instructions/IncludeDocumentListTest.php | 17 +++++ .../invalid/Document.md | 3 + .../{ => invalid}/WithoutTitle.md | 0 .../Instructions/IncludePackageList.php | 22 +++--- .../Instructions/IncludePackageListTest.md | 4 +- .../Instructions/IncludePackageListTest.php | 56 ++++++++++++++- .../package}/README.md | 0 .../package}/composer.json | 0 .../package}/README.md | 0 .../{ => no title}/package/composer.json | 0 .../package custom readme/CUSTOM.md | 0 .../package custom readme/README.md | 0 .../package custom readme/composer.json | 0 .../{ => packages}/package/README.md | 0 .../packages/package/composer.json | 5 ++ .../src/Preprocessor/Preprocessor.php | 69 +++++++++++-------- 20 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 packages/documentator/src/Preprocessor/Exceptions/DocumentTitleIsMissing.php create mode 100644 packages/documentator/src/Preprocessor/Exceptions/PackageComposerJsonIsMissing.php create mode 100644 packages/documentator/src/Preprocessor/Exceptions/PackageReadmeIsMissing.php create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/invalid/Document.md rename packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/{ => invalid}/WithoutTitle.md (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{not a package => invalid/package}/README.md (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{package no title => no readme/package}/composer.json (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{package no title => no title/package}/README.md (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{ => no title}/package/composer.json (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{ => packages}/package custom readme/CUSTOM.md (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{ => packages}/package custom readme/README.md (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{ => packages}/package custom readme/composer.json (100%) rename packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/{ => packages}/package/README.md (100%) create mode 100644 packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package/composer.json diff --git a/packages/documentator/src/Preprocessor/Exceptions/DocumentTitleIsMissing.php b/packages/documentator/src/Preprocessor/Exceptions/DocumentTitleIsMissing.php new file mode 100644 index 000000000..2ac043cde --- /dev/null +++ b/packages/documentator/src/Preprocessor/Exceptions/DocumentTitleIsMissing.php @@ -0,0 +1,31 @@ +document, + $path, + ), + $previous, + ); + } + + public function getDocument(): string { + return $this->document; + } +} diff --git a/packages/documentator/src/Preprocessor/Exceptions/PackageComposerJsonIsMissing.php b/packages/documentator/src/Preprocessor/Exceptions/PackageComposerJsonIsMissing.php new file mode 100644 index 000000000..1f839d3fb --- /dev/null +++ b/packages/documentator/src/Preprocessor/Exceptions/PackageComposerJsonIsMissing.php @@ -0,0 +1,31 @@ +package, + $path, + ), + $previous, + ); + } + + public function getPackage(): string { + return $this->package; + } +} diff --git a/packages/documentator/src/Preprocessor/Exceptions/PackageReadmeIsMissing.php b/packages/documentator/src/Preprocessor/Exceptions/PackageReadmeIsMissing.php new file mode 100644 index 000000000..9ef99f9d6 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Exceptions/PackageReadmeIsMissing.php @@ -0,0 +1,31 @@ +package, + $path, + ), + $previous, + ); + } + + public function getPackage(): string { + return $this->package; + } +} diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php index 3b2d62a2d..2d7da43b5 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentList.php @@ -3,6 +3,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; use LastDragon_ru\LaraASP\Documentator\Package; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\DocumentTitleIsMissing; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetIsNotDirectory; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Markdown; @@ -65,6 +66,8 @@ public function process(string $path, string $target): string { 'title' => $title, 'summary' => Markdown::getSummary($content), ]; + } else { + throw new DocumentTitleIsMissing($path, $target, Path::join($target, $file->getFilename())); } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php index 4d926a220..34661e2bc 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest.php @@ -2,6 +2,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\DocumentTitleIsMissing; use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase; use PHPUnit\Framework\Attributes\CoversClass; @@ -41,4 +42,20 @@ public function testProcessAnotherDirectory(): void { MARKDOWN, ); } + + public function testProcessWithoutTitle(): void { + $path = self::getTestData()->file('invalid/Document.md'); + $target = './'; + $instance = $this->app->make(IncludeDocumentList::class); + + self::expectExceptionObject( + new DocumentTitleIsMissing( + $path->getPathname(), + $target, + 'WithoutTitle.md', + ), + ); + + $instance->process($path->getPathname(), $target); + } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/invalid/Document.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/invalid/Document.md new file mode 100644 index 000000000..9a246aef7 --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/invalid/Document.md @@ -0,0 +1,3 @@ +# Document + +Document summary. diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/WithoutTitle.md b/packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/invalid/WithoutTitle.md similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/WithoutTitle.md rename to packages/documentator/src/Preprocessor/Instructions/IncludeDocumentListTest/invalid/WithoutTitle.md diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php index d9b8c7c29..a1eda3f20 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php @@ -4,6 +4,9 @@ use Exception; use LastDragon_ru\LaraASP\Documentator\Package; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\DocumentTitleIsMissing; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PackageComposerJsonIsMissing; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PackageReadmeIsMissing; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\TargetIsNotDirectory; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instruction; use LastDragon_ru\LaraASP\Documentator\Utils\Markdown; @@ -51,13 +54,13 @@ public function process(string $path, string $target): string { ->exclude('node_modules') ->directories(); - foreach ($directories as $directory) { + foreach ($directories as $package) { // Package? - $packagePath = $directory->getPathname(); + $packagePath = $package->getPathname(); $packageInfo = $this->getPackageInfo($packagePath); if (!$packageInfo) { - continue; + throw new PackageComposerJsonIsMissing($path, $target, Path::join($target, $package->getFilename())); } // Readme @@ -67,18 +70,21 @@ public function process(string $path, string $target): string { : false; if (!$readme || $content === false) { - continue; + throw new PackageReadmeIsMissing($path, $target, Path::join($target, $package->getFilename())); } // Extract - $title = Markdown::getTitle($content); + $packageTitle = Markdown::getTitle($content); + $readmePath = Path::join($target, $package->getFilename(), $readme); - if ($title) { + if ($packageTitle) { $packages[] = [ - 'path' => Path::join($target, $directory->getFilename(), $readme), - 'title' => $title, + 'path' => $readmePath, + 'title' => $packageTitle, 'summary' => Markdown::getSummary($content), ]; + } else { + throw new DocumentTitleIsMissing($path, $target, $readmePath); } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md index b7b49deaf..ede1f959b 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.md @@ -4,8 +4,8 @@ Summary text. -[Read more](). +[Read more](). ## The Package with custom readme -[Read more](). +[Read more](). diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php index 37e9912ca..50a97c4a2 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest.php @@ -2,6 +2,9 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\DocumentTitleIsMissing; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PackageComposerJsonIsMissing; +use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PackageReadmeIsMissing; use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase; use PHPUnit\Framework\Attributes\CoversClass; @@ -13,9 +16,10 @@ #[CoversClass(IncludePackageList::class)] class IncludePackageListTest extends TestCase { public function testProcess(): void { - $path = self::getTestData()->file('.md'); + $path = self::getTestData()->file('Document.md')->getPathname(); + $target = basename(self::getTestData()->path('/packages')); $instance = $this->app->make(IncludePackageList::class); - $actual = $instance->process($path->getPathname(), basename(self::getTestData()->path('/'))); + $actual = $instance->process($path, $target); self::assertEquals( self::getTestData()->content('.md'), @@ -26,4 +30,52 @@ public function testProcess(): void { MARKDOWN, ); } + + public function testProcessNotAPackage(): void { + $path = self::getTestData()->file('Document.md')->getPathname(); + $target = basename(self::getTestData()->path('/invalid')); + $instance = $this->app->make(IncludePackageList::class); + + self::expectExceptionObject( + new PackageComposerJsonIsMissing( + $path, + $target, + 'invalid/package', + ), + ); + + $instance->process($path, $target); + } + + public function testProcessNoReadme(): void { + $path = self::getTestData()->file('Document.md')->getPathname(); + $target = basename(self::getTestData()->path('/no readme')); + $instance = $this->app->make(IncludePackageList::class); + + self::expectExceptionObject( + new PackageReadmeIsMissing( + $path, + $target, + 'no readme/package', + ), + ); + + $instance->process($path, $target); + } + + public function testProcessNoTitle(): void { + $path = self::getTestData()->file('Document.md')->getPathname(); + $target = basename(self::getTestData()->path('/no title')); + $instance = $this->app->make(IncludePackageList::class); + + self::expectExceptionObject( + new DocumentTitleIsMissing( + $path, + $target, + 'no title/package/README.md', + ), + ); + + $instance->process($path, $target); + } } diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/not a package/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/invalid/package/README.md similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/not a package/README.md rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/invalid/package/README.md diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/no readme/package/composer.json similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/composer.json rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/no readme/package/composer.json diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/no title/package/README.md similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package no title/README.md rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/no title/package/README.md diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/no title/package/composer.json similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/composer.json rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/no title/package/composer.json diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/CUSTOM.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package custom readme/CUSTOM.md similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/CUSTOM.md rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package custom readme/CUSTOM.md diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package custom readme/README.md similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/README.md rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package custom readme/README.md diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package custom readme/composer.json similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package custom readme/composer.json rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package custom readme/composer.json diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/README.md b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package/README.md similarity index 100% rename from packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/package/README.md rename to packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package/README.md diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package/composer.json b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package/composer.json new file mode 100644 index 000000000..0a9113a4a --- /dev/null +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageListTest/packages/package/composer.json @@ -0,0 +1,5 @@ +{ + "name": "lastdragon-ru/lara-asp-documentator", + "homepage": "https://github.com/LastDragon-ru/lara-asp", + "readme": "README.md" +} diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 9daabbc0f..6efc872e2 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -2,6 +2,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Preprocessor; +use Exception; use Illuminate\Container\Container; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeDocumentList; @@ -96,53 +97,61 @@ protected function getInstruction(string $name): ?Instruction { public function process(string $path, string $string): string { $path = Path::normalize($path); $cache = []; - $result = preg_replace_callback( - pattern : static::Regexp, - callback: function (array $matches) use (&$cache, $path): string { - $hash = $this->getHash("{$matches['instruction']}={$matches['target']}"); - $content = $cache[$hash] ?? null; - $instruction = $this->getInstruction($matches['instruction']); - - if ($content === null) { - $target = rawurldecode($matches['target']); - $content = trim($instruction?->process($path, $target) ?? ''); - $cache[$hash] = $content; - } - - // Return - $warning = static::Warning; - $prefix = <<getHash("{$matches['instruction']}={$matches['target']}"); + $content = $cache[$hash] ?? null; + $instruction = $this->getInstruction($matches['instruction']); + + if ($content === null) { + $target = rawurldecode($matches['target']); + $content = trim($instruction?->process($path, $target) ?? ''); + $cache[$hash] = $content; + } + + // Return + $warning = static::Warning; + $prefix = << Date: Thu, 24 Aug 2023 10:40:46 +0400 Subject: [PATCH 25/33] Command description moved to `AsCommand` Attribute. --- packages/documentator/src/Commands/Preprocess.php | 11 ++++------- packages/documentator/src/Commands/Requirements.php | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/documentator/src/Commands/Preprocess.php b/packages/documentator/src/Commands/Preprocess.php index e23513ef6..c8bc8b3e7 100644 --- a/packages/documentator/src/Commands/Preprocess.php +++ b/packages/documentator/src/Commands/Preprocess.php @@ -12,16 +12,13 @@ use function file_put_contents; use function getcwd; -#[AsCommand(name: Preprocess::Name)] +#[AsCommand( + name : Preprocess::Name, + description: 'Preprocess Markdown files.', +)] class Preprocess extends Command { public const Name = Package::Name.':preprocess'; - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint - * @var string|null - */ - public $description = 'Preprocess Markdown files.'; - /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint * @var string diff --git a/packages/documentator/src/Commands/Requirements.php b/packages/documentator/src/Commands/Requirements.php index aedd8b4bf..fefcf4c2e 100644 --- a/packages/documentator/src/Commands/Requirements.php +++ b/packages/documentator/src/Commands/Requirements.php @@ -32,16 +32,13 @@ use const JSON_THROW_ON_ERROR; -#[AsCommand(name: Requirements::Name)] +#[AsCommand( + name : Requirements::Name, + description: 'Generates a table with the required versions of PHP/Laravel in Markdown format.', +)] class Requirements extends Command { public const Name = Package::Name.':requirements'; - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint - * @var string|null - */ - public $description = 'Generates a table with the required versions of PHP/Laravel in Markdown format.'; - /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint * @var string From bcaabf6bce239d8a71f60df19339f2bdbdc4505a Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 24 Aug 2023 11:31:26 +0400 Subject: [PATCH 26/33] `Preprocess` command will return help with all available Instructions. --- .../documentator/src/Commands/Preprocess.php | 69 +++++++++++++++++++ .../src/Preprocessor/Instruction.php | 4 ++ .../Instructions/IncludeDocumentList.php | 12 ++++ .../Instructions/IncludeExample.php | 12 ++++ .../Preprocessor/Instructions/IncludeExec.php | 8 +++ .../Preprocessor/Instructions/IncludeFile.php | 8 +++ .../Instructions/IncludePackageList.php | 11 +++ .../src/Preprocessor/Preprocessor.php | 20 +++--- .../src/Preprocessor/PreprocessorTest.php | 8 +++ 9 files changed, 144 insertions(+), 8 deletions(-) diff --git a/packages/documentator/src/Commands/Preprocess.php b/packages/documentator/src/Commands/Preprocess.php index c8bc8b3e7..11ef23e59 100644 --- a/packages/documentator/src/Commands/Preprocess.php +++ b/packages/documentator/src/Commands/Preprocess.php @@ -3,6 +3,7 @@ namespace LastDragon_ru\LaraASP\Documentator\Commands; use Illuminate\Console\Command; +use Illuminate\Container\Container; use LastDragon_ru\LaraASP\Core\Utils\Cast; use LastDragon_ru\LaraASP\Documentator\Package; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Preprocessor; @@ -11,7 +12,13 @@ use function file_put_contents; use function getcwd; +use function implode; +use function ksort; +use function strtr; +/** + * @see Preprocessor + */ #[AsCommand( name : Preprocess::Name, description: 'Preprocess Markdown files.', @@ -19,6 +26,30 @@ class Preprocess extends Command { public const Name = Package::Name.':preprocess'; + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint + * @var string + */ + public $help = <<<'HELP' + Replaces special instructions in Markdown. + + ```plain + []: + [=name]: + ``` + + ### Supported instructions: + + %instructions% + + ### Limitations: + + * `` will be processed everywhere in the file (eg within + the code block) and may give unpredictable results. + * `` cannot be inside text. + * Nested `` doesn't support. + HELP; + /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint * @var string @@ -49,4 +80,42 @@ public function __invoke(Preprocessor $preprocessor): void { }); } } + + public function getProcessedHelp(): string { + return strtr(parent::getProcessedHelp(), [ + '%instructions%' => $this->getInstructionsHelp(), + ]); + } + + protected function getInstructionsHelp(): string { + $preprocessor = Container::getInstance()->make(Preprocessor::class); + $instructions = $preprocessor->getInstructions(); + $help = []; + + foreach ($instructions as $instruction) { + $name = $instruction::getName(); + $desc = $instruction::getDescription(); + $target = $instruction::getTargetDescription(); + + if ($target !== null) { + $help[$name] = <<` - {$target} + + {$desc} + HELP; + } else { + $help[$name] = <<` directory. Each file + must have `# Header` as the first construction. The first paragraph + after the Header will be used as a summary. + DESC; + } + + public static function getTargetDescription(): ?string { + return 'Directory path.'; + } + public function process(string $path, string $target): string { // Directory? $root = Path::getPath(dirname($path), $target); diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php index 10afb71ab..216ae3293 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExample.php @@ -28,6 +28,18 @@ public static function getName(): string { return 'include:example'; } + public static function getDescription(): string { + return <<<'DESC' + Includes contents of the `` file as an example wrapped into + ` ```code block``` `. It also searches for `.run` file, execute + it if found, and include its result right after the code block. + DESC; + } + + public static function getTargetDescription(): ?string { + return 'Example file path.'; + } + public function process(string $path, string $target): string { $language = $this->getLanguage($path, $target); $content = trim(parent::process($path, $target)); diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeExec.php b/packages/documentator/src/Preprocessor/Instructions/IncludeExec.php index f681e8708..0ff90ff60 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeExec.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeExec.php @@ -21,6 +21,14 @@ public static function getName(): string { return 'include:exec'; } + public static function getDescription(): string { + return 'Executes the `` and returns result.'; + } + + public static function getTargetDescription(): ?string { + return 'Path to the executable.'; + } + public function process(string $path, string $target): string { try { return $this->process->run( diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php index 30cbd02ca..e6e7ed65c 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludeFile.php @@ -18,6 +18,14 @@ public static function getName(): string { return 'include:file'; } + public static function getDescription(): string { + return 'Includes the `` file.'; + } + + public static function getTargetDescription(): ?string { + return 'File path.'; + } + public function process(string $path, string $target): string { $file = Path::getPath(dirname($path), $target); $content = file_get_contents($file); diff --git a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php index a1eda3f20..ef0e0f20e 100644 --- a/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php +++ b/packages/documentator/src/Preprocessor/Instructions/IncludePackageList.php @@ -36,6 +36,17 @@ public static function getName(): string { return 'include:package-list'; } + public static function getDescription(): string { + return <<<'DESC' + Generates package list from `` directory. The readme file will be + used to determine package name and summary. + DESC; + } + + public static function getTargetDescription(): ?string { + return 'Directory path.'; + } + public function process(string $path, string $target): string { // Directory? $root = Path::getPath(dirname($path), $target); diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 6efc872e2..3eec64d0f 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Container\Container; +use LastDragon_ru\LaraASP\Documentator\Commands\Preprocess; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Exceptions\PreprocessFailed; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeDocumentList; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludeExample; @@ -12,6 +13,7 @@ use LastDragon_ru\LaraASP\Documentator\Preprocessor\Instructions\IncludePackageList; use LastDragon_ru\LaraASP\Documentator\Utils\Path; +use function array_column; use function preg_replace_callback; use function rawurldecode; use function sha1; @@ -25,14 +27,6 @@ * []: * [=name]: * - * Supported instructions: - * - * | `` | `` | Description | - * |-------------------|--------------------------------|----------------------------------------------------------| - * | `include:file` | path to the file | Include content of the file as is. | - * | `include:exec` | the command to execute | Execute the command and include output. | - * | `include:example` | path to the example file | Include file in the code block + its output if possible. | - * * Limitations: * - `` will be processed everywhere in the file (eg within the code * block) and may give unpredictable results. @@ -40,6 +34,9 @@ * - Nested `` doesn't supported. * * @todo Use https://github.com/thephpleague/commonmark? + * @todo Sync with {@see Preprocess} command + * + * @see Preprocess */ class Preprocessor { protected const Warning = 'Generated automatically. Do not edit.'; @@ -69,6 +66,13 @@ public function __construct() { $this->addInstruction(IncludeDocumentList::class); } + /** + * @return list> + */ + public function getInstructions(): array { + return array_column($this->instructions, 0); + } + /** * @param Instruction|class-string $instruction */ diff --git a/packages/documentator/src/Preprocessor/PreprocessorTest.php b/packages/documentator/src/Preprocessor/PreprocessorTest.php index 0a46077cf..f1a1e0ded 100644 --- a/packages/documentator/src/Preprocessor/PreprocessorTest.php +++ b/packages/documentator/src/Preprocessor/PreprocessorTest.php @@ -47,6 +47,14 @@ public static function getName(): string { return 'empty'; } + public static function getDescription(): string { + return ''; + } + + public static function getTargetDescription(): ?string { + return ''; + } + public function process(string $path, string $target): string { return ''; } From 7c69e0857b1dae1d9689e4f30b4c2917a2c9b28a Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:23:10 +0400 Subject: [PATCH 27/33] `Markdown` will allow comments before Header and Summary. --- packages/documentator/src/Utils/Markdown.php | 54 ++++++++++++++++--- .../documentator/src/Utils/MarkdownTest.php | 30 +++++++++++ 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/packages/documentator/src/Utils/Markdown.php b/packages/documentator/src/Utils/Markdown.php index f0386bf41..9063a2ca6 100644 --- a/packages/documentator/src/Utils/Markdown.php +++ b/packages/documentator/src/Utils/Markdown.php @@ -3,16 +3,20 @@ namespace LastDragon_ru\LaraASP\Documentator\Utils; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; +use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Block\Paragraph; +use League\CommonMark\Node\Node; use League\CommonMark\Parser\MarkdownParser; use function array_slice; use function implode; use function ltrim; use function preg_split; +use function str_ends_with; +use function str_starts_with; use function trim; class Markdown { @@ -31,9 +35,10 @@ public static function getTitle(string $string): ?string { * Returns the first paragraph right after `# Header` if present. */ public static function getSummary(string $string): ?string { - $node = static::getTitleNode($string)?->next(); - $summary = $node instanceof Paragraph - ? static::getText($string, $node) + $title = static::getTitleNode($string); + $summary = static::getFirstNode($title?->next(), Paragraph::class); + $summary = $summary + ? static::getText($string, $summary) : null; return $summary; @@ -48,10 +53,8 @@ protected static function getDocumentNode(string $string): Document { } protected static function getTitleNode(string $string): ?Heading { - $node = static::getDocumentNode($string)->firstChild(); - $header = $node instanceof Heading && $node->getLevel() === 1 - ? $node - : null; + $document = static::getDocumentNode($string); + $header = static::getFirstNode($document, Heading::class, static fn ($n) => $n->getLevel() === 1); return $header; } @@ -71,4 +74,41 @@ protected static function getText(string $string, ?AbstractBlock $node): ?string return $text; } + + /** + * @template T of Node + * + * @param class-string $class + * @param callable(T): bool $filter + * + * @return ?T + */ + protected static function getFirstNode(?Node $node, string $class, callable $filter = null): ?Node { + // Null? + if ($node === null) { + return null; + } + + // Wanted? + if ($node instanceof $class && ($filter === null || $filter($node))) { + return $node; + } + + // Comment? + if ( + $node instanceof HtmlBlock + && str_starts_with($node->getLiteral(), '') + ) { + return static::getFirstNode($node->next(), $class, $filter); + } + + // Document? + if ($node instanceof Document) { + return static::getFirstNode($node->firstChild(), $class, $filter); + } + + // Not found + return null; + } } diff --git a/packages/documentator/src/Utils/MarkdownTest.php b/packages/documentator/src/Utils/MarkdownTest.php index 7912aec32..0973db750 100644 --- a/packages/documentator/src/Utils/MarkdownTest.php +++ b/packages/documentator/src/Utils/MarkdownTest.php @@ -44,6 +44,18 @@ public function testGetTitle(): void { # Header + fsdfsdfsdf + MARKDOWN, + ), + ); + self::assertEquals( + 'Header', + Markdown::getTitle( + <<<'MARKDOWN' + + + # Header + fsdfsdfsdf MARKDOWN, ), @@ -103,6 +115,24 @@ public function testGetSummary(): void { # Header + fsdfsdfsdf + fsdfsdfsdf + MARKDOWN, + ), + ); + self::assertEquals( + <<<'TEXT' + fsdfsdfsdf + fsdfsdfsdf + TEXT, + Markdown::getSummary( + <<<'MARKDOWN' + + + # Header + + + fsdfsdfsdf fsdfsdfsdf MARKDOWN, From 87c7dec3718d955321d3a317025e971f4efe6d34 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:54:47 +0400 Subject: [PATCH 28/33] `Preprocess` command will use `Symfony\Component\Filesystem\Filesystem` instead of `file_put_contents`. --- .../documentator/src/Commands/Preprocess.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/documentator/src/Commands/Preprocess.php b/packages/documentator/src/Commands/Preprocess.php index 11ef23e59..29bf08ebc 100644 --- a/packages/documentator/src/Commands/Preprocess.php +++ b/packages/documentator/src/Commands/Preprocess.php @@ -8,9 +8,9 @@ use LastDragon_ru\LaraASP\Documentator\Package; use LastDragon_ru\LaraASP\Documentator\Preprocessor\Preprocessor; use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; -use function file_put_contents; use function getcwd; use function implode; use function ksort; @@ -58,7 +58,7 @@ class Preprocess extends Command { {path? : directory to process} SIGNATURE; - public function __invoke(Preprocessor $preprocessor): void { + public function __invoke(Filesystem $filesystem, Preprocessor $preprocessor): void { $cwd = getcwd(); $path = Cast::toString($this->argument('path') ?? $cwd); $finder = Finder::create() @@ -70,14 +70,18 @@ public function __invoke(Preprocessor $preprocessor): void { ->name('*.md'); foreach ($finder as $file) { - $this->components->task($file->getPathname(), static function () use ($preprocessor, $file): bool { - $path = $file->getPathname(); - $content = $file->getContents(); - $result = $preprocessor->process($path, $content); - - return $content === $result - || file_put_contents($path, $result) !== false; - }); + $this->components->task( + $file->getPathname(), + static function () use ($filesystem, $preprocessor, $file): void { + $path = $file->getPathname(); + $content = $file->getContents(); + $result = $preprocessor->process($path, $content); + + if ($content !== $result) { + $filesystem->dumpFile($path, $content); + } + }, + ); } } From 6de62b4b49ca737aa4da7a68eb99471fe3ac50bc Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:52:06 +0400 Subject: [PATCH 29/33] feat(documentator): Command `lara-asp-documentator:commands` to save commands help into Markdown files. --- packages/core/src/Utils/Cast.php | 13 ++ .../views/commands/markdown.blade.php | 47 ++++++ .../documentator/src/Commands/Commands.php | 92 ++++++++++++ .../src/Commands/CommandsTest.php | 142 ++++++++++++++++++ .../src/Commands/CommandsTest~a.md | 27 ++++ .../src/Commands/CommandsTest~b.md | 10 ++ packages/documentator/src/Provider.php | 2 + .../src/Utils/ArtisanSerializer.php | 85 +++++++++++ .../src/Utils/ArtisanSerializerTest.php | 45 ++++++ 9 files changed, 463 insertions(+) create mode 100644 packages/documentator/resources/views/commands/markdown.blade.php create mode 100644 packages/documentator/src/Commands/Commands.php create mode 100644 packages/documentator/src/Commands/CommandsTest.php create mode 100644 packages/documentator/src/Commands/CommandsTest~a.md create mode 100644 packages/documentator/src/Commands/CommandsTest~b.md create mode 100644 packages/documentator/src/Utils/ArtisanSerializer.php create mode 100644 packages/documentator/src/Utils/ArtisanSerializerTest.php diff --git a/packages/core/src/Utils/Cast.php b/packages/core/src/Utils/Cast.php index 9a333dd4d..3d70f4787 100644 --- a/packages/core/src/Utils/Cast.php +++ b/packages/core/src/Utils/Cast.php @@ -63,4 +63,17 @@ public static function toIterable(mixed $value): iterable { return $value; } + + /** + * @template T of object + * + * @param class-string $class + * + * @return T + */ + public static function to(string $class, mixed $value): object { + assert($value instanceof $class); + + return $value; + } } diff --git a/packages/documentator/resources/views/commands/markdown.blade.php b/packages/documentator/resources/views/commands/markdown.blade.php new file mode 100644 index 000000000..c724ac14d --- /dev/null +++ b/packages/documentator/resources/views/commands/markdown.blade.php @@ -0,0 +1,47 @@ + + + +# `{{ $command->getName() }}` +@if($command->getDescription()) + +{!! $command->getDescription() !!} +@endif + +## Usages + +@foreach(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) +* `{!! $usage !!}` +@endforeach +@if($command->getDescription() !== ($help = $command->getProcessedHelp())) + +## Description + +{!! $help !!} +@endif +@if($command->getDefinition()->getArguments()) + +## Arguments +@foreach($command->getDefinition()->getArguments() as $argument) + +### `{!! $serializer->getArgumentSignature($argument) !!}` + +{!! $argument->getDescription() ?: '_No description provided._' !!} +@endforeach +@endif +@if($command->getDefinition()->getOptions()) + +## Options +@foreach($command->getDefinition()->getOptions() as $option) + +### `{!! $serializer->getOptionSignature($option) !!}`@if($option->isNegatable()), `--no-{{ $option->getName() }}`@endif + + +{!! $option->getDescription() ?: '_No description provided._' !!} +@endforeach +@endif diff --git a/packages/documentator/src/Commands/Commands.php b/packages/documentator/src/Commands/Commands.php new file mode 100644 index 000000000..da7f9b972 --- /dev/null +++ b/packages/documentator/src/Commands/Commands.php @@ -0,0 +1,92 @@ +getApplication()); + $namespace = $application->findNamespace(Cast::toString($this->argument('namespace'))); + $target = Cast::toString($this->argument('target')); + $default = Cast::toBool($this->option('defaults')); + $commands = $application->all($namespace); + + // Cleanup + $this->components->task( + 'Prepare', + static function () use ($filesystem, $target): void { + if (is_dir($target)) { + $filesystem->remove( + Finder::create()->in($target), + ); + } else { + $filesystem->mkdir($target); + } + }, + ); + + // Process + foreach ($commands as $command) { + if ($command->isHidden()) { + continue; + } + + $this->components->task( + "Command: {$command->getName()}", + static function () use ($filesystem, $serializer, $namespace, $target, $default, $command): void { + // Default options? + if ($default) { + $command->mergeApplicationDefinition(); + } else { + $command->setDefinition( + $command->getNativeDefinition(), + ); + } + + // Render + $name = Str::after((string) $command->getName(), "{$namespace}:"); + $path = Path::getPath($target, "{$name}.md"); + $package = Package::Name; + $content = view("{$package}::commands.markdown", [ + 'serializer' => $serializer, + 'command' => $command, + ])->render(); + + $filesystem->dumpFile($path, $content); + }, + ); + } + } +} diff --git a/packages/documentator/src/Commands/CommandsTest.php b/packages/documentator/src/Commands/CommandsTest.php new file mode 100644 index 000000000..cddf7ae63 --- /dev/null +++ b/packages/documentator/src/Commands/CommandsTest.php @@ -0,0 +1,142 @@ + + // ========================================================================= + /** + * @inheritDoc + */ + protected function getPackageProviders(mixed $app): array { + return array_merge(parent::getPackageProviders($app), [ + CommandsTest_Provider::class, + ]); + } + // + + // + // ========================================================================= + public function testInvoke(): void { + $directory = self::getTempDirectory(); + + self::assertNotFalse( + file_put_contents(Path::join($directory, 'file.txt'), static::class), + ); + + $this->artisan("lara-asp-documentator:commands test-namespace {$directory}"); + + $files = iterator_to_array(Finder::create()->in($directory)->files()); + $files = array_reduce($files, static function (array $combined, SplFileInfo $file): array { + return array_merge($combined, [ + $file->getFilename() => $file->getContents(), + ]); + }, []); + + self::assertEquals( + [ + 'command-a.md' => self::getTestData()->content('~a.md'), + 'command-b.md' => self::getTestData()->content('~b.md'), + ], + $files, + ); + } + // +} + +// @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses +// @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class CommandsTest_Provider extends ServiceProvider { + public function boot(): void { + $this->commands( + CommandsTest_CommandA::class, + CommandsTest_CommandB::class, + CommandsTest_CommandC::class, + ); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +class CommandsTest_CommandA extends Command { + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint + * @var string + */ + protected $signature = <<<'SIGNATURE' + test-namespace:command-a + {arg-a : Argument a} + {arg-b? : Optional argument b} + {--a|option-a : Option A} + {--option-b= : Option B} + SIGNATURE; + + public function __invoke(): void { + throw new Exception('Should not be called.'); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +#[AsCommand( + name : 'test-namespace:command-b', + description: 'Command B description.', +)] +class CommandsTest_CommandB extends Command { + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint + * @var array + */ + protected $aliases = ['command-b-alias']; + + public function __invoke(): void { + throw new Exception('Should not be called.'); + } +} + +/** + * @internal + * @noinspection PhpMultipleClassesDeclarationsInOneFile + */ +#[AsCommand( + name: 'test-namespace:command-c', +)] +class CommandsTest_CommandC extends Command { + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint + * @var boolean + */ + protected $hidden = true; + + public function __invoke(): void { + throw new Exception('Should not be called.'); + } +} diff --git a/packages/documentator/src/Commands/CommandsTest~a.md b/packages/documentator/src/Commands/CommandsTest~a.md new file mode 100644 index 000000000..1fbd28e31 --- /dev/null +++ b/packages/documentator/src/Commands/CommandsTest~a.md @@ -0,0 +1,27 @@ + + +# `test-namespace:command-a` + +## Usages + +* `test-namespace:command-a [-a|--option-a] [--option-b [OPTION-B]] [--] []` + +## Arguments + +### `arg-a` + +Argument a + +### `arg-b?` + +Optional argument b + +## Options + +### `--a|option-a` + +Option A + +### `--option-b=` + +Option B diff --git a/packages/documentator/src/Commands/CommandsTest~b.md b/packages/documentator/src/Commands/CommandsTest~b.md new file mode 100644 index 000000000..eaccc4b83 --- /dev/null +++ b/packages/documentator/src/Commands/CommandsTest~b.md @@ -0,0 +1,10 @@ + + +# `test-namespace:command-b` + +Command B description. + +## Usages + +* `test-namespace:command-b` +* `command-b-alias` diff --git a/packages/documentator/src/Provider.php b/packages/documentator/src/Provider.php index f921d0898..3c1c79b2f 100644 --- a/packages/documentator/src/Provider.php +++ b/packages/documentator/src/Provider.php @@ -4,6 +4,7 @@ use Illuminate\Support\ServiceProvider; use LastDragon_ru\LaraASP\Core\Concerns\ProviderWithViews; +use LastDragon_ru\LaraASP\Documentator\Commands\Commands; use LastDragon_ru\LaraASP\Documentator\Commands\Preprocess; use LastDragon_ru\LaraASP\Documentator\Commands\Requirements; @@ -15,6 +16,7 @@ public function boot(): void { $this->commands( Requirements::class, Preprocess::class, + Commands::class, ); } diff --git a/packages/documentator/src/Utils/ArtisanSerializer.php b/packages/documentator/src/Utils/ArtisanSerializer.php new file mode 100644 index 000000000..48ef8df20 --- /dev/null +++ b/packages/documentator/src/Utils/ArtisanSerializer.php @@ -0,0 +1,85 @@ +getDefault() !== null; + $signature = $argument->getName(); + + if ($default) { + $signature .= '='; + } + + if (!$argument->isRequired() && !$default) { + $signature .= '?'; + } + + if ($argument->isArray()) { + $signature .= '*'; + } + + if ($default) { + $signature .= $this->getValue($argument->getDefault()); + } + + return $signature; + } + + public function getOptionSignature(InputOption $option): string { + $default = $option->getDefault() !== null && $option->acceptValue(); + $signature = '--'; + + if ($option->getShortcut()) { + $signature .= $option->getShortcut().'|'; + } + + $signature .= $option->getName(); + + if ($default || $option->isValueOptional()) { + $signature .= '='; + } + + if ($option->isNegatable()) { + // Not yet supported by Laravel :( + } + + if ($option->isArray()) { + $signature .= '*'; + } + + if ($default) { + $signature .= $this->getValue($option->getDefault()); + } + + return $signature; + } + + protected function getValue(mixed $value): string { + // Signature is a string so all should be fine. + return match (true) { + is_array($value) => implode(',', array_map($this->getValue(...), $value)), + is_bool($value) => ($value ? 'true' : 'false'), + is_scalar($value) => (string) $value, + default => '', + }; + } +} diff --git a/packages/documentator/src/Utils/ArtisanSerializerTest.php b/packages/documentator/src/Utils/ArtisanSerializerTest.php new file mode 100644 index 000000000..5c4aa87c4 --- /dev/null +++ b/packages/documentator/src/Utils/ArtisanSerializerTest.php @@ -0,0 +1,45 @@ +getArgumentSignature($argument)); + } + + #[TestWith(['--user'])] + #[TestWith(['--u|user'])] + #[TestWith(['--user=*'])] + #[TestWith(['--u|user=*'])] + #[TestWith(['--u|user=default'])] + #[TestWith(['--u|user=*default'])] + public function testGetOptionSignature(string $signature): void { + $parsed = Parser::parse("command {{$signature}}")[2] ?? []; + $option = reset($parsed); + + self::assertInstanceOf(InputOption::class, $option); + self::assertEquals($signature, (new ArtisanSerializer())->getOptionSignature($option)); + } +} From 6a0fc83280f4ec70e1bb344afb5088ae709e1676 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:08:34 +0400 Subject: [PATCH 30/33] `Preprocess` command fixes & Minor improvements. --- .../documentator/src/Commands/Preprocess.php | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/documentator/src/Commands/Preprocess.php b/packages/documentator/src/Commands/Preprocess.php index 29bf08ebc..a14d35c13 100644 --- a/packages/documentator/src/Commands/Preprocess.php +++ b/packages/documentator/src/Commands/Preprocess.php @@ -26,6 +26,14 @@ class Preprocess extends Command { public const Name = Package::Name.':preprocess'; + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint + * @var string + */ + public $signature = self::Name.<<<'SIGNATURE' + {path? : Directory to process.} + SIGNATURE; + /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint * @var string @@ -38,11 +46,11 @@ class Preprocess extends Command { [=name]: ``` - ### Supported instructions: + ### Supported instructions %instructions% - ### Limitations: + ### Limitations * `` will be processed everywhere in the file (eg within the code block) and may give unpredictable results. @@ -50,14 +58,6 @@ class Preprocess extends Command { * Nested `` doesn't support. HELP; - /** - * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint - * @var string - */ - public $signature = self::Name.<<<'SIGNATURE' - {path? : directory to process} - SIGNATURE; - public function __invoke(Filesystem $filesystem, Preprocessor $preprocessor): void { $cwd = getcwd(); $path = Cast::toString($this->argument('path') ?? $cwd); @@ -78,7 +78,7 @@ static function () use ($filesystem, $preprocessor, $file): void { $result = $preprocessor->process($path, $content); if ($content !== $result) { - $filesystem->dumpFile($path, $content); + $filesystem->dumpFile($path, $result); } }, ); @@ -86,13 +86,14 @@ static function () use ($filesystem, $preprocessor, $file): void { } public function getProcessedHelp(): string { + $preprocessor = Container::getInstance()->make(Preprocessor::class); + return strtr(parent::getProcessedHelp(), [ - '%instructions%' => $this->getInstructionsHelp(), + '%instructions%' => $this->getInstructionsHelp($preprocessor), ]); } - protected function getInstructionsHelp(): string { - $preprocessor = Container::getInstance()->make(Preprocessor::class); + protected function getInstructionsHelp(Preprocessor $preprocessor): string { $instructions = $preprocessor->getInstructions(); $help = []; @@ -103,7 +104,7 @@ protected function getInstructionsHelp(): string { if ($target !== null) { $help[$name] = <<` * `` - {$target} @@ -111,7 +112,7 @@ protected function getInstructionsHelp(): string { HELP; } else { $help[$name] = << Date: Fri, 25 Aug 2023 11:10:09 +0400 Subject: [PATCH 31/33] Documentation. --- .markdownlint.yaml | 7 +- packages/documentator/README.md | 26 +++++++ .../documentator/docs/commands/commands.md | 25 +++++++ .../documentator/docs/commands/preprocess.md | 68 +++++++++++++++++++ .../docs/commands/requirements.md | 15 ++++ 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 packages/documentator/docs/commands/commands.md create mode 100644 packages/documentator/docs/commands/preprocess.md create mode 100644 packages/documentator/docs/commands/requirements.md diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 4c3793ad5..c2a980a58 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -251,5 +251,10 @@ MD052: true MD053: # Ignored definitions ignored_definitions: [ - "//" + "//", + "include:document-list", + "include:example", + "include:exec", + "include:file", + "include:package-list", ] diff --git a/packages/documentator/README.md b/packages/documentator/README.md index 29dd8798c..cfcd022ff 100644 --- a/packages/documentator/README.md +++ b/packages/documentator/README.md @@ -5,3 +5,29 @@ > [Read more](https://github.com/LastDragon-ru/lara-asp). This package provides various utilities for documentation generation. + +# Commands + +[include:document-list]: ./docs/commands +[//]: # (start: eb736c56d36bfbf743249954931dda71ebb3dec0) +[//]: # (warning: Generated automatically. Do not edit.) + +## `lara-asp-documentator:commands` + +Saves help for each command in the `namespace` into a separate file in the `target` directory. + +[Read more](). + +## `lara-asp-documentator:preprocess` + +Preprocess Markdown files. + +[Read more](). + +## `lara-asp-documentator:requirements` + +Generates a table with the required versions of PHP/Laravel in Markdown format. + +[Read more](). + +[//]: # (end: eb736c56d36bfbf743249954931dda71ebb3dec0) diff --git a/packages/documentator/docs/commands/commands.md b/packages/documentator/docs/commands/commands.md new file mode 100644 index 000000000..2320de0cf --- /dev/null +++ b/packages/documentator/docs/commands/commands.md @@ -0,0 +1,25 @@ + + +# `lara-asp-documentator:commands` + +Saves help for each command in the `namespace` into a separate file in the `target` directory. + +## Usages + +* `lara-asp-documentator:commands [--defaults] [--] ` + +## Arguments + +### `namespace` + +The namespace of the commands. + +### `target` + +Directory to save generated files. It will be created if not exist. All files/directories inside it will be removed otherwise. + +## Options + +### `--defaults` + +Include application default arguments/options like `--help`, etc. diff --git a/packages/documentator/docs/commands/preprocess.md b/packages/documentator/docs/commands/preprocess.md new file mode 100644 index 000000000..064cdabeb --- /dev/null +++ b/packages/documentator/docs/commands/preprocess.md @@ -0,0 +1,68 @@ + + +# `lara-asp-documentator:preprocess` + +Preprocess Markdown files. + +## Usages + +* `lara-asp-documentator:preprocess []` + +## Description + +Replaces special instructions in Markdown. + +```plain +[]: +[=name]: +``` + +### Supported instructions + +#### `[include:document-list]: ` + +* `` - Directory path. + +Returns the list of `*.md` files in the `` directory. Each file +must have `# Header` as the first construction. The first paragraph +after the Header will be used as a summary. + +#### `[include:example]: ` + +* `` - Example file path. + +Includes contents of the `` file as an example wrapped into +` ```code block``` `. It also searches for `.run` file, execute +it if found, and include its result right after the code block. + +#### `[include:exec]: ` + +* `` - Path to the executable. + +Executes the `` and returns result. + +#### `[include:file]: ` + +* `` - File path. + +Includes the `` file. + +#### `[include:package-list]: ` + +* `` - Directory path. + +Generates package list from `` directory. The readme file will be +used to determine package name and summary. + +### Limitations + +* `` will be processed everywhere in the file (eg within + the code block) and may give unpredictable results. +* `` cannot be inside text. +* Nested `` doesn't support. + +## Arguments + +### `path?` + +Directory to process. diff --git a/packages/documentator/docs/commands/requirements.md b/packages/documentator/docs/commands/requirements.md new file mode 100644 index 000000000..cc2cbc126 --- /dev/null +++ b/packages/documentator/docs/commands/requirements.md @@ -0,0 +1,15 @@ + + +# `lara-asp-documentator:requirements` + +Generates a table with the required versions of PHP/Laravel in Markdown format. + +## Usages + +* `lara-asp-documentator:requirements []` + +## Arguments + +### `cwd?` + +working directory (should be a git repository) From 206e218ec0de7782d28c2e4fc54b3999c63b6a9d Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:20:57 +0400 Subject: [PATCH 32/33] Preprocessor will handle `[instruction]: `. --- .../documentator/src/Preprocessor/Preprocessor.php | 12 +++++++++--- .../src/Preprocessor/PreprocessorTest.php | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/documentator/src/Preprocessor/Preprocessor.php b/packages/documentator/src/Preprocessor/Preprocessor.php index 3eec64d0f..020ca5fe1 100644 --- a/packages/documentator/src/Preprocessor/Preprocessor.php +++ b/packages/documentator/src/Preprocessor/Preprocessor.php @@ -14,9 +14,12 @@ use LastDragon_ru\LaraASP\Documentator\Utils\Path; use function array_column; +use function mb_substr; use function preg_replace_callback; use function rawurldecode; use function sha1; +use function str_ends_with; +use function str_starts_with; use function trim; use const PREG_UNMATCHED_AS_NULL; @@ -43,7 +46,7 @@ class Preprocessor { protected const Regexp = <<<'REGEXP' /^ (?P - \[(?P[^\]=]+)(?:=[^]]+)?\]:\s(?P[^ ]+?) + \[(?P[^\]=]+)(?:=[^]]+)?\]:\s(?P(?:[^ ]+?)|(?:<[^>]+?>)) ) (?P\R \[\/\/\]:\s\#\s\(start:\s(?P[^)]+)\) @@ -107,12 +110,15 @@ public function process(string $path, string $string): string { $result = preg_replace_callback( pattern : static::Regexp, callback: function (array $matches) use (&$cache, $path): string { - $hash = $this->getHash("{$matches['instruction']}={$matches['target']}"); + $target = $matches['target']; + $target = str_starts_with($target, '<') && str_ends_with($target, '>') + ? mb_substr($target, 1, -1) + : rawurldecode($target); + $hash = $this->getHash("{$matches['instruction']}={$target}"); $content = $cache[$hash] ?? null; $instruction = $this->getInstruction($matches['instruction']); if ($content === null) { - $target = rawurldecode($matches['target']); $content = trim($instruction?->process($path, $target) ?? ''); $cache[$hash] = $content; } diff --git a/packages/documentator/src/Preprocessor/PreprocessorTest.php b/packages/documentator/src/Preprocessor/PreprocessorTest.php index f1a1e0ded..e0d0f8b75 100644 --- a/packages/documentator/src/Preprocessor/PreprocessorTest.php +++ b/packages/documentator/src/Preprocessor/PreprocessorTest.php @@ -20,7 +20,7 @@ public function testProcess(): void { [test]: ./path/to/file - [test]: ./path/to/file + [test]: <./path/to/file> [//]: # (start: hash) [test]: ./path/to/file @@ -84,7 +84,7 @@ public function process(string $path, string $target): string { [//]: # (end: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) - [test]: ./path/to/file + [test]: <./path/to/file> [//]: # (start: 8c3f20586897a62ee759aae56b703dd6cd11a8ad) [//]: # (warning: Generated automatically. Do not edit.) From 6df7bdfbfb67608dd4efc3d4c84849d84453846c Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:50:55 +0400 Subject: [PATCH 33/33] Tests fixes. --- .../src/Commands/CommandsTest.php | 24 +++++++-- .../src/Utils/ArtisanSerializerTest.php | 51 ++++++++++++++----- packages/documentator/src/Utils/Path.php | 8 +-- packages/documentator/src/Utils/PathTest.php | 6 ++- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/packages/documentator/src/Commands/CommandsTest.php b/packages/documentator/src/Commands/CommandsTest.php index cddf7ae63..ed8204289 100644 --- a/packages/documentator/src/Commands/CommandsTest.php +++ b/packages/documentator/src/Commands/CommandsTest.php @@ -37,13 +37,17 @@ protected function getPackageProviders(mixed $app): array { // // ========================================================================= public function testInvoke(): void { - $directory = self::getTempDirectory(); + $directory = Path::normalize(self::getTempDirectory()); self::assertNotFalse( file_put_contents(Path::join($directory, 'file.txt'), static::class), ); - $this->artisan("lara-asp-documentator:commands test-namespace {$directory}"); + $result = $this + ->withoutMockingConsoleOutput() + ->artisan("lara-asp-documentator:commands test-namespace {$directory}"); + + self::assertEquals(Command::SUCCESS, $result); $files = iterator_to_array(Finder::create()->in($directory)->files()); $files = array_reduce($files, static function (array $combined, SplFileInfo $file): array { @@ -107,16 +111,28 @@ public function __invoke(): void { * @noinspection PhpMultipleClassesDeclarationsInOneFile */ #[AsCommand( - name : 'test-namespace:command-b', - description: 'Command B description.', + name: 'test-namespace:command-b', )] class CommandsTest_CommandB extends Command { + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint + * @var string|null + */ + protected $description = 'Command B description.'; + /** * @phpcsSuppress SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingNativeTypeHint * @var array */ protected $aliases = ['command-b-alias']; + public function __construct() { + parent::__construct(); + + // @phpstan-ignore-next-line Required for Laravel v9 + $this->setAliases((array) $this->aliases); + } + public function __invoke(): void { throw new Exception('Should not be called.'); } diff --git a/packages/documentator/src/Utils/ArtisanSerializerTest.php b/packages/documentator/src/Utils/ArtisanSerializerTest.php index 5c4aa87c4..c0ac0e6ea 100644 --- a/packages/documentator/src/Utils/ArtisanSerializerTest.php +++ b/packages/documentator/src/Utils/ArtisanSerializerTest.php @@ -5,7 +5,6 @@ use Illuminate\Console\Parser; use LastDragon_ru\LaraASP\Documentator\Testing\Package\TestCase; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\TestWith; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; @@ -16,11 +15,11 @@ */ #[CoversClass(ArtisanSerializer::class)] class ArtisanSerializerTest extends TestCase { - #[TestWith(['user'])] - #[TestWith(['user?'])] - #[TestWith(['user*?'])] - #[TestWith(['user=default'])] - #[TestWith(['user=*default'])] + // + // ========================================================================= + /** + * @dataProvider dataProviderGetArgumentSignature + */ public function testGetArgumentSignature(string $signature): void { $parsed = Parser::parse("command {{$signature}}")[1] ?? []; $argument = reset($parsed); @@ -29,12 +28,9 @@ public function testGetArgumentSignature(string $signature): void { self::assertEquals($signature, (new ArtisanSerializer())->getArgumentSignature($argument)); } - #[TestWith(['--user'])] - #[TestWith(['--u|user'])] - #[TestWith(['--user=*'])] - #[TestWith(['--u|user=*'])] - #[TestWith(['--u|user=default'])] - #[TestWith(['--u|user=*default'])] + /** + * @dataProvider dataProviderGetOptionSignature + */ public function testGetOptionSignature(string $signature): void { $parsed = Parser::parse("command {{$signature}}")[2] ?? []; $option = reset($parsed); @@ -42,4 +38,35 @@ public function testGetOptionSignature(string $signature): void { self::assertInstanceOf(InputOption::class, $option); self::assertEquals($signature, (new ArtisanSerializer())->getOptionSignature($option)); } + // + + // + // ========================================================================= + /** + * @return array + */ + public static function dataProviderGetArgumentSignature(): array { + return [ + ['user'], + ['user?'], + ['user*?'], + ['user=default'], + ['user=*default'], + ]; + } + + /** + * @return array + */ + public static function dataProviderGetOptionSignature(): array { + return [ + ['--user'], + ['--u|user'], + ['--user=*'], + ['--u|user=*'], + ['--u|user=default'], + ['--u|user=*default'], + ]; + } + // } diff --git a/packages/documentator/src/Utils/Path.php b/packages/documentator/src/Utils/Path.php index ae5d06507..461b2dfb4 100644 --- a/packages/documentator/src/Utils/Path.php +++ b/packages/documentator/src/Utils/Path.php @@ -10,7 +10,7 @@ class Path { public static function getPath(string $root, string $path): string { $path = static::isRelative($path) - ? SymfonyPath::join(static::getDirname($root), $path) + ? static::join(static::getDirname($root), $path) : $path; $path = static::normalize($path); @@ -18,15 +18,15 @@ public static function getPath(string $root, string $path): string { } public static function getDirname(string $path): string { - return is_file($path) ? dirname($path) : $path; + return static::normalize(is_file($path) ? dirname($path) : $path); } public static function isRelative(string $path): bool { - return SymfonyPath::isRelative($path); + return SymfonyPath::isRelative(static::normalize($path)); } public static function isAbsolute(string $path): bool { - return SymfonyPath::isAbsolute($path); + return SymfonyPath::isAbsolute(static::normalize($path)); } public static function normalize(string $path): string { diff --git a/packages/documentator/src/Utils/PathTest.php b/packages/documentator/src/Utils/PathTest.php index 39d38feef..2e394b3f7 100644 --- a/packages/documentator/src/Utils/PathTest.php +++ b/packages/documentator/src/Utils/PathTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\Attributes\CoversClass; use function dirname; +use function str_replace; /** * @internal @@ -15,6 +16,9 @@ class PathTest extends TestCase { public function testGetPath(): void { self::assertEquals('/absolute/path/to/file', Path::getPath('any/path', '/absolute/path/./to/file')); self::assertEquals('/absolute/path/to/file', Path::getPath('/absolute/path', 'to/./file')); - self::assertEquals(dirname(__FILE__).'/to/file', Path::getPath(__FILE__, 'to/./file')); + self::assertEquals( + str_replace('\\', '/', dirname(__FILE__).'/to/file'), + Path::getPath(__FILE__, 'to/./file'), + ); } }