From 1228c5253893609f4a5e4925446eea451b71ebfc Mon Sep 17 00:00:00 2001 From: eko54r Date: Sat, 18 Apr 2026 16:10:57 +0700 Subject: [PATCH] update after stop build --- Dockerfile | 2 +- composer.lock | 342 +- vendor/composer/InstalledVersions.php | 38 +- vendor/composer/installed.json | 354 + vendor/composer/installed.php | 465 +- .../.agents/skills/release-minor/SKILL.md | 38 + .../knuckleswtf/scribe/.githooks/pre-commit | 36 + vendor/knuckleswtf/scribe/AGENTS.md | 82 + vendor/knuckleswtf/scribe/CHANGELOG.md | 115 + vendor/knuckleswtf/scribe/CONTRIBUTING.md | 2 + vendor/knuckleswtf/scribe/LICENSE.md | 21 + vendor/knuckleswtf/scribe/README.md | 34 + vendor/knuckleswtf/scribe/camel/BaseDTO.php | 172 + .../scribe/camel/BaseDTOCollection.php | 50 + vendor/knuckleswtf/scribe/camel/Camel.php | 231 + .../Extraction/ExtractedEndpointData.php | 169 + .../scribe/camel/Extraction/Metadata.php | 24 + .../scribe/camel/Extraction/Parameter.php | 32 + .../scribe/camel/Extraction/Response.php | 67 + .../camel/Extraction/ResponseCollection.php | 20 + .../scribe/camel/Extraction/ResponseField.php | 31 + .../camel/Output/OutputEndpointData.php | 355 + .../scribe/camel/Output/Parameter.php | 13 + vendor/knuckleswtf/scribe/composer.json | 98 + vendor/knuckleswtf/scribe/config/scribe.php | 259 + vendor/knuckleswtf/scribe/lang/scribe.php | 77 + vendor/knuckleswtf/scribe/logo-scribe.png | Bin 0 -> 10029 bytes .../resources/css/theme-default.print.css | 393 + .../resources/css/theme-default.style.css | 1094 ++ .../resources/css/theme-elements.style.css | 13690 ++++++++++++++++ .../resources/example_custom_endpoint.yaml | 53 + .../scribe/resources/images/navbar.png | Bin 0 -> 96 bytes .../scribe/resources/js/theme-default.js | 149 + .../scribe/resources/js/tryitout.js | 289 + .../views/components/badges/auth.blade.php | 3 + .../views/components/badges/base.blade.php | 1 + .../components/badges/deprecated.blade.php | 5 + .../components/badges/http-method.blade.php | 5 + .../views/components/field-details.blade.php | 76 + .../views/components/nested-fields.blade.php | 105 + .../views/external/elements.blade.php | 34 + .../views/external/rapidoc.blade.php | 23 + .../resources/views/external/scalar.blade.php | 27 + .../resources/views/markdown/auth.blade.php | 12 + .../resources/views/markdown/intro.blade.php | 13 + .../example-requests/bash.md.blade.php | 34 + .../example-requests/javascript.md.blade.php | 62 + .../example-requests/php.md.blade.php | 51 + .../example-requests/python.md.blade.php | 52 + .../views/themes/default/endpoint.blade.php | 190 + .../views/themes/default/groups.blade.php | 21 + .../views/themes/default/index.blade.php | 74 + .../views/themes/default/sidebar.blade.php | 69 + .../components/field-details.blade.php | 70 + .../components/nested-fields.blade.php | 38 + .../views/themes/elements/endpoint.blade.php | 281 + .../views/themes/elements/groups.blade.php | 28 + .../views/themes/elements/index.blade.php | 360 + .../views/themes/elements/sidebar.blade.php | 92 + .../themes/elements/try_it_out.blade.php | 322 + vendor/knuckleswtf/scribe/routes/laravel.php | 21 + .../scribe/src/Attributes/Authenticated.php | 16 + .../scribe/src/Attributes/BodyParam.php | 6 + .../scribe/src/Attributes/Deprecated.php | 16 + .../scribe/src/Attributes/Endpoint.php | 29 + .../scribe/src/Attributes/GenericParam.php | 57 + .../scribe/src/Attributes/Group.php | 45 + .../scribe/src/Attributes/Header.php | 20 + .../scribe/src/Attributes/QueryParam.php | 6 + .../scribe/src/Attributes/Response.php | 26 + .../scribe/src/Attributes/ResponseField.php | 20 + .../Attributes/ResponseFromApiResource.php | 45 + .../src/Attributes/ResponseFromFile.php | 25 + .../Attributes/ResponseFromTransformer.php | 23 + .../scribe/src/Attributes/Subgroup.php | 35 + .../scribe/src/Attributes/Unauthenticated.php | 12 + .../scribe/src/Attributes/UrlParam.php | 6 + .../scribe/src/Commands/DiffConfig.php | 35 + .../src/Commands/GenerateDocumentation.php | 257 + .../scribe/src/Commands/MakeStrategy.php | 32 + .../scribe/src/Commands/Upgrade.php | 182 + .../scribe/src/Commands/stubs/strategy.stub | 33 + .../knuckleswtf/scribe/src/Config/AuthIn.php | 13 + .../scribe/src/Config/Defaults.php | 52 + .../knuckleswtf/scribe/src/Config/helpers.php | 58 + .../src/Exceptions/CouldntFindFactory.php | 11 + .../src/Exceptions/CouldntGetRouteDetails.php | 11 + .../CouldntProcessValidationRule.php | 15 + .../CouldntStartDatabaseTransaction.php | 16 + .../DatabaseTransactionsNotSupported.php | 15 + .../scribe/src/Exceptions/GroupNotFound.php | 16 + .../ProblemParsingValidationRules.php | 15 + .../scribe/src/Exceptions/ScribeException.php | 9 + .../scribe/src/Extracting/ApiDetails.php | 169 + .../Extracting/DatabaseTransactionHelpers.php | 69 + .../scribe/src/Extracting/Extractor.php | 520 + .../Extracting/FindsFormRequestForMethod.php | 36 + .../Extracting/InstantiatesExampleModels.php | 109 + .../scribe/src/Extracting/MethodAstParser.php | 104 + .../scribe/src/Extracting/ParamHelpers.php | 270 + .../src/Extracting/ParsesValidationRules.php | 992 ++ .../scribe/src/Extracting/RouteDocBlocker.php | 98 + .../Shared/ApiResourceResponseTools.php | 136 + .../Extracting/Shared/ResponseFieldTools.php | 38 + .../Extracting/Shared/ResponseFileTools.php | 36 + .../Shared/TransformerResponseTools.php | 40 + .../Extracting/Shared/UrlParamsNormalizer.php | 226 + .../RequestValidate.php | 43 + .../RequestValidateFacade.php | 43 + .../ValidationRulesFinders/ThisValidate.php | 38 + .../ValidationRulesFinders/ValidatorMake.php | 35 + .../GetFromBodyParamAttribute.php | 14 + .../BodyParameters/GetFromBodyParamTag.php | 47 + .../BodyParameters/GetFromFormRequest.php | 23 + .../BodyParameters/GetFromInlineValidator.php | 21 + .../Strategies/GetFieldsFromTagStrategy.php | 68 + .../Strategies/GetFromFormRequestBase.php | 98 + .../Strategies/GetFromInlineValidatorBase.php | 207 + .../GetParamsFromAttributeStrategy.php | 52 + .../Headers/GetFromHeaderAttribute.php | 31 + .../Strategies/Headers/GetFromHeaderTag.php | 39 + .../Strategies/Metadata/GetFromDocBlocks.php | 156 + .../Metadata/GetFromMetadataAttributes.php | 51 + .../Strategies/PhpAttributeStrategy.php | 66 + .../QueryParameters/GetFromFormRequest.php | 22 + .../GetFromInlineValidator.php | 21 + .../GetFromQueryParamAttribute.php | 14 + .../QueryParameters/GetFromQueryParamTag.php | 80 + .../GetFromResponseFieldAttribute.php | 75 + .../GetFromResponseFieldTag.php | 125 + .../Strategies/Responses/ResponseCalls.php | 356 + .../Responses/UseApiResourceTags.php | 164 + .../Responses/UseResponseAttributes.php | 125 + .../Responses/UseResponseFileTag.php | 54 + .../Strategies/Responses/UseResponseTag.php | 50 + .../Responses/UseTransformerTags.php | 132 + .../src/Extracting/Strategies/StaticData.php | 30 + .../src/Extracting/Strategies/Strategy.php | 47 + .../TagStrategyWithFormRequestFallback.php | 46 + .../UrlParameters/GetFromLaravelAPI.php | 225 + .../GetFromUrlParamAttribute.php | 14 + .../UrlParameters/GetFromUrlParamTag.php | 54 + .../GroupedEndpointsContract.php | 10 + .../GroupedEndpointsFactory.php | 50 + .../GroupedEndpointsFromApp.php | 309 + .../GroupedEndpointsFromCamelDir.php | 27 + .../scribe/src/Matching/MatchedRoute.php | 50 + .../scribe/src/Matching/RouteMatcher.php | 72 + .../src/Matching/RouteMatcherInterface.php | 14 + vendor/knuckleswtf/scribe/src/Scribe.php | 98 + .../scribe/src/ScribeServiceProvider.php | 119 + .../scribe/src/Tools/AnnotationParser.php | 57 + .../scribe/src/Tools/BladeMarkdownEngine.php | 30 + .../scribe/src/Tools/ConfigDiffer.php | 99 + .../scribe/src/Tools/ConsoleOutputUtils.php | 174 + .../scribe/src/Tools/DocumentationConfig.php | 42 + .../scribe/src/Tools/ErrorHandlingUtils.php | 42 + .../knuckleswtf/scribe/src/Tools/Globals.php | 22 + .../scribe/src/Tools/MarkdownParser.php | 31 + .../scribe/src/Tools/PathConfig.php | 46 + .../scribe/src/Tools/RoutePatternMatcher.php | 31 + vendor/knuckleswtf/scribe/src/Tools/Utils.php | 341 + .../scribe/src/Tools/WritingUtils.php | 267 + .../src/Writing/CustomTranslationsLoader.php | 63 + .../scribe/src/Writing/ExternalHtmlWriter.php | 49 + .../scribe/src/Writing/HtmlWriter.php | 234 + .../scribe/src/Writing/OpenAPISpecWriter.php | 118 + .../OpenApiSpecGenerators/Base31Generator.php | 155 + .../OpenApiSpecGenerators/BaseGenerator.php | 706 + .../OpenApiGenerator.php | 63 + .../OverridesGenerator.php | 15 + .../SecurityGenerator.php | 60 + .../src/Writing/PostmanCollectionWriter.php | 404 + .../knuckleswtf/scribe/src/Writing/Writer.php | 288 + vendor/mpociot/reflection-docblock/.gitignore | 2 + .../mpociot/reflection-docblock/.travis.yml | 32 + vendor/mpociot/reflection-docblock/LICENSE | 21 + vendor/mpociot/reflection-docblock/README.md | 57 + .../mpociot/reflection-docblock/composer.json | 26 + .../mpociot/reflection-docblock/composer.lock | 1124 ++ .../reflection-docblock/phpunit.xml.dist | 14 + .../src/Mpociot/Reflection/DocBlock.php | 468 + .../Mpociot/Reflection/DocBlock/Context.php | 154 + .../Reflection/DocBlock/Description.php | 223 + .../Mpociot/Reflection/DocBlock/Location.php | 76 + .../Reflection/DocBlock/Serializer.php | 198 + .../src/Mpociot/Reflection/DocBlock/Tag.php | 377 + .../Reflection/DocBlock/Tag/AuthorTag.php | 131 + .../Reflection/DocBlock/Tag/CoversTag.php | 24 + .../Reflection/DocBlock/Tag/DeprecatedTag.php | 26 + .../Reflection/DocBlock/Tag/ExampleTag.php | 156 + .../Reflection/DocBlock/Tag/LinkTag.php | 81 + .../Reflection/DocBlock/Tag/MethodTag.php | 209 + .../Reflection/DocBlock/Tag/ParamTag.php | 119 + .../DocBlock/Tag/PropertyReadTag.php | 24 + .../Reflection/DocBlock/Tag/PropertyTag.php | 24 + .../DocBlock/Tag/PropertyWriteTag.php | 24 + .../Reflection/DocBlock/Tag/ReturnTag.php | 99 + .../Reflection/DocBlock/Tag/SeeTag.php | 81 + .../Reflection/DocBlock/Tag/SinceTag.php | 26 + .../Reflection/DocBlock/Tag/SourceTag.php | 137 + .../Reflection/DocBlock/Tag/ThrowsTag.php | 24 + .../Reflection/DocBlock/Tag/UsesTag.php | 24 + .../Reflection/DocBlock/Tag/VarTag.php | 24 + .../Reflection/DocBlock/Tag/VersionTag.php | 108 + .../Reflection/DocBlock/Type/Collection.php | 228 + .../Reflection/DocBlock/DescriptionTest.php | 245 + .../Reflection/DocBlock/Tag/CoversTagTest.php | 86 + .../DocBlock/Tag/DeprecatedTagTest.php | 115 + .../DocBlock/Tag/ExampleTagTest.php | 203 + .../Reflection/DocBlock/Tag/LinkTagTest.php | 87 + .../Reflection/DocBlock/Tag/MethodTagTest.php | 146 + .../Reflection/DocBlock/Tag/ParamTagTest.php | 118 + .../Reflection/DocBlock/Tag/ReturnTagTest.php | 102 + .../Reflection/DocBlock/Tag/SeeTagTest.php | 86 + .../Reflection/DocBlock/Tag/SinceTagTest.php | 115 + .../Reflection/DocBlock/Tag/SourceTagTest.php | 116 + .../Reflection/DocBlock/Tag/ThrowsTagTest.php | 102 + .../Reflection/DocBlock/Tag/UsesTagTest.php | 86 + .../Reflection/DocBlock/Tag/VarTagTest.php | 94 + .../DocBlock/Tag/VersionTagTest.php | 115 + .../Reflection/DocBlock/TagTest.php | 313 + .../DocBlock/Type/CollectionTest.php | 253 + .../phpDocumentor/Reflection/DocBlockTest.php | 337 + .../.github/workflows/unit-tests.yaml | 37 + vendor/parsedown/parsedown/LICENSE.txt | 20 + vendor/parsedown/parsedown/Parsedown.php | 1995 +++ vendor/parsedown/parsedown/README.md | 98 + vendor/parsedown/parsedown/composer.json | 33 + vendor/shalvah/upgrader/LICENSE | 21 + vendor/shalvah/upgrader/README.md | 54 + vendor/shalvah/upgrader/composer.json | 55 + vendor/shalvah/upgrader/phpstan.neon | 6 + .../shalvah/upgrader/src/ComparesAstNodes.php | 92 + vendor/shalvah/upgrader/src/ModifiesAsts.php | 80 + .../upgrader/src/ReadsAndWritesAsts.php | 84 + .../upgrader/src/UnresolveNamespaces.php | 21 + vendor/shalvah/upgrader/src/Upgrader.php | 277 + vendor/symfony/var-exporter/CHANGELOG.md | 49 + .../Exception/ClassNotFoundException.php | 20 + .../Exception/ExceptionInterface.php | 16 + .../var-exporter/Exception/LogicException.php | 16 + .../NotInstantiableTypeException.php | 20 + vendor/symfony/var-exporter/Hydrator.php | 78 + vendor/symfony/var-exporter/Instantiator.php | 59 + .../var-exporter/Internal/Exporter.php | 434 + .../var-exporter/Internal/Hydrator.php | 324 + .../Internal/LazyDecoratorTrait.php | 158 + .../Internal/LazyObjectRegistry.php | 99 + .../var-exporter/Internal/LazyObjectState.php | 48 + .../var-exporter/Internal/NamedClosure.php | 26 + .../var-exporter/Internal/Reference.php | 28 + .../var-exporter/Internal/Registry.php | 142 + .../symfony/var-exporter/Internal/Values.php | 25 + vendor/symfony/var-exporter/LICENSE | 19 + .../var-exporter/LazyObjectInterface.php | 32 + vendor/symfony/var-exporter/ProxyHelper.php | 452 + vendor/symfony/var-exporter/README.md | 94 + vendor/symfony/var-exporter/VarExporter.php | 114 + vendor/symfony/var-exporter/composer.json | 33 + 260 files changed, 43609 insertions(+), 226 deletions(-) create mode 100644 vendor/knuckleswtf/scribe/.agents/skills/release-minor/SKILL.md create mode 100644 vendor/knuckleswtf/scribe/.githooks/pre-commit create mode 100644 vendor/knuckleswtf/scribe/AGENTS.md create mode 100644 vendor/knuckleswtf/scribe/CHANGELOG.md create mode 100644 vendor/knuckleswtf/scribe/CONTRIBUTING.md create mode 100644 vendor/knuckleswtf/scribe/LICENSE.md create mode 100644 vendor/knuckleswtf/scribe/README.md create mode 100644 vendor/knuckleswtf/scribe/camel/BaseDTO.php create mode 100644 vendor/knuckleswtf/scribe/camel/BaseDTOCollection.php create mode 100644 vendor/knuckleswtf/scribe/camel/Camel.php create mode 100644 vendor/knuckleswtf/scribe/camel/Extraction/ExtractedEndpointData.php create mode 100644 vendor/knuckleswtf/scribe/camel/Extraction/Metadata.php create mode 100644 vendor/knuckleswtf/scribe/camel/Extraction/Parameter.php create mode 100644 vendor/knuckleswtf/scribe/camel/Extraction/Response.php create mode 100644 vendor/knuckleswtf/scribe/camel/Extraction/ResponseCollection.php create mode 100644 vendor/knuckleswtf/scribe/camel/Extraction/ResponseField.php create mode 100644 vendor/knuckleswtf/scribe/camel/Output/OutputEndpointData.php create mode 100644 vendor/knuckleswtf/scribe/camel/Output/Parameter.php create mode 100644 vendor/knuckleswtf/scribe/composer.json create mode 100644 vendor/knuckleswtf/scribe/config/scribe.php create mode 100644 vendor/knuckleswtf/scribe/lang/scribe.php create mode 100644 vendor/knuckleswtf/scribe/logo-scribe.png create mode 100644 vendor/knuckleswtf/scribe/resources/css/theme-default.print.css create mode 100644 vendor/knuckleswtf/scribe/resources/css/theme-default.style.css create mode 100644 vendor/knuckleswtf/scribe/resources/css/theme-elements.style.css create mode 100644 vendor/knuckleswtf/scribe/resources/example_custom_endpoint.yaml create mode 100644 vendor/knuckleswtf/scribe/resources/images/navbar.png create mode 100644 vendor/knuckleswtf/scribe/resources/js/theme-default.js create mode 100644 vendor/knuckleswtf/scribe/resources/js/tryitout.js create mode 100644 vendor/knuckleswtf/scribe/resources/views/components/badges/auth.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/components/badges/base.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/components/badges/deprecated.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/components/badges/http-method.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/components/field-details.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/components/nested-fields.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/external/elements.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/external/rapidoc.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/external/scalar.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/markdown/auth.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/markdown/intro.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/partials/example-requests/bash.md.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/partials/example-requests/javascript.md.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/partials/example-requests/php.md.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/partials/example-requests/python.md.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/default/endpoint.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/default/groups.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/default/index.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/default/sidebar.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/components/field-details.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/components/nested-fields.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/endpoint.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/groups.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/index.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/sidebar.blade.php create mode 100644 vendor/knuckleswtf/scribe/resources/views/themes/elements/try_it_out.blade.php create mode 100644 vendor/knuckleswtf/scribe/routes/laravel.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Authenticated.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/BodyParam.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Deprecated.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Endpoint.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/GenericParam.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Group.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Header.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/QueryParam.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Response.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/ResponseField.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/ResponseFromApiResource.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/ResponseFromFile.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/ResponseFromTransformer.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Subgroup.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/Unauthenticated.php create mode 100644 vendor/knuckleswtf/scribe/src/Attributes/UrlParam.php create mode 100644 vendor/knuckleswtf/scribe/src/Commands/DiffConfig.php create mode 100644 vendor/knuckleswtf/scribe/src/Commands/GenerateDocumentation.php create mode 100644 vendor/knuckleswtf/scribe/src/Commands/MakeStrategy.php create mode 100644 vendor/knuckleswtf/scribe/src/Commands/Upgrade.php create mode 100644 vendor/knuckleswtf/scribe/src/Commands/stubs/strategy.stub create mode 100644 vendor/knuckleswtf/scribe/src/Config/AuthIn.php create mode 100644 vendor/knuckleswtf/scribe/src/Config/Defaults.php create mode 100644 vendor/knuckleswtf/scribe/src/Config/helpers.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/CouldntFindFactory.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/CouldntGetRouteDetails.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/CouldntProcessValidationRule.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/CouldntStartDatabaseTransaction.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/DatabaseTransactionsNotSupported.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/GroupNotFound.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/ProblemParsingValidationRules.php create mode 100644 vendor/knuckleswtf/scribe/src/Exceptions/ScribeException.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/ApiDetails.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/DatabaseTransactionHelpers.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Extractor.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/FindsFormRequestForMethod.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/InstantiatesExampleModels.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/MethodAstParser.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/ParamHelpers.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/ParsesValidationRules.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/RouteDocBlocker.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ApiResourceResponseTools.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFieldTools.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFileTools.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/TransformerResponseTools.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/UrlParamsNormalizer.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidate.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidateFacade.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ThisValidate.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ValidatorMake.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamAttribute.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromInlineValidator.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFieldsFromTagStrategy.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromFormRequestBase.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromInlineValidatorBase.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderAttribute.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderTag.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromDocBlocks.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromMetadataAttributes.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/PhpAttributeStrategy.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromFormRequest.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromInlineValidator.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamAttribute.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldAttribute.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldTag.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/ResponseCalls.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseApiResourceTags.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseAttributes.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseFileTag.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseTag.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseTransformerTags.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/StaticData.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/Strategy.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/TagStrategyWithFormRequestFallback.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromLaravelAPI.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamAttribute.php create mode 100644 vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php create mode 100644 vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsContract.php create mode 100644 vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFactory.php create mode 100644 vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromApp.php create mode 100644 vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php create mode 100644 vendor/knuckleswtf/scribe/src/Matching/MatchedRoute.php create mode 100644 vendor/knuckleswtf/scribe/src/Matching/RouteMatcher.php create mode 100644 vendor/knuckleswtf/scribe/src/Matching/RouteMatcherInterface.php create mode 100644 vendor/knuckleswtf/scribe/src/Scribe.php create mode 100644 vendor/knuckleswtf/scribe/src/ScribeServiceProvider.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/AnnotationParser.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/BladeMarkdownEngine.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/ConfigDiffer.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/ConsoleOutputUtils.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/DocumentationConfig.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/ErrorHandlingUtils.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/Globals.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/MarkdownParser.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/PathConfig.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/RoutePatternMatcher.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/Utils.php create mode 100644 vendor/knuckleswtf/scribe/src/Tools/WritingUtils.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/CustomTranslationsLoader.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/ExternalHtmlWriter.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/HtmlWriter.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/Base31Generator.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/BaseGenerator.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OpenApiGenerator.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OverridesGenerator.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/SecurityGenerator.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/PostmanCollectionWriter.php create mode 100644 vendor/knuckleswtf/scribe/src/Writing/Writer.php create mode 100644 vendor/mpociot/reflection-docblock/.gitignore create mode 100644 vendor/mpociot/reflection-docblock/.travis.yml create mode 100644 vendor/mpociot/reflection-docblock/LICENSE create mode 100644 vendor/mpociot/reflection-docblock/README.md create mode 100644 vendor/mpociot/reflection-docblock/composer.json create mode 100644 vendor/mpociot/reflection-docblock/composer.lock create mode 100644 vendor/mpociot/reflection-docblock/phpunit.xml.dist create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Context.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Description.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Location.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Serializer.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/AuthorTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/CoversTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/DeprecatedTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ExampleTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/LinkTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/MethodTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ParamTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyReadTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyWriteTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ReturnTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SeeTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SinceTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SourceTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ThrowsTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/UsesTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VarTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VersionTag.php create mode 100644 vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Type/Collection.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/DescriptionTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/CoversTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/DeprecatedTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ExampleTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/LinkTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/MethodTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ParamTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ReturnTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SeeTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SinceTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SourceTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ThrowsTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/UsesTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VarTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VersionTagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/TagTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Type/CollectionTest.php create mode 100644 vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlockTest.php create mode 100644 vendor/parsedown/parsedown/.github/workflows/unit-tests.yaml create mode 100644 vendor/parsedown/parsedown/LICENSE.txt create mode 100644 vendor/parsedown/parsedown/Parsedown.php create mode 100644 vendor/parsedown/parsedown/README.md create mode 100644 vendor/parsedown/parsedown/composer.json create mode 100644 vendor/shalvah/upgrader/LICENSE create mode 100644 vendor/shalvah/upgrader/README.md create mode 100644 vendor/shalvah/upgrader/composer.json create mode 100644 vendor/shalvah/upgrader/phpstan.neon create mode 100644 vendor/shalvah/upgrader/src/ComparesAstNodes.php create mode 100644 vendor/shalvah/upgrader/src/ModifiesAsts.php create mode 100644 vendor/shalvah/upgrader/src/ReadsAndWritesAsts.php create mode 100644 vendor/shalvah/upgrader/src/UnresolveNamespaces.php create mode 100644 vendor/shalvah/upgrader/src/Upgrader.php create mode 100644 vendor/symfony/var-exporter/CHANGELOG.md create mode 100644 vendor/symfony/var-exporter/Exception/ClassNotFoundException.php create mode 100644 vendor/symfony/var-exporter/Exception/ExceptionInterface.php create mode 100644 vendor/symfony/var-exporter/Exception/LogicException.php create mode 100644 vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php create mode 100644 vendor/symfony/var-exporter/Hydrator.php create mode 100644 vendor/symfony/var-exporter/Instantiator.php create mode 100644 vendor/symfony/var-exporter/Internal/Exporter.php create mode 100644 vendor/symfony/var-exporter/Internal/Hydrator.php create mode 100644 vendor/symfony/var-exporter/Internal/LazyDecoratorTrait.php create mode 100644 vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php create mode 100644 vendor/symfony/var-exporter/Internal/LazyObjectState.php create mode 100644 vendor/symfony/var-exporter/Internal/NamedClosure.php create mode 100644 vendor/symfony/var-exporter/Internal/Reference.php create mode 100644 vendor/symfony/var-exporter/Internal/Registry.php create mode 100644 vendor/symfony/var-exporter/Internal/Values.php create mode 100644 vendor/symfony/var-exporter/LICENSE create mode 100644 vendor/symfony/var-exporter/LazyObjectInterface.php create mode 100644 vendor/symfony/var-exporter/ProxyHelper.php create mode 100644 vendor/symfony/var-exporter/README.md create mode 100644 vendor/symfony/var-exporter/VarExporter.php create mode 100644 vendor/symfony/var-exporter/composer.json diff --git a/Dockerfile b/Dockerfile index 28532de8..cddf3ce4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,7 +58,7 @@ ENV COMPOSER_ALLOW_SUPERUSER=1 \ # Install PHP dependencies (this layer will be cached) RUN composer config -g repo.packagist composer https://packagist.org \ - && composer install --no-interaction --no-progress --no-scripts --prefer-dist + && composer install --no-interaction --no-progress --no-scripts --prefer-dist --ignore-platform-reqs # Isolate the clean vendor directory #RUN mv vendor /vendor_clean diff --git a/composer.lock b/composer.lock index 89c88ca5..6057020a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b8877c500627363407035db84e7762b1", + "content-hash": "102ef9927311b93f5c6e22956143aee4", "packages": [ { "name": "ageekdev/laravel-barcode", @@ -11964,6 +11964,99 @@ }, "time": "2025-04-30T06:54:44+00:00" }, + { + "name": "knuckleswtf/scribe", + "version": "5.9.0", + "source": { + "type": "git", + "url": "https://github.com/knuckleswtf/scribe.git", + "reference": "170cb6f8c56f1b26db692fdd1f26574b1765c6f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knuckleswtf/scribe/zipball/170cb6f8c56f1b26db692fdd1f26574b1765c6f7", + "reference": "170cb6f8c56f1b26db692fdd1f26574b1765c6f7", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "ext-pdo": "*", + "fakerphp/faker": "^1.23.1", + "laravel/framework": "^9.21 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "league/flysystem": "^3.0", + "mpociot/reflection-docblock": "^1.0.1", + "nikic/php-parser": "^5.0", + "nunomaduro/collision": "^6.0 || ^7.0 || ^8.0", + "parsedown/parsedown": "^1.7", + "php": ">=8.1", + "ramsey/uuid": "^4.2.2", + "shalvah/upgrader": "^0.6.0", + "symfony/var-exporter": "^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^6.0 || ^7.0 || ^8.0" + }, + "replace": { + "mpociot/laravel-apidoc-generator": "*" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "laravel/legacy-factories": "^1.3.0", + "laravel/pint": "^1.20", + "league/fractal": "^0.20", + "nikic/fast-route": "^1.3", + "orchestra/testbench": "^7.0 || ^8.0 || ^9.10 || ^10.0 || ^11.0", + "pestphp/pest": "^1.21 || ^2.0 || ^3.0 || ^4.0", + "phpstan/phpstan": "^2.1.5", + "phpunit/phpunit": "^9.0 || ^10.0 || ^11.0 || ^12.0", + "spatie/ray": "^1.41", + "symfony/css-selector": "^6.0 || ^7.0 || ^8.0", + "symfony/dom-crawler": "^6.0 || ^7.0 || ^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Knuckles\\Scribe\\ScribeServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Config/helpers.php" + ], + "psr-4": { + "Knuckles\\Camel\\": "camel/", + "Knuckles\\Scribe\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shalvah" + } + ], + "description": "Generate API documentation for humans from your Laravel codebase.✍", + "homepage": "https://github.com/knuckleswtf/scribe", + "keywords": [ + "api", + "documentation", + "laravel" + ], + "support": { + "issues": "https://github.com/knuckleswtf/scribe/issues", + "source": "https://github.com/knuckleswtf/scribe/tree/5.9.0" + }, + "funding": [ + { + "url": "https://patreon.com/shalvah", + "type": "patreon" + } + ], + "time": "2026-03-21T00:39:27+00:00" + }, { "name": "laravel/breeze", "version": "v1.29.1", @@ -12239,6 +12332,59 @@ }, "time": "2024-05-16T03:13:13+00:00" }, + { + "name": "mpociot/reflection-docblock", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/mpociot/reflection-docblock.git", + "reference": "c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpociot/reflection-docblock/zipball/c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587", + "reference": "c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mpociot": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "issues": "https://github.com/mpociot/reflection-docblock/issues", + "source": "https://github.com/mpociot/reflection-docblock/tree/master" + }, + "time": "2016-06-20T20:53:12+00:00" + }, { "name": "nunomaduro/collision", "version": "v7.12.0", @@ -12335,6 +12481,56 @@ ], "time": "2025-03-14T22:35:49+00:00" }, + { + "name": "parsedown/parsedown", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/parsedown/parsedown.git", + "reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/parsedown/parsedown/zipball/96baaad00f71ba04d76e45b4620f54d3beabd6f7", + "reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.6" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/parsedown/parsedown/issues", + "source": "https://github.com/parsedown/parsedown/tree/1.8.0" + }, + "time": "2026-02-16T11:41:01+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -13836,6 +14032,64 @@ ], "time": "2023-02-07T11:34:05+00:00" }, + { + "name": "shalvah/upgrader", + "version": "0.6.0", + "source": { + "type": "git", + "url": "https://github.com/shalvah/upgrader.git", + "reference": "d95ed17fe9f5e1ee7d47ad835595f1af080a867f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shalvah/upgrader/zipball/d95ed17fe9f5e1ee7d47ad835595f1af080a867f", + "reference": "d95ed17fe9f5e1ee7d47ad835595f1af080a867f", + "shasum": "" + }, + "require": { + "illuminate/support": ">=8.0", + "nikic/php-parser": "^5.0", + "php": ">=8.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.2.0", + "pestphp/pest": "^1.21", + "phpstan/phpstan": "^1.0", + "spatie/ray": "^1.33" + }, + "type": "library", + "autoload": { + "psr-4": { + "Shalvah\\Upgrader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shalvah", + "email": "hello@shalvah.me" + } + ], + "description": "Create automatic upgrades for your package.", + "homepage": "http://github.com/shalvah/upgrader", + "keywords": [ + "upgrade" + ], + "support": { + "issues": "https://github.com/shalvah/upgrader/issues", + "source": "https://github.com/shalvah/upgrader/tree/0.6.0" + }, + "funding": [ + { + "url": "https://patreon.com/shalvah", + "type": "patreon" + } + ], + "time": "2024-02-20T11:51:46+00:00" + }, { "name": "spatie/backtrace", "version": "1.8.1", @@ -14217,6 +14471,86 @@ ], "time": "2025-02-20T13:13:55+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v8.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/15776bb07a91b089037da89f8832fa41d5fa6ec6", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-30T15:14:47+00:00" + }, { "name": "symfony/yaml", "version": "v7.4.1", @@ -14346,12 +14680,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.1" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 41bc143c..07b32ed6 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -28,7 +28,7 @@ class InstalledVersions { /** * @var mixed[]|null - * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; @@ -39,7 +39,7 @@ class InstalledVersions /** * @var array[] - * @psalm-var array}> + * @psalm-var array}> */ private static $installedByVendor = array(); @@ -98,7 +98,7 @@ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; } } @@ -119,7 +119,7 @@ public static function isInstalled($packageName, $includeDevRequirements = true) */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { - $constraint = $parser->parseConstraints($constraint); + $constraint = $parser->parseConstraints((string) $constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); @@ -243,7 +243,7 @@ public static function getInstallPath($packageName) /** * @return array - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { @@ -257,7 +257,7 @@ public static function getRootPackage() * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @@ -280,7 +280,7 @@ public static function getRawData() * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ public static function getAllRawData() { @@ -303,7 +303,7 @@ public static function getAllRawData() * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { @@ -313,7 +313,7 @@ public static function reload($data) /** * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ private static function getInstalled() { @@ -322,15 +322,20 @@ private static function getInstalled() } $installed = array(); + $copiedLocalDir = false; if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $required; + $copiedLocalDir = true; } } } @@ -340,12 +345,17 @@ private static function getInstalled() // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = require __DIR__ . '/installed.php'; + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; } else { self::$installed = array(); } } - $installed[] = self::$installed; + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } return $installed; } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 49fd5411..5d0a9f8e 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -3923,6 +3923,102 @@ }, "install-path": "../kavenegar/php" }, + { + "name": "knuckleswtf/scribe", + "version": "5.9.0", + "version_normalized": "5.9.0.0", + "source": { + "type": "git", + "url": "https://github.com/knuckleswtf/scribe.git", + "reference": "170cb6f8c56f1b26db692fdd1f26574b1765c6f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/knuckleswtf/scribe/zipball/170cb6f8c56f1b26db692fdd1f26574b1765c6f7", + "reference": "170cb6f8c56f1b26db692fdd1f26574b1765c6f7", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "ext-json": "*", + "ext-pdo": "*", + "fakerphp/faker": "^1.23.1", + "laravel/framework": "^9.21 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "league/flysystem": "^3.0", + "mpociot/reflection-docblock": "^1.0.1", + "nikic/php-parser": "^5.0", + "nunomaduro/collision": "^6.0 || ^7.0 || ^8.0", + "parsedown/parsedown": "^1.7", + "php": ">=8.1", + "ramsey/uuid": "^4.2.2", + "shalvah/upgrader": "^0.6.0", + "symfony/var-exporter": "^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^6.0 || ^7.0 || ^8.0" + }, + "replace": { + "mpociot/laravel-apidoc-generator": "*" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "laravel/legacy-factories": "^1.3.0", + "laravel/pint": "^1.20", + "league/fractal": "^0.20", + "nikic/fast-route": "^1.3", + "orchestra/testbench": "^7.0 || ^8.0 || ^9.10 || ^10.0 || ^11.0", + "pestphp/pest": "^1.21 || ^2.0 || ^3.0 || ^4.0", + "phpstan/phpstan": "^2.1.5", + "phpunit/phpunit": "^9.0 || ^10.0 || ^11.0 || ^12.0", + "spatie/ray": "^1.41", + "symfony/css-selector": "^6.0 || ^7.0 || ^8.0", + "symfony/dom-crawler": "^6.0 || ^7.0 || ^8.0" + }, + "time": "2026-03-21T00:39:27+00:00", + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Knuckles\\Scribe\\ScribeServiceProvider" + ] + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/Config/helpers.php" + ], + "psr-4": { + "Knuckles\\Camel\\": "camel/", + "Knuckles\\Scribe\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shalvah" + } + ], + "description": "Generate API documentation for humans from your Laravel codebase.✍", + "homepage": "https://github.com/knuckleswtf/scribe", + "keywords": [ + "api", + "documentation", + "laravel" + ], + "support": { + "issues": "https://github.com/knuckleswtf/scribe/issues", + "source": "https://github.com/knuckleswtf/scribe/tree/5.9.0" + }, + "funding": [ + { + "url": "https://patreon.com/shalvah", + "type": "patreon" + } + ], + "install-path": "../knuckleswtf/scribe" + }, { "name": "kreait/firebase-php", "version": "7.24.0", @@ -6530,6 +6626,62 @@ }, "install-path": "../mpdf/psr-log-aware-trait" }, + { + "name": "mpociot/reflection-docblock", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/mpociot/reflection-docblock.git", + "reference": "c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpociot/reflection-docblock/zipball/c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587", + "reference": "c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "time": "2016-06-20T20:53:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Mpociot": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "issues": "https://github.com/mpociot/reflection-docblock/issues", + "source": "https://github.com/mpociot/reflection-docblock/tree/master" + }, + "install-path": "../mpociot/reflection-docblock" + }, { "name": "mtdowling/jmespath.php", "version": "2.8.0", @@ -7623,6 +7775,59 @@ }, "install-path": "../paragonie/random_compat" }, + { + "name": "parsedown/parsedown", + "version": "1.8.0", + "version_normalized": "1.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/parsedown/parsedown.git", + "reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/parsedown/parsedown/zipball/96baaad00f71ba04d76e45b4620f54d3beabd6f7", + "reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.6" + }, + "time": "2026-02-16T11:41:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/parsedown/parsedown/issues", + "source": "https://github.com/parsedown/parsedown/tree/1.8.0" + }, + "install-path": "../parsedown/parsedown" + }, { "name": "paytm/paytmchecksum", "version": "v1.1.0", @@ -11141,6 +11346,67 @@ ], "install-path": "../setasign/fpdi" }, + { + "name": "shalvah/upgrader", + "version": "0.6.0", + "version_normalized": "0.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/shalvah/upgrader.git", + "reference": "d95ed17fe9f5e1ee7d47ad835595f1af080a867f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shalvah/upgrader/zipball/d95ed17fe9f5e1ee7d47ad835595f1af080a867f", + "reference": "d95ed17fe9f5e1ee7d47ad835595f1af080a867f", + "shasum": "" + }, + "require": { + "illuminate/support": ">=8.0", + "nikic/php-parser": "^5.0", + "php": ">=8.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.2.0", + "pestphp/pest": "^1.21", + "phpstan/phpstan": "^1.0", + "spatie/ray": "^1.33" + }, + "time": "2024-02-20T11:51:46+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Shalvah\\Upgrader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shalvah", + "email": "hello@shalvah.me" + } + ], + "description": "Create automatic upgrades for your package.", + "homepage": "http://github.com/shalvah/upgrader", + "keywords": [ + "upgrade" + ], + "support": { + "issues": "https://github.com/shalvah/upgrader/issues", + "source": "https://github.com/shalvah/upgrader/tree/0.6.0" + }, + "funding": [ + { + "url": "https://patreon.com/shalvah", + "type": "patreon" + } + ], + "install-path": "../shalvah/upgrader" + }, { "name": "simplesoftwareio/simple-qrcode", "version": "4.2.0", @@ -14289,6 +14555,89 @@ ], "install-path": "../symfony/var-dumper" }, + { + "name": "symfony/var-exporter", + "version": "v8.0.8", + "version_normalized": "8.0.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/15776bb07a91b089037da89f8832fa41d5fa6ec6", + "reference": "15776bb07a91b089037da89f8832fa41d5fa6ec6", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "time": "2026-03-30T15:14:47+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v8.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-exporter" + }, { "name": "symfony/yaml", "version": "v7.4.1", @@ -14945,11 +15294,14 @@ "fakerphp/faker", "filp/whoops", "hamcrest/hamcrest-php", + "knuckleswtf/scribe", "laravel/breeze", "laravel/pint", "laravel/sail", "mockery/mockery", + "mpociot/reflection-docblock", "nunomaduro/collision", + "parsedown/parsedown", "phar-io/manifest", "phar-io/version", "phpunit/php-code-coverage", @@ -14973,11 +15325,13 @@ "sebastian/recursion-context", "sebastian/type", "sebastian/version", + "shalvah/upgrader", "spatie/backtrace", "spatie/error-solutions", "spatie/flare-client-php", "spatie/ignition", "spatie/laravel-ignition", + "symfony/var-exporter", "symfony/yaml", "theseer/tokenizer" ] diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 13d9fa1e..8b6b6ac4 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,148 +1,148 @@ array( + 'name' => 'laravel/laravel', 'pretty_version' => 'dev-main', 'version' => 'dev-main', + 'reference' => 'e449146306f0d01ca98f763653faeff9b3dcc0e7', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '85369e0969bf2a7945e5fcad2a7df04599f8fd41', - 'name' => 'laravel/laravel', 'dev' => true, ), 'versions' => array( 'ageekdev/laravel-barcode' => array( 'pretty_version' => 'v1.3.0', 'version' => '1.3.0.0', + 'reference' => 'a06b96d32fc5f51399d84d0ffbd323f7b4798404', 'type' => 'library', 'install_path' => __DIR__ . '/../ageekdev/laravel-barcode', 'aliases' => array(), - 'reference' => 'a06b96d32fc5f51399d84d0ffbd323f7b4798404', 'dev_requirement' => false, ), 'anandsiddharth/laravel-paytm-wallet' => array( 'pretty_version' => 'v2.0.0', 'version' => '2.0.0.0', + 'reference' => '5342add0719e9c5ca94fdd13f766e6a810f2ef45', 'type' => 'library', 'install_path' => __DIR__ . '/../anandsiddharth/laravel-paytm-wallet', 'aliases' => array(), - 'reference' => '5342add0719e9c5ca94fdd13f766e6a810f2ef45', 'dev_requirement' => false, ), 'aws/aws-crt-php' => array( 'pretty_version' => 'v1.2.7', 'version' => '1.2.7.0', + 'reference' => 'd71d9906c7bb63a28295447ba12e74723bd3730e', 'type' => 'library', 'install_path' => __DIR__ . '/../aws/aws-crt-php', 'aliases' => array(), - 'reference' => 'd71d9906c7bb63a28295447ba12e74723bd3730e', 'dev_requirement' => false, ), 'aws/aws-sdk-php' => array( 'pretty_version' => '3.369.10', 'version' => '3.369.10.0', + 'reference' => 'e179090bf2d658be7be37afc146111966ba6f41b', 'type' => 'library', 'install_path' => __DIR__ . '/../aws/aws-sdk-php', 'aliases' => array(), - 'reference' => 'e179090bf2d658be7be37afc146111966ba6f41b', 'dev_requirement' => false, ), 'bacon/bacon-qr-code' => array( 'pretty_version' => '2.0.8', 'version' => '2.0.8.0', + 'reference' => '8674e51bb65af933a5ffaf1c308a660387c35c22', 'type' => 'library', 'install_path' => __DIR__ . '/../bacon/bacon-qr-code', 'aliases' => array(), - 'reference' => '8674e51bb65af933a5ffaf1c308a660387c35c22', 'dev_requirement' => false, ), 'barryvdh/laravel-dompdf' => array( 'pretty_version' => 'v3.1.1', 'version' => '3.1.1.0', + 'reference' => '8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d', 'type' => 'library', 'install_path' => __DIR__ . '/../barryvdh/laravel-dompdf', 'aliases' => array(), - 'reference' => '8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d', 'dev_requirement' => false, ), 'beste/clock' => array( 'pretty_version' => '3.0.0', 'version' => '3.0.0.0', + 'reference' => '7004b55fcd54737b539886244b3a3b2188181974', 'type' => 'library', 'install_path' => __DIR__ . '/../beste/clock', 'aliases' => array(), - 'reference' => '7004b55fcd54737b539886244b3a3b2188181974', 'dev_requirement' => false, ), 'beste/in-memory-cache' => array( 'pretty_version' => '1.4.0', 'version' => '1.4.0.0', + 'reference' => '1b9fbfcfbd0b657b90a759ac41b22748501c7f0e', 'type' => 'library', 'install_path' => __DIR__ . '/../beste/in-memory-cache', 'aliases' => array(), - 'reference' => '1b9fbfcfbd0b657b90a759ac41b22748501c7f0e', 'dev_requirement' => false, ), 'beste/json' => array( 'pretty_version' => '1.7.0', 'version' => '1.7.0.0', + 'reference' => '976525f1ce2323a4e044364269d60b402603e216', 'type' => 'library', 'install_path' => __DIR__ . '/../beste/json', 'aliases' => array(), - 'reference' => '976525f1ce2323a4e044364269d60b402603e216', 'dev_requirement' => false, ), 'brick/math' => array( 'pretty_version' => '0.12.3', 'version' => '0.12.3.0', + 'reference' => '866551da34e9a618e64a819ee1e01c20d8a588ba', 'type' => 'library', 'install_path' => __DIR__ . '/../brick/math', 'aliases' => array(), - 'reference' => '866551da34e9a618e64a819ee1e01c20d8a588ba', 'dev_requirement' => false, ), 'carbonphp/carbon-doctrine-types' => array( 'pretty_version' => '2.1.0', 'version' => '2.1.0.0', + 'reference' => '99f76ffa36cce3b70a4a6abce41dba15ca2e84cb', 'type' => 'library', 'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types', 'aliases' => array(), - 'reference' => '99f76ffa36cce3b70a4a6abce41dba15ca2e84cb', 'dev_requirement' => false, ), 'clue/stream-filter' => array( 'pretty_version' => 'v1.7.0', 'version' => '1.7.0.0', + 'reference' => '049509fef80032cb3f051595029ab75b49a3c2f7', 'type' => 'library', 'install_path' => __DIR__ . '/../clue/stream-filter', 'aliases' => array(), - 'reference' => '049509fef80032cb3f051595029ab75b49a3c2f7', 'dev_requirement' => false, ), 'composer/ca-bundle' => array( 'pretty_version' => '1.5.10', 'version' => '1.5.10.0', + 'reference' => '961a5e4056dd2e4a2eedcac7576075947c28bf63', 'type' => 'library', 'install_path' => __DIR__ . '/./ca-bundle', 'aliases' => array(), - 'reference' => '961a5e4056dd2e4a2eedcac7576075947c28bf63', 'dev_requirement' => false, ), 'composer/pcre' => array( 'pretty_version' => '3.3.2', 'version' => '3.3.2.0', + 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', 'type' => 'library', 'install_path' => __DIR__ . '/./pcre', 'aliases' => array(), - 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', 'dev_requirement' => false, ), 'composer/semver' => array( 'pretty_version' => '3.4.4', 'version' => '3.4.4.0', + 'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), - 'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95', 'dev_requirement' => false, ), 'cordoval/hamcrest-php' => array( @@ -154,19 +154,19 @@ 'cuyz/valinor' => array( 'pretty_version' => '2.3.1', 'version' => '2.3.1.0', + 'reference' => '212835b2efb89becd9881f4836e9b0b32ea105bf', 'type' => 'library', 'install_path' => __DIR__ . '/../cuyz/valinor', 'aliases' => array(), - 'reference' => '212835b2efb89becd9881f4836e9b0b32ea105bf', 'dev_requirement' => false, ), 'dasprid/enum' => array( 'pretty_version' => '1.0.7', 'version' => '1.0.7.0', + 'reference' => 'b5874fa9ed0043116c72162ec7f4fb50e02e7cce', 'type' => 'library', 'install_path' => __DIR__ . '/../dasprid/enum', 'aliases' => array(), - 'reference' => 'b5874fa9ed0043116c72162ec7f4fb50e02e7cce', 'dev_requirement' => false, ), 'davedevelopment/hamcrest-php' => array( @@ -178,334 +178,334 @@ 'dflydev/dot-access-data' => array( 'pretty_version' => 'v3.0.3', 'version' => '3.0.3.0', + 'reference' => 'a23a2bf4f31d3518f3ecb38660c95715dfead60f', 'type' => 'library', 'install_path' => __DIR__ . '/../dflydev/dot-access-data', 'aliases' => array(), - 'reference' => 'a23a2bf4f31d3518f3ecb38660c95715dfead60f', 'dev_requirement' => false, ), 'dipesh79/laravel-phonepe' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', + 'reference' => '286f3b26f1ab86f68543e1d40776b1f97cb14ca2', 'type' => 'library', 'install_path' => __DIR__ . '/../dipesh79/laravel-phonepe', 'aliases' => array(), - 'reference' => '286f3b26f1ab86f68543e1d40776b1f97cb14ca2', 'dev_requirement' => false, ), 'doctrine/annotations' => array( 'pretty_version' => '1.14.4', 'version' => '1.14.4.0', + 'reference' => '253dca476f70808a5aeed3a47cc2cc88c5cab915', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/annotations', 'aliases' => array(), - 'reference' => '253dca476f70808a5aeed3a47cc2cc88c5cab915', 'dev_requirement' => false, ), 'doctrine/common' => array( 'pretty_version' => '3.5.0', 'version' => '3.5.0.0', + 'reference' => 'd9ea4a54ca2586db781f0265d36bea731ac66ec5', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/common', 'aliases' => array(), - 'reference' => 'd9ea4a54ca2586db781f0265d36bea731ac66ec5', 'dev_requirement' => false, ), 'doctrine/deprecations' => array( 'pretty_version' => '1.1.5', 'version' => '1.1.5.0', + 'reference' => '459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/deprecations', 'aliases' => array(), - 'reference' => '459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38', 'dev_requirement' => false, ), 'doctrine/event-manager' => array( 'pretty_version' => '2.0.1', 'version' => '2.0.1.0', + 'reference' => 'b680156fa328f1dfd874fd48c7026c41570b9c6e', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/event-manager', 'aliases' => array(), - 'reference' => 'b680156fa328f1dfd874fd48c7026c41570b9c6e', 'dev_requirement' => false, ), 'doctrine/inflector' => array( 'pretty_version' => '2.1.0', 'version' => '2.1.0.0', + 'reference' => '6d6c96277ea252fc1304627204c3d5e6e15faa3b', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/inflector', 'aliases' => array(), - 'reference' => '6d6c96277ea252fc1304627204c3d5e6e15faa3b', 'dev_requirement' => false, ), 'doctrine/lexer' => array( 'pretty_version' => '2.1.1', 'version' => '2.1.1.0', + 'reference' => '861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/lexer', 'aliases' => array(), - 'reference' => '861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6', 'dev_requirement' => false, ), 'doctrine/persistence' => array( 'pretty_version' => '4.1.1', 'version' => '4.1.1.0', + 'reference' => 'b9c49ad3558bb77ef973f4e173f2e9c2eca9be09', 'type' => 'library', 'install_path' => __DIR__ . '/../doctrine/persistence', 'aliases' => array(), - 'reference' => 'b9c49ad3558bb77ef973f4e173f2e9c2eca9be09', 'dev_requirement' => false, ), 'dompdf/dompdf' => array( 'pretty_version' => 'v3.1.4', 'version' => '3.1.4.0', + 'reference' => 'db712c90c5b9868df3600e64e68da62e78a34623', 'type' => 'library', 'install_path' => __DIR__ . '/../dompdf/dompdf', 'aliases' => array(), - 'reference' => 'db712c90c5b9868df3600e64e68da62e78a34623', 'dev_requirement' => false, ), 'dompdf/php-font-lib' => array( 'pretty_version' => '1.0.1', 'version' => '1.0.1.0', + 'reference' => '6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d', 'type' => 'library', 'install_path' => __DIR__ . '/../dompdf/php-font-lib', 'aliases' => array(), - 'reference' => '6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d', 'dev_requirement' => false, ), 'dompdf/php-svg-lib' => array( 'pretty_version' => '1.0.2', 'version' => '1.0.2.0', + 'reference' => '8259ffb930817e72b1ff1caef5d226501f3dfeb1', 'type' => 'library', 'install_path' => __DIR__ . '/../dompdf/php-svg-lib', 'aliases' => array(), - 'reference' => '8259ffb930817e72b1ff1caef5d226501f3dfeb1', 'dev_requirement' => false, ), 'dragonmantank/cron-expression' => array( 'pretty_version' => 'v3.6.0', 'version' => '3.6.0.0', + 'reference' => 'd61a8a9604ec1f8c3d150d09db6ce98b32675013', 'type' => 'library', 'install_path' => __DIR__ . '/../dragonmantank/cron-expression', 'aliases' => array(), - 'reference' => 'd61a8a9604ec1f8c3d150d09db6ce98b32675013', 'dev_requirement' => false, ), 'egulias/email-validator' => array( 'pretty_version' => '4.0.4', 'version' => '4.0.4.0', + 'reference' => 'd42c8731f0624ad6bdc8d3e5e9a4524f68801cfa', 'type' => 'library', 'install_path' => __DIR__ . '/../egulias/email-validator', 'aliases' => array(), - 'reference' => 'd42c8731f0624ad6bdc8d3e5e9a4524f68801cfa', 'dev_requirement' => false, ), 'ezyang/htmlpurifier' => array( 'pretty_version' => 'v4.19.0', 'version' => '4.19.0.0', + 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', 'type' => 'library', 'install_path' => __DIR__ . '/../ezyang/htmlpurifier', 'aliases' => array(), - 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', 'dev_requirement' => false, ), 'fakerphp/faker' => array( 'pretty_version' => 'v1.24.1', 'version' => '1.24.1.0', + 'reference' => 'e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5', 'type' => 'library', 'install_path' => __DIR__ . '/../fakerphp/faker', 'aliases' => array(), - 'reference' => 'e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5', 'dev_requirement' => true, ), 'fedapay/fedapay-php' => array( 'pretty_version' => '0.4.7', 'version' => '0.4.7.0', + 'reference' => '150c196ae7778b10ab04dc82cd882cf1a3c332ce', 'type' => 'library', 'install_path' => __DIR__ . '/../fedapay/fedapay-php', 'aliases' => array(), - 'reference' => '150c196ae7778b10ab04dc82cd882cf1a3c332ce', 'dev_requirement' => false, ), 'fig/http-message-util' => array( 'pretty_version' => '1.1.5', 'version' => '1.1.5.0', + 'reference' => '9d94dc0154230ac39e5bf89398b324a86f63f765', 'type' => 'library', 'install_path' => __DIR__ . '/../fig/http-message-util', 'aliases' => array(), - 'reference' => '9d94dc0154230ac39e5bf89398b324a86f63f765', 'dev_requirement' => false, ), 'filp/whoops' => array( 'pretty_version' => '2.18.4', 'version' => '2.18.4.0', + 'reference' => 'd2102955e48b9fd9ab24280a7ad12ed552752c4d', 'type' => 'library', 'install_path' => __DIR__ . '/../filp/whoops', 'aliases' => array(), - 'reference' => 'd2102955e48b9fd9ab24280a7ad12ed552752c4d', 'dev_requirement' => true, ), 'firebase/php-jwt' => array( 'pretty_version' => 'v6.11.1', 'version' => '6.11.1.0', + 'reference' => 'd1e91ecf8c598d073d0995afa8cd5c75c6e19e66', 'type' => 'library', 'install_path' => __DIR__ . '/../firebase/php-jwt', 'aliases' => array(), - 'reference' => 'd1e91ecf8c598d073d0995afa8cd5c75c6e19e66', 'dev_requirement' => false, ), 'fruitcake/php-cors' => array( 'pretty_version' => 'v1.4.0', 'version' => '1.4.0.0', + 'reference' => '38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379', 'type' => 'library', 'install_path' => __DIR__ . '/../fruitcake/php-cors', 'aliases' => array(), - 'reference' => '38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379', 'dev_requirement' => false, ), 'google/auth' => array( 'pretty_version' => 'v1.50.0', 'version' => '1.50.0.0', + 'reference' => 'e1c26a718198e16d8a3c69b1cae136b73f959b0f', 'type' => 'library', 'install_path' => __DIR__ . '/../google/auth', 'aliases' => array(), - 'reference' => 'e1c26a718198e16d8a3c69b1cae136b73f959b0f', 'dev_requirement' => false, ), 'google/cloud-core' => array( 'pretty_version' => 'v1.69.0', 'version' => '1.69.0.0', + 'reference' => 'a35bcf9d79f7007eaaf325e00011d08f40494fb1', 'type' => 'library', 'install_path' => __DIR__ . '/../google/cloud-core', 'aliases' => array(), - 'reference' => 'a35bcf9d79f7007eaaf325e00011d08f40494fb1', 'dev_requirement' => false, ), 'google/cloud-storage' => array( 'pretty_version' => 'v1.49.0', 'version' => '1.49.0.0', + 'reference' => '30aefa19ce5af165cef8bb39c224cfa865461541', 'type' => 'library', 'install_path' => __DIR__ . '/../google/cloud-storage', 'aliases' => array(), - 'reference' => '30aefa19ce5af165cef8bb39c224cfa865461541', 'dev_requirement' => false, ), 'google/common-protos' => array( 'pretty_version' => '4.12.4', 'version' => '4.12.4.0', + 'reference' => '0127156899af0df2681bd42024c60bd5360d64e3', 'type' => 'library', 'install_path' => __DIR__ . '/../google/common-protos', 'aliases' => array(), - 'reference' => '0127156899af0df2681bd42024c60bd5360d64e3', 'dev_requirement' => false, ), 'google/gax' => array( 'pretty_version' => 'v1.40.0', 'version' => '1.40.0.0', + 'reference' => '1d3834d60b3f0794427c64d2b27d7c627fbba92c', 'type' => 'library', 'install_path' => __DIR__ . '/../google/gax', 'aliases' => array(), - 'reference' => '1d3834d60b3f0794427c64d2b27d7c627fbba92c', 'dev_requirement' => false, ), 'google/grpc-gcp' => array( 'pretty_version' => 'v0.4.1', 'version' => '0.4.1.0', + 'reference' => 'e585b7721bbe806ef45b5c52ae43dfc2bff89968', 'type' => 'library', 'install_path' => __DIR__ . '/../google/grpc-gcp', 'aliases' => array(), - 'reference' => 'e585b7721bbe806ef45b5c52ae43dfc2bff89968', 'dev_requirement' => false, ), 'google/longrunning' => array( 'pretty_version' => '0.6.0', 'version' => '0.6.0.0', + 'reference' => '226d3b5166eaa13754cc5e452b37872478e23375', 'type' => 'library', 'install_path' => __DIR__ . '/../google/longrunning', 'aliases' => array(), - 'reference' => '226d3b5166eaa13754cc5e452b37872478e23375', 'dev_requirement' => false, ), 'google/protobuf' => array( 'pretty_version' => 'v4.33.3', 'version' => '4.33.3.0', + 'reference' => '281537d44d6c270606354e65bfa75a0969dbd629', 'type' => 'library', 'install_path' => __DIR__ . '/../google/protobuf', 'aliases' => array(), - 'reference' => '281537d44d6c270606354e65bfa75a0969dbd629', 'dev_requirement' => false, ), 'graham-campbell/result-type' => array( 'pretty_version' => 'v1.1.4', 'version' => '1.1.4.0', + 'reference' => 'e01f4a821471308ba86aa202fed6698b6b695e3b', 'type' => 'library', 'install_path' => __DIR__ . '/../graham-campbell/result-type', 'aliases' => array(), - 'reference' => 'e01f4a821471308ba86aa202fed6698b6b695e3b', 'dev_requirement' => false, ), 'grpc/grpc' => array( 'pretty_version' => '1.74.0', 'version' => '1.74.0.0', + 'reference' => '32bf4dba256d60d395582fb6e4e8d3936bcdb713', 'type' => 'library', 'install_path' => __DIR__ . '/../grpc/grpc', 'aliases' => array(), - 'reference' => '32bf4dba256d60d395582fb6e4e8d3936bcdb713', 'dev_requirement' => false, ), 'guzzlehttp/guzzle' => array( 'pretty_version' => '7.10.0', 'version' => '7.10.0.0', + 'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', 'aliases' => array(), - 'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4', 'dev_requirement' => false, ), 'guzzlehttp/promises' => array( 'pretty_version' => '2.3.0', 'version' => '2.3.0.0', + 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/promises', 'aliases' => array(), - 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', 'dev_requirement' => false, ), 'guzzlehttp/psr7' => array( 'pretty_version' => '2.8.0', 'version' => '2.8.0.0', + 'reference' => '21dc724a0583619cd1652f673303492272778051', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), - 'reference' => '21dc724a0583619cd1652f673303492272778051', 'dev_requirement' => false, ), 'guzzlehttp/uri-template' => array( 'pretty_version' => 'v1.0.5', 'version' => '1.0.5.0', + 'reference' => '4f4bbd4e7172148801e76e3decc1e559bdee34e1', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/uri-template', 'aliases' => array(), - 'reference' => '4f4bbd4e7172148801e76e3decc1e559bdee34e1', 'dev_requirement' => false, ), 'hamcrest/hamcrest-php' => array( 'pretty_version' => 'v2.1.1', 'version' => '2.1.1.0', + 'reference' => 'f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487', 'type' => 'library', 'install_path' => __DIR__ . '/../hamcrest/hamcrest-php', 'aliases' => array(), - 'reference' => 'f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487', 'dev_requirement' => true, ), 'hardevine/shoppingcart' => array( 'pretty_version' => '3.4', 'version' => '3.4.0.0', + 'reference' => '023025ca6f2106b82842da146b2895aeb18a9eeb', 'type' => 'library', 'install_path' => __DIR__ . '/../hardevine/shoppingcart', 'aliases' => array(), - 'reference' => '023025ca6f2106b82842da146b2895aeb18a9eeb', 'dev_requirement' => false, ), 'illuminate/auth' => array( @@ -709,21 +709,30 @@ 'karim007/laravel-bkash-tokenize' => array( 'pretty_version' => 'v2.4', 'version' => '2.4.0.0', + 'reference' => 'f3ff1eaa075697985da4137c6632967139476452', 'type' => 'library', 'install_path' => __DIR__ . '/../karim007/laravel-bkash-tokenize', 'aliases' => array(), - 'reference' => 'f3ff1eaa075697985da4137c6632967139476452', 'dev_requirement' => false, ), 'kavenegar/php' => array( 'pretty_version' => 'v1.2.2', 'version' => '1.2.2.0', + 'reference' => '884561b8d8c91b01f7db25284c2be9923c809412', 'type' => 'library', 'install_path' => __DIR__ . '/../kavenegar/php', 'aliases' => array(), - 'reference' => '884561b8d8c91b01f7db25284c2be9923c809412', 'dev_requirement' => false, ), + 'knuckleswtf/scribe' => array( + 'pretty_version' => '5.9.0', + 'version' => '5.9.0.0', + 'reference' => '170cb6f8c56f1b26db692fdd1f26574b1765c6f7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../knuckleswtf/scribe', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'kodova/hamcrest-php' => array( 'dev_requirement' => true, 'replaced' => array( @@ -733,318 +742,333 @@ 'kreait/firebase-php' => array( 'pretty_version' => '7.24.0', 'version' => '7.24.0.0', + 'reference' => '7e2c321265dcb2eeea6fdb72e3819db684e0d4be', 'type' => 'library', 'install_path' => __DIR__ . '/../kreait/firebase-php', 'aliases' => array(), - 'reference' => '7e2c321265dcb2eeea6fdb72e3819db684e0d4be', 'dev_requirement' => false, ), 'kreait/firebase-tokens' => array( 'pretty_version' => '5.3.0', 'version' => '5.3.0.0', + 'reference' => '5dc119c8c29fcab1fb3a35ed5164dee0d33e4430', 'type' => 'library', 'install_path' => __DIR__ . '/../kreait/firebase-tokens', 'aliases' => array(), - 'reference' => '5dc119c8c29fcab1fb3a35ed5164dee0d33e4430', 'dev_requirement' => false, ), 'laravel/breeze' => array( 'pretty_version' => 'v1.29.1', 'version' => '1.29.1.0', + 'reference' => '22c53b84b7fff91b01a318d71a10dfc251e92849', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/breeze', 'aliases' => array(), - 'reference' => '22c53b84b7fff91b01a318d71a10dfc251e92849', 'dev_requirement' => true, ), 'laravel/framework' => array( 'pretty_version' => 'v10.50.0', 'version' => '10.50.0.0', + 'reference' => 'fc41c8ceb4d4a55b23d4030ef4ed86383e4b2bc3', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/framework', 'aliases' => array(), - 'reference' => 'fc41c8ceb4d4a55b23d4030ef4ed86383e4b2bc3', 'dev_requirement' => false, ), 'laravel/laravel' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', + 'reference' => 'e449146306f0d01ca98f763653faeff9b3dcc0e7', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '85369e0969bf2a7945e5fcad2a7df04599f8fd41', 'dev_requirement' => false, ), 'laravel/pint' => array( 'pretty_version' => 'v1.27.0', 'version' => '1.27.0.0', + 'reference' => 'c67b4195b75491e4dfc6b00b1c78b68d86f54c90', 'type' => 'project', 'install_path' => __DIR__ . '/../laravel/pint', 'aliases' => array(), - 'reference' => 'c67b4195b75491e4dfc6b00b1c78b68d86f54c90', 'dev_requirement' => true, ), 'laravel/prompts' => array( 'pretty_version' => 'v0.1.25', 'version' => '0.1.25.0', + 'reference' => '7b4029a84c37cb2725fc7f011586e2997040bc95', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/prompts', 'aliases' => array(), - 'reference' => '7b4029a84c37cb2725fc7f011586e2997040bc95', 'dev_requirement' => false, ), 'laravel/sail' => array( 'pretty_version' => 'v1.52.0', 'version' => '1.52.0.0', + 'reference' => '64ac7d8abb2dbcf2b76e61289451bae79066b0b3', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/sail', 'aliases' => array(), - 'reference' => '64ac7d8abb2dbcf2b76e61289451bae79066b0b3', 'dev_requirement' => true, ), 'laravel/sanctum' => array( 'pretty_version' => 'v3.3.3', 'version' => '3.3.3.0', + 'reference' => '8c104366459739f3ada0e994bcd3e6fd681ce3d5', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/sanctum', 'aliases' => array(), - 'reference' => '8c104366459739f3ada0e994bcd3e6fd681ce3d5', 'dev_requirement' => false, ), 'laravel/serializable-closure' => array( 'pretty_version' => 'v1.3.7', 'version' => '1.3.7.0', + 'reference' => '4f48ade902b94323ca3be7646db16209ec76be3d', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/serializable-closure', 'aliases' => array(), - 'reference' => '4f48ade902b94323ca3be7646db16209ec76be3d', 'dev_requirement' => false, ), 'laravel/socialite' => array( 'pretty_version' => 'v5.24.1', 'version' => '5.24.1.0', + 'reference' => '25e28c14d55404886777af1d77cf030e0f633142', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/socialite', 'aliases' => array(), - 'reference' => '25e28c14d55404886777af1d77cf030e0f633142', 'dev_requirement' => false, ), 'laravel/tinker' => array( 'pretty_version' => 'v2.11.0', 'version' => '2.11.0.0', + 'reference' => '3d34b97c9a1747a81a3fde90482c092bd8b66468', 'type' => 'library', 'install_path' => __DIR__ . '/../laravel/tinker', 'aliases' => array(), - 'reference' => '3d34b97c9a1747a81a3fde90482c092bd8b66468', 'dev_requirement' => false, ), 'lcobucci/jwt' => array( 'pretty_version' => '5.6.0', 'version' => '5.6.0.0', + 'reference' => 'bb3e9f21e4196e8afc41def81ef649c164bca25e', 'type' => 'library', 'install_path' => __DIR__ . '/../lcobucci/jwt', 'aliases' => array(), - 'reference' => 'bb3e9f21e4196e8afc41def81ef649c164bca25e', 'dev_requirement' => false, ), 'league/commonmark' => array( 'pretty_version' => '2.8.0', 'version' => '2.8.0.0', + 'reference' => '4efa10c1e56488e658d10adf7b7b7dcd19940bfb', 'type' => 'library', 'install_path' => __DIR__ . '/../league/commonmark', 'aliases' => array(), - 'reference' => '4efa10c1e56488e658d10adf7b7b7dcd19940bfb', 'dev_requirement' => false, ), 'league/config' => array( 'pretty_version' => 'v1.2.0', 'version' => '1.2.0.0', + 'reference' => '754b3604fb2984c71f4af4a9cbe7b57f346ec1f3', 'type' => 'library', 'install_path' => __DIR__ . '/../league/config', 'aliases' => array(), - 'reference' => '754b3604fb2984c71f4af4a9cbe7b57f346ec1f3', 'dev_requirement' => false, ), 'league/flysystem' => array( 'pretty_version' => '3.30.2', 'version' => '3.30.2.0', + 'reference' => '5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277', 'type' => 'library', 'install_path' => __DIR__ . '/../league/flysystem', 'aliases' => array(), - 'reference' => '5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277', 'dev_requirement' => false, ), 'league/flysystem-local' => array( 'pretty_version' => '3.30.2', 'version' => '3.30.2.0', + 'reference' => 'ab4f9d0d672f601b102936aa728801dd1a11968d', 'type' => 'library', 'install_path' => __DIR__ . '/../league/flysystem-local', 'aliases' => array(), - 'reference' => 'ab4f9d0d672f601b102936aa728801dd1a11968d', 'dev_requirement' => false, ), 'league/mime-type-detection' => array( 'pretty_version' => '1.16.0', 'version' => '1.16.0.0', + 'reference' => '2d6702ff215bf922936ccc1ad31007edc76451b9', 'type' => 'library', 'install_path' => __DIR__ . '/../league/mime-type-detection', 'aliases' => array(), - 'reference' => '2d6702ff215bf922936ccc1ad31007edc76451b9', 'dev_requirement' => false, ), 'league/oauth1-client' => array( 'pretty_version' => 'v1.11.0', 'version' => '1.11.0.0', + 'reference' => 'f9c94b088837eb1aae1ad7c4f23eb65cc6993055', 'type' => 'library', 'install_path' => __DIR__ . '/../league/oauth1-client', 'aliases' => array(), - 'reference' => 'f9c94b088837eb1aae1ad7c4f23eb65cc6993055', 'dev_requirement' => false, ), 'league/omnipay' => array( 'pretty_version' => 'v3.2.1', 'version' => '3.2.1.0', - 'type' => 'metapackage', - 'install_path' => NULL, - 'aliases' => array(), 'reference' => '38f66a0cc043ed51d6edf7956d6439a2f263501f', + 'type' => 'metapackage', + 'install_path' => null, + 'aliases' => array(), 'dev_requirement' => false, ), 'maatwebsite/excel' => array( 'pretty_version' => '3.1.67', 'version' => '3.1.67.0', + 'reference' => 'e508e34a502a3acc3329b464dad257378a7edb4d', 'type' => 'library', 'install_path' => __DIR__ . '/../maatwebsite/excel', 'aliases' => array(), - 'reference' => 'e508e34a502a3acc3329b464dad257378a7edb4d', 'dev_requirement' => false, ), 'maennchen/zipstream-php' => array( 'pretty_version' => '3.1.2', 'version' => '3.1.2.0', + 'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f', 'type' => 'library', 'install_path' => __DIR__ . '/../maennchen/zipstream-php', 'aliases' => array(), - 'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f', 'dev_requirement' => false, ), 'markbaker/complex' => array( 'pretty_version' => '3.0.2', 'version' => '3.0.2.0', + 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', 'type' => 'library', 'install_path' => __DIR__ . '/../markbaker/complex', 'aliases' => array(), - 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', 'dev_requirement' => false, ), 'markbaker/matrix' => array( 'pretty_version' => '3.0.1', 'version' => '3.0.1.0', + 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', 'type' => 'library', 'install_path' => __DIR__ . '/../markbaker/matrix', 'aliases' => array(), - 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', 'dev_requirement' => false, ), 'masterminds/html5' => array( 'pretty_version' => '2.10.0', 'version' => '2.10.0.0', + 'reference' => 'fcf91eb64359852f00d921887b219479b4f21251', 'type' => 'library', 'install_path' => __DIR__ . '/../masterminds/html5', 'aliases' => array(), - 'reference' => 'fcf91eb64359852f00d921887b219479b4f21251', 'dev_requirement' => false, ), 'mediaburst/clockworksms' => array( 'pretty_version' => '2.0.0', 'version' => '2.0.0.0', + 'reference' => '5a5e49406614dcb49c0ff3dea2a96a81cbc32c38', 'type' => 'library', 'install_path' => __DIR__ . '/../mediaburst/clockworksms', 'aliases' => array(), - 'reference' => '5a5e49406614dcb49c0ff3dea2a96a81cbc32c38', 'dev_requirement' => false, ), 'melipayamak/php' => array( 'pretty_version' => '1.0.0', 'version' => '1.0.0.0', + 'reference' => 'b8f7db07eef1fa57f8a23d379949e3f04cb923b8', 'type' => 'library', 'install_path' => __DIR__ . '/../melipayamak/php', 'aliases' => array(), - 'reference' => 'b8f7db07eef1fa57f8a23d379949e3f04cb923b8', 'dev_requirement' => false, ), 'mercadopago/dx-php' => array( 'pretty_version' => '2.6.2', 'version' => '2.6.2.0', + 'reference' => 'f5f97bd96dfcb3bafdfba634b3bc757025238caa', 'type' => 'library', 'install_path' => __DIR__ . '/../mercadopago/dx-php', 'aliases' => array(), - 'reference' => 'f5f97bd96dfcb3bafdfba634b3bc757025238caa', 'dev_requirement' => false, ), 'mockery/mockery' => array( 'pretty_version' => '1.6.12', 'version' => '1.6.12.0', + 'reference' => '1f4efdd7d3beafe9807b08156dfcb176d18f1699', 'type' => 'library', 'install_path' => __DIR__ . '/../mockery/mockery', 'aliases' => array(), - 'reference' => '1f4efdd7d3beafe9807b08156dfcb176d18f1699', 'dev_requirement' => true, ), 'mollie/mollie-api-php' => array( 'pretty_version' => 'v2.79.1', 'version' => '2.79.1.0', + 'reference' => '4c1cf5f603178dd15bdf60b5e3999f91bb59f5b0', 'type' => 'library', 'install_path' => __DIR__ . '/../mollie/mollie-api-php', 'aliases' => array(), - 'reference' => '4c1cf5f603178dd15bdf60b5e3999f91bb59f5b0', 'dev_requirement' => false, ), 'moneyphp/money' => array( 'pretty_version' => 'v4.8.0', 'version' => '4.8.0.0', + 'reference' => 'b358727ea5a5cd2d7475e59c31dfc352440ae7ec', 'type' => 'library', 'install_path' => __DIR__ . '/../moneyphp/money', 'aliases' => array(), - 'reference' => 'b358727ea5a5cd2d7475e59c31dfc352440ae7ec', 'dev_requirement' => false, ), 'monolog/monolog' => array( 'pretty_version' => '3.10.0', 'version' => '3.10.0.0', + 'reference' => 'b321dd6749f0bf7189444158a3ce785cc16d69b0', 'type' => 'library', 'install_path' => __DIR__ . '/../monolog/monolog', 'aliases' => array(), - 'reference' => 'b321dd6749f0bf7189444158a3ce785cc16d69b0', 'dev_requirement' => false, ), 'mpdf/mpdf' => array( 'pretty_version' => 'v8.2.7', 'version' => '8.2.7.0', + 'reference' => 'b59670a09498689c33ce639bac8f5ba26721dab3', 'type' => 'library', 'install_path' => __DIR__ . '/../mpdf/mpdf', 'aliases' => array(), - 'reference' => 'b59670a09498689c33ce639bac8f5ba26721dab3', 'dev_requirement' => false, ), 'mpdf/psr-http-message-shim' => array( 'pretty_version' => 'v2.0.1', 'version' => '2.0.1.0', + 'reference' => 'f25a0153d645e234f9db42e5433b16d9b113920f', 'type' => 'library', 'install_path' => __DIR__ . '/../mpdf/psr-http-message-shim', 'aliases' => array(), - 'reference' => 'f25a0153d645e234f9db42e5433b16d9b113920f', 'dev_requirement' => false, ), 'mpdf/psr-log-aware-trait' => array( 'pretty_version' => 'v3.0.0', 'version' => '3.0.0.0', + 'reference' => 'a633da6065e946cc491e1c962850344bb0bf3e78', 'type' => 'library', 'install_path' => __DIR__ . '/../mpdf/psr-log-aware-trait', 'aliases' => array(), - 'reference' => 'a633da6065e946cc491e1c962850344bb0bf3e78', 'dev_requirement' => false, ), + 'mpociot/laravel-apidoc-generator' => array( + 'dev_requirement' => true, + 'replaced' => array( + 0 => '*', + ), + ), + 'mpociot/reflection-docblock' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => 'c8b2e2b1f5cebbb06e2b5ccbf2958f2198867587', + 'type' => 'library', + 'install_path' => __DIR__ . '/../mpociot/reflection-docblock', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'mtdowling/cron-expression' => array( 'dev_requirement' => false, 'replaced' => array( @@ -1054,154 +1078,163 @@ 'mtdowling/jmespath.php' => array( 'pretty_version' => '2.8.0', 'version' => '2.8.0.0', + 'reference' => 'a2a865e05d5f420b50cc2f85bb78d565db12a6bc', 'type' => 'library', 'install_path' => __DIR__ . '/../mtdowling/jmespath.php', 'aliases' => array(), - 'reference' => 'a2a865e05d5f420b50cc2f85bb78d565db12a6bc', 'dev_requirement' => false, ), 'myclabs/deep-copy' => array( 'pretty_version' => '1.13.4', 'version' => '1.13.4.0', + 'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a', 'type' => 'library', 'install_path' => __DIR__ . '/../myclabs/deep-copy', 'aliases' => array(), - 'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a', 'dev_requirement' => false, ), 'nesbot/carbon' => array( 'pretty_version' => '2.73.0', 'version' => '2.73.0.0', + 'reference' => '9228ce90e1035ff2f0db84b40ec2e023ed802075', 'type' => 'library', 'install_path' => __DIR__ . '/../nesbot/carbon', 'aliases' => array(), - 'reference' => '9228ce90e1035ff2f0db84b40ec2e023ed802075', 'dev_requirement' => false, ), 'nette/schema' => array( 'pretty_version' => 'v1.3.3', 'version' => '1.3.3.0', + 'reference' => '2befc2f42d7c715fd9d95efc31b1081e5d765004', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/schema', 'aliases' => array(), - 'reference' => '2befc2f42d7c715fd9d95efc31b1081e5d765004', 'dev_requirement' => false, ), 'nette/utils' => array( 'pretty_version' => 'v4.1.1', 'version' => '4.1.1.0', + 'reference' => 'c99059c0315591f1a0db7ad6002000288ab8dc72', 'type' => 'library', 'install_path' => __DIR__ . '/../nette/utils', 'aliases' => array(), - 'reference' => 'c99059c0315591f1a0db7ad6002000288ab8dc72', 'dev_requirement' => false, ), 'nikic/php-parser' => array( 'pretty_version' => 'v5.7.0', 'version' => '5.7.0.0', + 'reference' => 'dca41cd15c2ac9d055ad70dbfd011130757d1f82', 'type' => 'library', 'install_path' => __DIR__ . '/../nikic/php-parser', 'aliases' => array(), - 'reference' => 'dca41cd15c2ac9d055ad70dbfd011130757d1f82', 'dev_requirement' => false, ), 'nunomaduro/collision' => array( 'pretty_version' => 'v7.12.0', 'version' => '7.12.0.0', + 'reference' => '995245421d3d7593a6960822063bdba4f5d7cf1a', 'type' => 'library', 'install_path' => __DIR__ . '/../nunomaduro/collision', 'aliases' => array(), - 'reference' => '995245421d3d7593a6960822063bdba4f5d7cf1a', 'dev_requirement' => true, ), 'nunomaduro/termwind' => array( 'pretty_version' => 'v1.17.0', 'version' => '1.17.0.0', + 'reference' => '5369ef84d8142c1d87e4ec278711d4ece3cbf301', 'type' => 'library', 'install_path' => __DIR__ . '/../nunomaduro/termwind', 'aliases' => array(), - 'reference' => '5369ef84d8142c1d87e4ec278711d4ece3cbf301', 'dev_requirement' => false, ), 'nwidart/laravel-modules' => array( 'pretty_version' => '10.0.6', 'version' => '10.0.6.0', + 'reference' => 'a6f2c8b53ae7945ef41d296735e963cee885ebde', 'type' => 'library', 'install_path' => __DIR__ . '/../nwidart/laravel-modules', 'aliases' => array(), - 'reference' => 'a6f2c8b53ae7945ef41d296735e963cee885ebde', 'dev_requirement' => false, ), 'omnipay/common' => array( 'pretty_version' => 'v3.4.0', 'version' => '3.4.0.0', + 'reference' => '83552de28815c5e46f66360c4d52c3a82a7d5efc', 'type' => 'library', 'install_path' => __DIR__ . '/../omnipay/common', 'aliases' => array(), - 'reference' => '83552de28815c5e46f66360c4d52c3a82a7d5efc', 'dev_requirement' => false, ), 'omnipay/paypal' => array( 'pretty_version' => 'v3.0.2', 'version' => '3.0.2.0', + 'reference' => '519db61b32ff0c1e56cbec94762b970ee9674f65', 'type' => 'library', 'install_path' => __DIR__ . '/../omnipay/paypal', 'aliases' => array(), - 'reference' => '519db61b32ff0c1e56cbec94762b970ee9674f65', 'dev_requirement' => false, ), 'omnipay/stripe' => array( 'pretty_version' => 'v3.2.0', 'version' => '3.2.0.0', + 'reference' => '20812498efedc1079baae5fea96567fefd669105', 'type' => 'library', 'install_path' => __DIR__ . '/../omnipay/stripe', 'aliases' => array(), - 'reference' => '20812498efedc1079baae5fea96567fefd669105', 'dev_requirement' => false, ), 'paragonie/constant_time_encoding' => array( 'pretty_version' => 'v3.1.3', 'version' => '3.1.3.0', + 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', 'type' => 'library', 'install_path' => __DIR__ . '/../paragonie/constant_time_encoding', 'aliases' => array(), - 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', 'dev_requirement' => false, ), 'paragonie/random_compat' => array( 'pretty_version' => 'v9.99.100', 'version' => '9.99.100.0', + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', 'type' => 'library', 'install_path' => __DIR__ . '/../paragonie/random_compat', 'aliases' => array(), - 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', 'dev_requirement' => false, ), + 'parsedown/parsedown' => array( + 'pretty_version' => '1.8.0', + 'version' => '1.8.0.0', + 'reference' => '96baaad00f71ba04d76e45b4620f54d3beabd6f7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../parsedown/parsedown', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'paytm/paytmchecksum' => array( 'pretty_version' => 'v1.1.0', 'version' => '1.1.0.0', + 'reference' => 'a032d3cbeb3846720c2c606b9f3c8057c355042e', 'type' => 'library', 'install_path' => __DIR__ . '/../paytm/paytmchecksum', 'aliases' => array(), - 'reference' => 'a032d3cbeb3846720c2c606b9f3c8057c355042e', 'dev_requirement' => false, ), 'phar-io/manifest' => array( 'pretty_version' => '2.0.4', 'version' => '2.0.4.0', + 'reference' => '54750ef60c58e43759730615a392c31c80e23176', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/manifest', 'aliases' => array(), - 'reference' => '54750ef60c58e43759730615a392c31c80e23176', 'dev_requirement' => true, ), 'phar-io/version' => array( 'pretty_version' => '3.2.1', 'version' => '3.2.1.0', + 'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/version', 'aliases' => array(), - 'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74', 'dev_requirement' => true, ), 'php-http/async-client-implementation' => array( @@ -1221,46 +1254,46 @@ 'php-http/discovery' => array( 'pretty_version' => '1.20.0', 'version' => '1.20.0.0', + 'reference' => '82fe4c73ef3363caed49ff8dd1539ba06044910d', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/../php-http/discovery', 'aliases' => array(), - 'reference' => '82fe4c73ef3363caed49ff8dd1539ba06044910d', 'dev_requirement' => false, ), 'php-http/guzzle7-adapter' => array( 'pretty_version' => '1.1.0', 'version' => '1.1.0.0', + 'reference' => '03a415fde709c2f25539790fecf4d9a31bc3d0eb', 'type' => 'library', 'install_path' => __DIR__ . '/../php-http/guzzle7-adapter', 'aliases' => array(), - 'reference' => '03a415fde709c2f25539790fecf4d9a31bc3d0eb', 'dev_requirement' => false, ), 'php-http/httplug' => array( 'pretty_version' => '2.4.1', 'version' => '2.4.1.0', + 'reference' => '5cad731844891a4c282f3f3e1b582c46839d22f4', 'type' => 'library', 'install_path' => __DIR__ . '/../php-http/httplug', 'aliases' => array(), - 'reference' => '5cad731844891a4c282f3f3e1b582c46839d22f4', 'dev_requirement' => false, ), 'php-http/message' => array( 'pretty_version' => '1.16.2', 'version' => '1.16.2.0', + 'reference' => '06dd5e8562f84e641bf929bfe699ee0f5ce8080a', 'type' => 'library', 'install_path' => __DIR__ . '/../php-http/message', 'aliases' => array(), - 'reference' => '06dd5e8562f84e641bf929bfe699ee0f5ce8080a', 'dev_requirement' => false, ), 'php-http/message-factory' => array( 'pretty_version' => '1.1.0', 'version' => '1.1.0.0', + 'reference' => '4d8778e1c7d405cbb471574821c1ff5b68cc8f57', 'type' => 'library', 'install_path' => __DIR__ . '/../php-http/message-factory', 'aliases' => array(), - 'reference' => '4d8778e1c7d405cbb471574821c1ff5b68cc8f57', 'dev_requirement' => false, ), 'php-http/message-factory-implementation' => array( @@ -1272,100 +1305,100 @@ 'php-http/promise' => array( 'pretty_version' => '1.3.1', 'version' => '1.3.1.0', + 'reference' => 'fc85b1fba37c169a69a07ef0d5a8075770cc1f83', 'type' => 'library', 'install_path' => __DIR__ . '/../php-http/promise', 'aliases' => array(), - 'reference' => 'fc85b1fba37c169a69a07ef0d5a8075770cc1f83', 'dev_requirement' => false, ), 'phpoffice/phpspreadsheet' => array( 'pretty_version' => '1.30.0', 'version' => '1.30.0.0', + 'reference' => '2f39286e0136673778b7a142b3f0d141e43d1714', 'type' => 'library', 'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet', 'aliases' => array(), - 'reference' => '2f39286e0136673778b7a142b3f0d141e43d1714', 'dev_requirement' => false, ), 'phpoption/phpoption' => array( 'pretty_version' => '1.9.5', 'version' => '1.9.5.0', + 'reference' => '75365b91986c2405cf5e1e012c5595cd487a98be', 'type' => 'library', 'install_path' => __DIR__ . '/../phpoption/phpoption', 'aliases' => array(), - 'reference' => '75365b91986c2405cf5e1e012c5595cd487a98be', 'dev_requirement' => false, ), 'phpseclib/phpseclib' => array( 'pretty_version' => '3.0.48', 'version' => '3.0.48.0', + 'reference' => '64065a5679c50acb886e82c07aa139b0f757bb89', 'type' => 'library', 'install_path' => __DIR__ . '/../phpseclib/phpseclib', 'aliases' => array(), - 'reference' => '64065a5679c50acb886e82c07aa139b0f757bb89', 'dev_requirement' => false, ), 'phpunit/php-code-coverage' => array( 'pretty_version' => '10.1.16', 'version' => '10.1.16.0', + 'reference' => '7e308268858ed6baedc8704a304727d20bc07c77', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-code-coverage', 'aliases' => array(), - 'reference' => '7e308268858ed6baedc8704a304727d20bc07c77', 'dev_requirement' => true, ), 'phpunit/php-file-iterator' => array( 'pretty_version' => '4.1.0', 'version' => '4.1.0.0', + 'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-file-iterator', 'aliases' => array(), - 'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c', 'dev_requirement' => true, ), 'phpunit/php-invoker' => array( 'pretty_version' => '4.0.0', 'version' => '4.0.0.0', + 'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-invoker', 'aliases' => array(), - 'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7', 'dev_requirement' => true, ), 'phpunit/php-text-template' => array( 'pretty_version' => '3.0.1', 'version' => '3.0.1.0', + 'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-text-template', 'aliases' => array(), - 'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748', 'dev_requirement' => true, ), 'phpunit/php-timer' => array( 'pretty_version' => '6.0.0', 'version' => '6.0.0.0', + 'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-timer', 'aliases' => array(), - 'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d', 'dev_requirement' => true, ), 'phpunit/phpunit' => array( 'pretty_version' => '10.5.60', 'version' => '10.5.60.0', + 'reference' => 'f2e26f52f80ef77832e359205f216eeac00e320c', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/phpunit', 'aliases' => array(), - 'reference' => 'f2e26f52f80ef77832e359205f216eeac00e320c', 'dev_requirement' => true, ), 'psr/cache' => array( 'pretty_version' => '3.0.0', 'version' => '3.0.0.0', + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/cache', 'aliases' => array(), - 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', 'dev_requirement' => false, ), 'psr/cache-implementation' => array( @@ -1377,10 +1410,10 @@ 'psr/clock' => array( 'pretty_version' => '1.0.0', 'version' => '1.0.0.0', + 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/clock', 'aliases' => array(), - 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d', 'dev_requirement' => false, ), 'psr/clock-implementation' => array( @@ -1392,10 +1425,10 @@ 'psr/container' => array( 'pretty_version' => '2.0.2', 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/container', 'aliases' => array(), - 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', 'dev_requirement' => false, ), 'psr/container-implementation' => array( @@ -1407,10 +1440,10 @@ 'psr/event-dispatcher' => array( 'pretty_version' => '1.0.0', 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/event-dispatcher', 'aliases' => array(), - 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', 'dev_requirement' => false, ), 'psr/event-dispatcher-implementation' => array( @@ -1422,10 +1455,10 @@ 'psr/http-client' => array( 'pretty_version' => '1.0.3', 'version' => '1.0.3.0', + 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-client', 'aliases' => array(), - 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', 'dev_requirement' => false, ), 'psr/http-client-implementation' => array( @@ -1438,10 +1471,10 @@ 'psr/http-factory' => array( 'pretty_version' => '1.1.0', 'version' => '1.1.0.0', + 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-factory', 'aliases' => array(), - 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a', 'dev_requirement' => false, ), 'psr/http-factory-implementation' => array( @@ -1454,10 +1487,10 @@ 'psr/http-message' => array( 'pretty_version' => '2.0', 'version' => '2.0.0.0', + 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/http-message', 'aliases' => array(), - 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', 'dev_requirement' => false, ), 'psr/http-message-implementation' => array( @@ -1470,10 +1503,10 @@ 'psr/log' => array( 'pretty_version' => '3.0.2', 'version' => '3.0.2.0', + 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), - 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3', 'dev_requirement' => false, ), 'psr/log-implementation' => array( @@ -1486,10 +1519,10 @@ 'psr/simple-cache' => array( 'pretty_version' => '3.0.0', 'version' => '3.0.0.0', + 'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/simple-cache', 'aliases' => array(), - 'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865', 'dev_requirement' => false, ), 'psr/simple-cache-implementation' => array( @@ -1501,46 +1534,46 @@ 'psy/psysh' => array( 'pretty_version' => 'v0.12.18', 'version' => '0.12.18.0', + 'reference' => 'ddff0ac01beddc251786fe70367cd8bbdb258196', 'type' => 'library', 'install_path' => __DIR__ . '/../psy/psysh', 'aliases' => array(), - 'reference' => 'ddff0ac01beddc251786fe70367cd8bbdb258196', 'dev_requirement' => false, ), 'ralouphie/getallheaders' => array( 'pretty_version' => '3.0.3', 'version' => '3.0.3.0', + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'type' => 'library', 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 'aliases' => array(), - 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 'dev_requirement' => false, ), 'ramsey/collection' => array( 'pretty_version' => '2.1.1', 'version' => '2.1.1.0', + 'reference' => '344572933ad0181accbf4ba763e85a0306a8c5e2', 'type' => 'library', 'install_path' => __DIR__ . '/../ramsey/collection', 'aliases' => array(), - 'reference' => '344572933ad0181accbf4ba763e85a0306a8c5e2', 'dev_requirement' => false, ), 'ramsey/uuid' => array( 'pretty_version' => '4.9.2', 'version' => '4.9.2.0', + 'reference' => '8429c78ca35a09f27565311b98101e2826affde0', 'type' => 'library', 'install_path' => __DIR__ . '/../ramsey/uuid', 'aliases' => array(), - 'reference' => '8429c78ca35a09f27565311b98101e2826affde0', 'dev_requirement' => false, ), 'razorpay/razorpay' => array( 'pretty_version' => '2.9.2', 'version' => '2.9.2.0', + 'reference' => 'c5cf59941eb2d888e80371328d932e6e8266d352', 'type' => 'library', 'install_path' => __DIR__ . '/../razorpay/razorpay', 'aliases' => array(), - 'reference' => 'c5cf59941eb2d888e80371328d932e6e8266d352', 'dev_requirement' => false, ), 'rhumsaa/uuid' => array( @@ -1552,325 +1585,334 @@ 'rize/uri-template' => array( 'pretty_version' => '0.4.1', 'version' => '0.4.1.0', + 'reference' => 'abb53c8b73a5b6c24e11f49036ab842f560cad33', 'type' => 'library', 'install_path' => __DIR__ . '/../rize/uri-template', 'aliases' => array(), - 'reference' => 'abb53c8b73a5b6c24e11f49036ab842f560cad33', 'dev_requirement' => false, ), 'rmccue/requests' => array( 'pretty_version' => 'v2.0.17', 'version' => '2.0.17.0', + 'reference' => '74d1648cc34e16a42ea25d548fc73ec107a90421', 'type' => 'library', 'install_path' => __DIR__ . '/../rmccue/requests', 'aliases' => array(), - 'reference' => '74d1648cc34e16a42ea25d548fc73ec107a90421', 'dev_requirement' => false, ), 'sabberworm/php-css-parser' => array( 'pretty_version' => 'v9.1.0', 'version' => '9.1.0.0', + 'reference' => '1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb', 'type' => 'library', 'install_path' => __DIR__ . '/../sabberworm/php-css-parser', 'aliases' => array(), - 'reference' => '1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb', 'dev_requirement' => false, ), 'safiull/laravel-installer' => array( 'pretty_version' => '3.5.2', 'version' => '3.5.2.0', + 'reference' => 'c69b36f2968f0be96b8584c9d5006345e915f5ef', 'type' => 'library', 'install_path' => __DIR__ . '/../safiull/laravel-installer', 'aliases' => array(), - 'reference' => 'c69b36f2968f0be96b8584c9d5006345e915f5ef', 'dev_requirement' => false, ), 'sebastian/cli-parser' => array( 'pretty_version' => '2.0.1', 'version' => '2.0.1.0', + 'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/cli-parser', 'aliases' => array(), - 'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084', 'dev_requirement' => true, ), 'sebastian/code-unit' => array( 'pretty_version' => '2.0.0', 'version' => '2.0.0.0', + 'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit', 'aliases' => array(), - 'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503', 'dev_requirement' => true, ), 'sebastian/code-unit-reverse-lookup' => array( 'pretty_version' => '3.0.0', 'version' => '3.0.0.0', + 'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup', 'aliases' => array(), - 'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d', 'dev_requirement' => true, ), 'sebastian/comparator' => array( 'pretty_version' => '5.0.4', 'version' => '5.0.4.0', + 'reference' => 'e8e53097718d2b53cfb2aa859b06a41abf58c62e', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/comparator', 'aliases' => array(), - 'reference' => 'e8e53097718d2b53cfb2aa859b06a41abf58c62e', 'dev_requirement' => true, ), 'sebastian/complexity' => array( 'pretty_version' => '3.2.0', 'version' => '3.2.0.0', + 'reference' => '68ff824baeae169ec9f2137158ee529584553799', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/complexity', 'aliases' => array(), - 'reference' => '68ff824baeae169ec9f2137158ee529584553799', 'dev_requirement' => true, ), 'sebastian/diff' => array( 'pretty_version' => '5.1.1', 'version' => '5.1.1.0', + 'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/diff', 'aliases' => array(), - 'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e', 'dev_requirement' => true, ), 'sebastian/environment' => array( 'pretty_version' => '6.1.0', 'version' => '6.1.0.0', + 'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/environment', 'aliases' => array(), - 'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984', 'dev_requirement' => true, ), 'sebastian/exporter' => array( 'pretty_version' => '5.1.4', 'version' => '5.1.4.0', + 'reference' => '0735b90f4da94969541dac1da743446e276defa6', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/exporter', 'aliases' => array(), - 'reference' => '0735b90f4da94969541dac1da743446e276defa6', 'dev_requirement' => true, ), 'sebastian/global-state' => array( 'pretty_version' => '6.0.2', 'version' => '6.0.2.0', + 'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/global-state', 'aliases' => array(), - 'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9', 'dev_requirement' => true, ), 'sebastian/lines-of-code' => array( 'pretty_version' => '2.0.2', 'version' => '2.0.2.0', + 'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/lines-of-code', 'aliases' => array(), - 'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0', 'dev_requirement' => true, ), 'sebastian/object-enumerator' => array( 'pretty_version' => '5.0.0', 'version' => '5.0.0.0', + 'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-enumerator', 'aliases' => array(), - 'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906', 'dev_requirement' => true, ), 'sebastian/object-reflector' => array( 'pretty_version' => '3.0.0', 'version' => '3.0.0.0', + 'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-reflector', 'aliases' => array(), - 'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957', 'dev_requirement' => true, ), 'sebastian/recursion-context' => array( 'pretty_version' => '5.0.1', 'version' => '5.0.1.0', + 'reference' => '47e34210757a2f37a97dcd207d032e1b01e64c7a', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/recursion-context', 'aliases' => array(), - 'reference' => '47e34210757a2f37a97dcd207d032e1b01e64c7a', 'dev_requirement' => true, ), 'sebastian/type' => array( 'pretty_version' => '4.0.0', 'version' => '4.0.0.0', + 'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/type', 'aliases' => array(), - 'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf', 'dev_requirement' => true, ), 'sebastian/version' => array( 'pretty_version' => '4.0.1', 'version' => '4.0.1.0', + 'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/version', 'aliases' => array(), - 'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17', 'dev_requirement' => true, ), 'setasign/fpdi' => array( 'pretty_version' => 'v2.6.4', 'version' => '2.6.4.0', + 'reference' => '4b53852fde2734ec6a07e458a085db627c60eada', 'type' => 'library', 'install_path' => __DIR__ . '/../setasign/fpdi', 'aliases' => array(), - 'reference' => '4b53852fde2734ec6a07e458a085db627c60eada', 'dev_requirement' => false, ), + 'shalvah/upgrader' => array( + 'pretty_version' => '0.6.0', + 'version' => '0.6.0.0', + 'reference' => 'd95ed17fe9f5e1ee7d47ad835595f1af080a867f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../shalvah/upgrader', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'simplesoftwareio/simple-qrcode' => array( 'pretty_version' => '4.2.0', 'version' => '4.2.0.0', + 'reference' => '916db7948ca6772d54bb617259c768c9cdc8d537', 'type' => 'library', 'install_path' => __DIR__ . '/../simplesoftwareio/simple-qrcode', 'aliases' => array(), - 'reference' => '916db7948ca6772d54bb617259c768c9cdc8d537', 'dev_requirement' => false, ), 'smsgatewayme/client' => array( 'pretty_version' => 'v0.0.1', 'version' => '0.0.1.0', + 'reference' => '864f326ad6951599b487aab3cc350a966c5cbd86', 'type' => 'library', 'install_path' => __DIR__ . '/../smsgatewayme/client', 'aliases' => array(), - 'reference' => '864f326ad6951599b487aab3cc350a966c5cbd86', 'dev_requirement' => false, ), 'spatie/backtrace' => array( 'pretty_version' => '1.8.1', 'version' => '1.8.1.0', + 'reference' => '8c0f16a59ae35ec8c62d85c3c17585158f430110', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/backtrace', 'aliases' => array(), - 'reference' => '8c0f16a59ae35ec8c62d85c3c17585158f430110', 'dev_requirement' => true, ), 'spatie/color' => array( 'pretty_version' => '1.8.0', 'version' => '1.8.0.0', + 'reference' => '142af7fec069a420babea80a5412eb2f646dcd8c', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/color', 'aliases' => array(), - 'reference' => '142af7fec069a420babea80a5412eb2f646dcd8c', 'dev_requirement' => false, ), 'spatie/error-solutions' => array( 'pretty_version' => '1.1.3', 'version' => '1.1.3.0', + 'reference' => 'e495d7178ca524f2dd0fe6a1d99a1e608e1c9936', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/error-solutions', 'aliases' => array(), - 'reference' => 'e495d7178ca524f2dd0fe6a1d99a1e608e1c9936', 'dev_requirement' => true, ), 'spatie/flare-client-php' => array( 'pretty_version' => '1.10.1', 'version' => '1.10.1.0', + 'reference' => 'bf1716eb98bd689451b071548ae9e70738dce62f', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/flare-client-php', 'aliases' => array(), - 'reference' => 'bf1716eb98bd689451b071548ae9e70738dce62f', 'dev_requirement' => true, ), 'spatie/ignition' => array( 'pretty_version' => '1.15.1', 'version' => '1.15.1.0', + 'reference' => '31f314153020aee5af3537e507fef892ffbf8c85', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/ignition', 'aliases' => array(), - 'reference' => '31f314153020aee5af3537e507fef892ffbf8c85', 'dev_requirement' => true, ), 'spatie/laravel-ignition' => array( 'pretty_version' => '2.9.1', 'version' => '2.9.1.0', + 'reference' => '1baee07216d6748ebd3a65ba97381b051838707a', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/laravel-ignition', 'aliases' => array(), - 'reference' => '1baee07216d6748ebd3a65ba97381b051838707a', 'dev_requirement' => true, ), 'spatie/laravel-permission' => array( 'pretty_version' => '6.24.0', 'version' => '6.24.0.0', + 'reference' => '76adb1fc8d07c16a0721c35c4cc330b7a12598d7', 'type' => 'library', 'install_path' => __DIR__ . '/../spatie/laravel-permission', 'aliases' => array(), - 'reference' => '76adb1fc8d07c16a0721c35c4cc330b7a12598d7', 'dev_requirement' => false, ), 'stripe/stripe-php' => array( 'pretty_version' => 'v13.18.0', 'version' => '13.18.0.0', + 'reference' => '02abb043b103766f4ed920642ae56ffdc58c7467', 'type' => 'library', 'install_path' => __DIR__ . '/../stripe/stripe-php', 'aliases' => array(), - 'reference' => '02abb043b103766f4ed920642ae56ffdc58c7467', 'dev_requirement' => false, ), 'symfony/console' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => 'f9f8a889f54c264f9abac3fc0f7a371ffca51997', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), - 'reference' => 'f9f8a889f54c264f9abac3fc0f7a371ffca51997', 'dev_requirement' => false, ), 'symfony/css-selector' => array( 'pretty_version' => 'v7.4.0', 'version' => '7.4.0.0', + 'reference' => 'ab862f478513e7ca2fe9ec117a6f01a8da6e1135', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/css-selector', 'aliases' => array(), - 'reference' => 'ab862f478513e7ca2fe9ec117a6f01a8da6e1135', 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( 'pretty_version' => 'v3.6.0', 'version' => '3.6.0.0', + 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), - 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', 'dev_requirement' => false, ), 'symfony/error-handler' => array( 'pretty_version' => 'v6.4.26', 'version' => '6.4.26.0', + 'reference' => '41bedcaec5b72640b0ec2096547b75fda72ead6c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/error-handler', 'aliases' => array(), - 'reference' => '41bedcaec5b72640b0ec2096547b75fda72ead6c', 'dev_requirement' => false, ), 'symfony/event-dispatcher' => array( 'pretty_version' => 'v7.4.0', 'version' => '7.4.0.0', + 'reference' => '9dddcddff1ef974ad87b3708e4b442dc38b2261d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher', 'aliases' => array(), - 'reference' => '9dddcddff1ef974ad87b3708e4b442dc38b2261d', 'dev_requirement' => false, ), 'symfony/event-dispatcher-contracts' => array( 'pretty_version' => 'v3.6.0', 'version' => '3.6.0.0', + 'reference' => '59eb412e93815df44f05f342958efa9f46b1e586', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', 'aliases' => array(), - 'reference' => '59eb412e93815df44f05f342958efa9f46b1e586', 'dev_requirement' => false, ), 'symfony/event-dispatcher-implementation' => array( @@ -1882,181 +1924,181 @@ 'symfony/filesystem' => array( 'pretty_version' => 'v7.4.0', 'version' => '7.4.0.0', + 'reference' => 'd551b38811096d0be9c4691d406991b47c0c630a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), - 'reference' => 'd551b38811096d0be9c4691d406991b47c0c630a', 'dev_requirement' => false, ), 'symfony/finder' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => '5547f2e1f0ca8e2e7abe490156b62da778cfbe2b', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), - 'reference' => '5547f2e1f0ca8e2e7abe490156b62da778cfbe2b', 'dev_requirement' => false, ), 'symfony/http-foundation' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => 'a35ee6f47e4775179704d7877a8b0da3cb09241a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-foundation', 'aliases' => array(), - 'reference' => 'a35ee6f47e4775179704d7877a8b0da3cb09241a', 'dev_requirement' => false, ), 'symfony/http-kernel' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => '16b0d46d8e11f480345c15b229cfc827a8a0f731', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-kernel', 'aliases' => array(), - 'reference' => '16b0d46d8e11f480345c15b229cfc827a8a0f731', 'dev_requirement' => false, ), 'symfony/mailer' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => '8835f93333474780fda1b987cae37e33c3e026ca', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/mailer', 'aliases' => array(), - 'reference' => '8835f93333474780fda1b987cae37e33c3e026ca', 'dev_requirement' => false, ), 'symfony/mime' => array( 'pretty_version' => 'v6.4.30', 'version' => '6.4.30.0', + 'reference' => '69aeef5d2692bb7c18ce133b09f67b27260b7acf', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/mime', 'aliases' => array(), - 'reference' => '69aeef5d2692bb7c18ce133b09f67b27260b7acf', 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), - 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 'dev_requirement' => false, ), 'symfony/polyfill-intl-grapheme' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '380872130d3a5dd3ace2f4010d95125fde5d5c70', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), - 'reference' => '380872130d3a5dd3ace2f4010d95125fde5d5c70', 'dev_requirement' => false, ), 'symfony/polyfill-intl-idn' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '9614ac4d8061dc257ecc64cba1b140873dce8ad3', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn', 'aliases' => array(), - 'reference' => '9614ac4d8061dc257ecc64cba1b140873dce8ad3', 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '3833d7255cc303546435cb650316bff708a1c75c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), - 'reference' => '3833d7255cc303546435cb650316bff708a1c75c', 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), - 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), - 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', 'dev_requirement' => false, ), 'symfony/polyfill-php83' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '17f6f9a6b1735c0f163024d959f700cfbc5155e5', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php83', 'aliases' => array(), - 'reference' => '17f6f9a6b1735c0f163024d959f700cfbc5155e5', 'dev_requirement' => false, ), 'symfony/polyfill-uuid' => array( 'pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', + 'reference' => '21533be36c24be3f4b1669c4725c7d1d2bab4ae2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-uuid', 'aliases' => array(), - 'reference' => '21533be36c24be3f4b1669c4725c7d1d2bab4ae2', 'dev_requirement' => false, ), 'symfony/process' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => '8541b7308fca001320e90bca8a73a28aa5604a6e', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/process', 'aliases' => array(), - 'reference' => '8541b7308fca001320e90bca8a73a28aa5604a6e', 'dev_requirement' => false, ), 'symfony/routing' => array( 'pretty_version' => 'v6.4.30', 'version' => '6.4.30.0', + 'reference' => 'ea50a13c2711eebcbb66b38ef6382e62e3262859', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/routing', 'aliases' => array(), - 'reference' => 'ea50a13c2711eebcbb66b38ef6382e62e3262859', 'dev_requirement' => false, ), 'symfony/service-contracts' => array( 'pretty_version' => 'v3.6.1', 'version' => '3.6.1.0', + 'reference' => '45112560a3ba2d715666a509a0bc9521d10b6c43', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/service-contracts', 'aliases' => array(), - 'reference' => '45112560a3ba2d715666a509a0bc9521d10b6c43', 'dev_requirement' => false, ), 'symfony/string' => array( 'pretty_version' => 'v7.4.0', 'version' => '7.4.0.0', + 'reference' => 'd50e862cb0a0e0886f73ca1f31b865efbb795003', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/string', 'aliases' => array(), - 'reference' => 'd50e862cb0a0e0886f73ca1f31b865efbb795003', 'dev_requirement' => false, ), 'symfony/translation' => array( 'pretty_version' => 'v6.4.31', 'version' => '6.4.31.0', + 'reference' => '81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/translation', 'aliases' => array(), - 'reference' => '81579408ecf7dc5aa2d8462a6d5c3a430a80e6f2', 'dev_requirement' => false, ), 'symfony/translation-contracts' => array( 'pretty_version' => 'v3.6.1', 'version' => '3.6.1.0', + 'reference' => '65a8bc82080447fae78373aa10f8d13b38338977', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/translation-contracts', 'aliases' => array(), - 'reference' => '65a8bc82080447fae78373aa10f8d13b38338977', 'dev_requirement' => false, ), 'symfony/translation-implementation' => array( @@ -2068,91 +2110,100 @@ 'symfony/uid' => array( 'pretty_version' => 'v6.4.24', 'version' => '6.4.24.0', + 'reference' => '17da16a750541a42cf2183935e0f6008316c23f7', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/uid', 'aliases' => array(), - 'reference' => '17da16a750541a42cf2183935e0f6008316c23f7', 'dev_requirement' => false, ), 'symfony/var-dumper' => array( 'pretty_version' => 'v6.4.26', 'version' => '6.4.26.0', + 'reference' => 'cfae1497a2f1eaad78dbc0590311c599c7178d4a', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-dumper', 'aliases' => array(), - 'reference' => 'cfae1497a2f1eaad78dbc0590311c599c7178d4a', 'dev_requirement' => false, ), + 'symfony/var-exporter' => array( + 'pretty_version' => 'v8.0.8', + 'version' => '8.0.8.0', + 'reference' => '15776bb07a91b089037da89f8832fa41d5fa6ec6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-exporter', + 'aliases' => array(), + 'dev_requirement' => true, + ), 'symfony/yaml' => array( 'pretty_version' => 'v7.4.1', 'version' => '7.4.1.0', + 'reference' => '24dd4de28d2e3988b311751ac49e684d783e2345', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), - 'reference' => '24dd4de28d2e3988b311751ac49e684d783e2345', 'dev_requirement' => true, ), 'thecodingmachine/safe' => array( 'pretty_version' => 'v3.3.0', 'version' => '3.3.0.0', + 'reference' => '2cdd579eeaa2e78e51c7509b50cc9fb89a956236', 'type' => 'library', 'install_path' => __DIR__ . '/../thecodingmachine/safe', 'aliases' => array(), - 'reference' => '2cdd579eeaa2e78e51c7509b50cc9fb89a956236', 'dev_requirement' => false, ), 'theseer/tokenizer' => array( 'pretty_version' => '1.3.1', 'version' => '1.3.1.0', + 'reference' => 'b7489ce515e168639d17feec34b8847c326b0b3c', 'type' => 'library', 'install_path' => __DIR__ . '/../theseer/tokenizer', 'aliases' => array(), - 'reference' => 'b7489ce515e168639d17feec34b8847c326b0b3c', 'dev_requirement' => true, ), 'tijsverkoyen/css-to-inline-styles' => array( 'pretty_version' => 'v2.4.0', 'version' => '2.4.0.0', + 'reference' => 'f0292ccf0ec75843d65027214426b6b163b48b41', 'type' => 'library', 'install_path' => __DIR__ . '/../tijsverkoyen/css-to-inline-styles', 'aliases' => array(), - 'reference' => 'f0292ccf0ec75843d65027214426b6b163b48b41', 'dev_requirement' => false, ), 'twilio/sdk' => array( 'pretty_version' => '8.10.1', 'version' => '8.10.1.0', + 'reference' => '84fc8e4b2b5ff32d20e73b17718b13e5ed56bc8c', 'type' => 'library', 'install_path' => __DIR__ . '/../twilio/sdk', 'aliases' => array(), - 'reference' => '84fc8e4b2b5ff32d20e73b17718b13e5ed56bc8c', 'dev_requirement' => false, ), 'tzsk/sms' => array( 'pretty_version' => '9.0.0', 'version' => '9.0.0.0', + 'reference' => '0e06b8f8f4116887dcf28238dc6a8dd350a1569e', 'type' => 'library', 'install_path' => __DIR__ . '/../tzsk/sms', 'aliases' => array(), - 'reference' => '0e06b8f8f4116887dcf28238dc6a8dd350a1569e', 'dev_requirement' => false, ), 'vlucas/phpdotenv' => array( 'pretty_version' => 'v5.6.3', 'version' => '5.6.3.0', + 'reference' => '955e7815d677a3eaa7075231212f2110983adecc', 'type' => 'library', 'install_path' => __DIR__ . '/../vlucas/phpdotenv', 'aliases' => array(), - 'reference' => '955e7815d677a3eaa7075231212f2110983adecc', 'dev_requirement' => false, ), 'voku/portable-ascii' => array( 'pretty_version' => '2.0.3', 'version' => '2.0.3.0', + 'reference' => 'b1d923f88091c6bf09699efcd7c8a1b1bfd7351d', 'type' => 'library', 'install_path' => __DIR__ . '/../voku/portable-ascii', 'aliases' => array(), - 'reference' => 'b1d923f88091c6bf09699efcd7c8a1b1bfd7351d', 'dev_requirement' => false, ), ), diff --git a/vendor/knuckleswtf/scribe/.agents/skills/release-minor/SKILL.md b/vendor/knuckleswtf/scribe/.agents/skills/release-minor/SKILL.md new file mode 100644 index 00000000..5a4ffce7 --- /dev/null +++ b/vendor/knuckleswtf/scribe/.agents/skills/release-minor/SKILL.md @@ -0,0 +1,38 @@ +--- +name: release-minor +description: Automates the process of tagging a new minor release for Scribe by analyzing commit messages, updating the changelog, and creating a GitHub release. +--- + +# Release Minor Version + +This skill automates the process of tagging a new minor release for Scribe. + +## Workflow + +1. **Analyze Commits & Build Changelog**: + - Identify the last release tag (e.g., using `git tag --sort=-v:refname | head -n 1`). + - Get all commits from the last tag to `HEAD`. + - Analyze commit messages to categorize them into "Added", "Modified", "Fixed", or "Removed". + - Draft a new section for `CHANGELOG.md` following the existing format: + ```markdown + ## () + ### Added + - [Description] ([#PR](link)) + + ### Modified + - ... + ``` + +2. **Update Files**: + - Prepend the new section to the top of the release list in `CHANGELOG.md`. + - Update the `public const VERSION` in `src/Scribe.php` to the new version number. + +3. **Commit and Push**: + - Stage `CHANGELOG.md` and `src/Scribe.php`. + - Commit with the message: `Bump version to `. + - Push the changes to the remote repository. + +4. **Create GitHub Release**: + - Use the `gh` CLI to create a new release. + - Command: `gh release create --title "" --notes ""` + - Ensure the notes correspond exactly to the added changelog section. diff --git a/vendor/knuckleswtf/scribe/.githooks/pre-commit b/vendor/knuckleswtf/scribe/.githooks/pre-commit new file mode 100644 index 00000000..3bf3cb9f --- /dev/null +++ b/vendor/knuckleswtf/scribe/.githooks/pre-commit @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +# shared pre-commit hook to run pint +# Try several fallbacks so it works on Unix and common Windows setups. + +# Prefer directly executable vendor binary +if [ -x "./vendor/bin/pint" ]; then + ./vendor/bin/pint . + exit $? +fi + +# If it's a PHP wrapper script, run with php +if [ -f "./vendor/bin/pint" ]; then + php "./vendor/bin/pint" . + exit $? +fi + +# If a Windows batch exists under vendor/bin +if [ -x "./vendor/bin/pint.bat" ]; then + ./vendor/bin/pint.bat . + exit $? +fi + +if [ -f "./vendor/bin/pint.bat" ]; then + # Try running with cmd.exe for Windows Git environments + if command -v cmd.exe >/dev/null 2>&1; then + cmd.exe /C "vendor\\bin\\pint.bat fix" + exit $? + fi +fi + +# Nothing to run; do not block the push but inform the user +echo "pint not found at ./vendor/bin/pint; skipping pre-push formatting." +exit 0 + diff --git a/vendor/knuckleswtf/scribe/AGENTS.md b/vendor/knuckleswtf/scribe/AGENTS.md new file mode 100644 index 00000000..3fe8615a --- /dev/null +++ b/vendor/knuckleswtf/scribe/AGENTS.md @@ -0,0 +1,82 @@ +# Agent Guidelines for Scribe + +This repository contains the source code for Scribe, a PHP package that generates API documentation for Laravel applications. + +## 🛠 Project Tooling & Commands + +This project uses **Composer** for dependency management and scripts. +- **Test Runner**: [Pest](https://pestphp.com/) (which wraps PHPUnit). +- **Static Analysis**: [PHPStan](https://phpstan.org/). +- **Code Style**: [PHP-CS-Fixer](https://cs.symfony.com/). + +### Key Commands + +| Action | Command | Description | +|--------|---------|-------------| +| **Run Tests** | `composer test` | Runs all tests using Pest. | +| **Run Single Test** | `vendor/bin/pest --filter MethodName` | Runs a specific test method. | +| **Run File Tests** | `vendor/bin/pest path/to/Test.php` | Runs all tests in a specific file. | +| **Lint Code** | `composer lint` | Runs PHPStan analysis. | +| **Check Style** | `composer style:check` | Checks code style without modifying files. | +| **Fix Style** | `composer style:fix` | Automatically fixes code style issues. | + +> **Note**: Always run `composer test` and `composer lint` before submitting changes. + +## 📐 Code Style & Conventions + +Adhere strictly to the existing style. The project generally follows **PSR-12**. + +### General PHP +- **Version**: PHP 8.1+. Use modern features like typed properties, union types, and constructor promotion where appropriate. +- **Strict Types**: Do **NOT** use `declare(strict_types=1);` unless strictly necessary or found in the file being edited (it is generally absent). +- **Indentation**: 4 spaces. +- **Arrays**: Use short array syntax `[]`. +- **Trailing Commas**: Use trailing commas in multi-line arrays. + +### Naming +- **Classes**: `PascalCase` (e.g., `PostmanCollectionWriter`). +- **Methods**: `camelCase` (e.g., `generatePostmanCollection`). +- **Variables**: `camelCase` (e.g., `$groupedEndpoints`). +- **Constants**: `UPPER_CASE_WITH_UNDERSCORES`. + +### Imports & Namespaces +- **Ordering**: Alphabetical order. +- **Grouping**: Group standard library imports separately if applicable, but generally keep them sorted. +- **Unused Imports**: Remove unused imports. + +### Type Hinting & Docblocks +- **Types**: Use native PHP type hints (param and return types) whenever possible. + - Example: `public function writeDocs(array $groupedEndpoints): void` +- **Docblocks**: + - Use PHPDoc for generic arrays to specify content types (e.g., `/** @param array $config */`). + - Use `/** @var ClassName $var */` for inline type hinting when the static analyzer cannot infer it (e.g., resolving from container). + - Do not duplicate native type info in docblocks unless adding detail. + +### Laravel Integration +- **Dependency Injection**: Prefer constructor injection. +- **Helpers**: Use Laravel helpers (`app()`, `config()`, `public_path()`) where appropriate and consistent with existing code. +- **Facades**: Facades are used (e.g., `Storage`, `Route`). Import them via `Illuminate\Support\Facades\...`. + +## 🧪 Testing Guidelines + +- **Framework**: Tests are written using PHPUnit class syntax (extending `BaseLaravelTest` or `TestCase`), but executed via Pest. +- **Location**: Place unit tests in `tests/Unit/`. +- **Naming**: Test classes end in `Test.php`. Test methods use the `/** @test */` annotation or start with `test`. +- **Assertions**: Use standard PHPUnit assertions (e.g., `$this->assertEquals`, `$this->assertCount`). +- **Mocking**: Use Mockery or Laravel's mocking helpers if applicable. + +## 📂 File Structure + +- `src/`: Core package source code. + - `Writing/`: Logic for generating output files (HTML, Postman, OpenAPI). + - `Tools/`: Utility classes. + - `Attributes/`: PHP Attributes used by the package. +- `tests/`: Test suite. +- `resources/views/`: Blade templates for generated documentation. +- `config/`: Configuration files. + +## 📝 Error Handling + +- Use specific exceptions where possible (e.g., `ScribeException`). +- Handle errors gracefully, especially when parsing user code or configuration. +- Use `ConsoleOutputUtils` (aliased as `c`) for CLI output in commands. diff --git a/vendor/knuckleswtf/scribe/CHANGELOG.md b/vendor/knuckleswtf/scribe/CHANGELOG.md new file mode 100644 index 00000000..d66b3ef7 --- /dev/null +++ b/vendor/knuckleswtf/scribe/CHANGELOG.md @@ -0,0 +1,115 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project aims to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## () +### Fixed + +### Modified + +### Added + +### Removed + +## 5.9.0 (21 March 2026) +### Modified +- Add symfony/yaml v8 support ([#1063](https://github.com/knuckleswtf/scribe/pull/1063)) + +## 5.8.0 (23 February 2026) +### Added +- Allow opt in suppressing of model events when generating response data with factories ([#1057](https://github.com/knuckleswtf/scribe/pull/1057)) +- Add agent skill for releasing (c56c096d) + +### Modified +- Bump dependencies for Laravel 13 ([#1060](https://github.com/knuckleswtf/scribe/pull/1060)) + +### Fixed +- Fix undefined array key 'text' in MarkdownParser::blockHeader ([#1059](https://github.com/knuckleswtf/scribe/pull/1059)) + +## 5.7.0 (04 February 2026) +### Added +- Feature: laravel console output ([#1047](https://github.com/knuckleswtf/scribe/pull/1047)) +- Add AGENTS.md for AI coding assistants ([#1055](https://github.com/knuckleswtf/scribe/pull/1055)) +- Add OpenAPI 3.1 schema example to examples conversion ([#1042](https://github.com/knuckleswtf/scribe/pull/1042)) + +### Modified +- Replace erusev/parsedown with parsedown/parsedown for PHP 8.4 compatibility ([#1049](https://github.com/knuckleswtf/scribe/pull/1049)) +- Use pint for code style ([#1054](https://github.com/knuckleswtf/scribe/pull/1054)) + +### Fixed +- Fix HTML special characters breaking Try It Out panel JSON display ([#1048](https://github.com/knuckleswtf/scribe/pull/1048)) +- Respect wrapping of API resources ([#1046](https://github.com/knuckleswtf/scribe/pull/1046)) +- Prevent empty enums when omitting ResponseField parameters ([#1044](https://github.com/knuckleswtf/scribe/pull/1044)) +- Openapi nullable response fields ([#1041](https://github.com/knuckleswtf/scribe/pull/1041)) + +## 5.6.0 (23 November 2025) +- Add support for OpenAPI v3.1 specification ([#1040](https://github.com/knuckleswtf/scribe/pull/1040)) + - Added `openapi.version` configuration option to choose between OpenAPI 3.0.3 (default) and 3.1.0 + - OpenAPI 3.1 uses JSON Schema Draft 2020-12 compatible nullable syntax: `type: ["string", "null"]` + - OpenAPI 3.0 continues to use `nullable: true` property + - Fully backward compatible - defaults to 3.0.3 if not configured + +## 5.5.0 (25 October 2025) +### Modified +- Change shalvah/clara constraint + +### Fixed +- fix: handle rare edge case in base OpenApi spec generator after parallel merge ([#1031](https://github.com/knuckleswtf/scribe/pull/1031)) + +## 5.4.0 (21 October 2025) +### Fixed +- fix: arrays of objects support in OpenAPI response generation ([1021](https://github.com/knuckleswtf/scribe/pull/1021)) +- fix: white space issue with headers displaying in endpoint view ([#1023](https://github.com/knuckleswtf/scribe/pull/1023)) +- fix: crash when API resource __construct enforces type in ResponseFromApiResource ([#1028](https://github.com/knuckleswtf/scribe/pull/1028)) + +### Modified +- Replace abandoned spatie/data-transfer-object with own DTO implementation ([#1024](https://github.com/knuckleswtf/scribe/pull/1024)) + +### Added +- Support `deprecated:` option for `#[BodyParam]/#[QueryParam]` attributes ([#1022](https://github.com/knuckleswtf/scribe/pull/1022)) +- Support for `sometimes` rule ([958](https://github.com/knuckleswtf/scribe/pull/958)) +- Support for custom response Content-Types (problem+json, etc.) ([#1029](https://github.com/knuckleswtf/scribe/pull/1029)) +- Support strings for the `#[Deprecated]` attribute ([#1019](https://github.com/knuckleswtf/scribe/pull/1019)) + +## 5.3.0 (29 July 2025) +### Added +- Support `@deprecated`/`[#Deprecated]` annotations for deprecating endpoints, along with deprecated badge in the included themes ([#994](https://github.com/knuckleswtf/scribe/pull/994)) +- Add enum list to Open API spec response properties ([#902](https://github.com/knuckleswtf/scribe/pull/902)) + +### Fixed +- Format response codes as strings in OpenAPI spec ([80d21f1c46](https://github.com/knuckleswtf/scribe/commit/80d21f1c4678e44ba8e2e549f075e7b3bfd72fe5)) +- Don't escape $baseUrl in view ([39695304c9c](https://github.com/knuckleswtf/scribe/commit/39695304c9cd75d627a4e8b59fe20b4636581066)) +- Possible empty part of Route when path_param is not mandatory ([#992](https://github.com/knuckleswtf/scribe/pull/992)) +- Postman collection generation failing due to invalid UTF-8 characters ([#997](https://github.com/knuckleswtf/scribe/pull/997)) +- Use Recursive Spec Merge in OpenAPI SecurityGenerator & Fix OverrideGenerator base ([#1003](https://github.com/knuckleswtf/scribe/pull/1003)) + +### Changed +- Resolve fully qualified names ([#1008](https://github.com/knuckleswtf/scribe/pull/1008)) +- Ensure Validator facade rules are detected ([#1006](https://github.com/knuckleswtf/scribe/pull/1006)) +- Move intro_text directly after description in config/scribe.php for easier discovery ([#1001](https://github.com/knuckleswtf/scribe/pull/1001)) + +## 5.2.1 (1 May 2025) +### Added +- Fix regressions in parsing validation rules [a9e7a668d](https://github.com/knuckleswtf/scribe/commit/a9e7a668d7fa74ad8a1591e443db6600498238ef) + +## 5.2.0 (17 April 2025) +### Added +- Fix breaking bugfix for validation rules (array of objects) in newer Laravel versions [03968babc9](https://github.com/knuckleswtf/scribe/commit/03968babc901d38a284d3569000205e7d38ba1e1) + +### Fixed +- Avoid swallowing errors on example model instantiation (#964)[https://github.com/knuckleswtf/scribe/pull/964] + +## 5.1.0 (25 February 2025) +### Added +- Support for streamed responses in response calls [790ad94e512](https://github.com/knuckleswtf/scribe/commit/790ad94e512d987feae6f0443835d8cf8de64f53) + +### Fixed +- Fixed use of `URL::useOrigin` vs `URL::forceRootURL` [956e9bf418](https://github.com/knuckleswtf/scribe/commit/956e9bf418f5fc06fe70009e476b1e8524aff5b1) + +## 5.0.1 (20 February 2025) +### Fixed +- Fix bug in wrongly trying to determine required fields for array of strings [#951](https://github.com/knuckleswtf/scribe/pull/951) + +## 5.0.0 (19 February 2025) +See the [migration guide](https://scribe.knuckles.wtf/migrating). diff --git a/vendor/knuckleswtf/scribe/CONTRIBUTING.md b/vendor/knuckleswtf/scribe/CONTRIBUTING.md new file mode 100644 index 00000000..24232856 --- /dev/null +++ b/vendor/knuckleswtf/scribe/CONTRIBUTING.md @@ -0,0 +1,2 @@ +# Contributing +Please see [our contribution guide](https://scribe.knuckles.wtf/laravel/contributing) diff --git a/vendor/knuckleswtf/scribe/LICENSE.md b/vendor/knuckleswtf/scribe/LICENSE.md new file mode 100644 index 00000000..27a1b1f1 --- /dev/null +++ b/vendor/knuckleswtf/scribe/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Shalvah + +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/vendor/knuckleswtf/scribe/README.md b/vendor/knuckleswtf/scribe/README.md new file mode 100644 index 00000000..b316cbbe --- /dev/null +++ b/vendor/knuckleswtf/scribe/README.md @@ -0,0 +1,34 @@ +
+logo-scribe
+ +[![Latest Stable Version](https://poser.pugx.org/knuckleswtf/scribe/v/stable)](https://packagist.org/packages/knuckleswtf/scribe) +[![Total Downloads](https://poser.pugx.org/knuckleswtf/scribe/downloads)](https://packagist.org/packages/knuckleswtf/scribe) +[![Code Style](https://img.shields.io/badge/code_style-pint-f58d33)](https://github.com/laravel/pint) + +
+ + +Scribe helps you generate API documentation for humans from your Laravel codebase. See a live example at [demo.scribe.knuckles.wtf](https://demo.scribe.knuckles.wtf). + +## Features +- Useful output: + - Pretty single-page HTML doc, with human-friendly text, code samples, and in-browser API tester ("Try It Out") + - Generates Postman collection and OpenAPI spec (v3.0.3 or v3.1.0) +- Smarts. Scribe can: + - extract request parameter details from FormRequests or validation rules + - safely call API endpoints to get sample responses + - generate sample responses from Eloquent API Resources or Transformers +- Customizable to different levels: + - Customize the UI by adjusting text, ordering, examples, or changing the UI itself + - Add custom strategies to adjust how data is extracted + - Statically define extra endpoints or information not in your codebase + +> [!TIP] +> 👋 Scribe helps you generate docs automatically, but if you really want to make friendly, maintainable, and testable API docs, there are some more things you need to know. +> So I made [a course](https://shalvah.teachable.com/p/api-documentation-for-developers?utm_source=scribe-laravel&utm_medium=referral&utm_campaign=none) for you.🤗 + +## Documentation +Check out the documentation at [scribe.knuckles.wtf/laravel](http://scribe.knuckles.wtf/laravel). + +## Contributing +Contributing is easy! See our [contribution guide](https://scribe.knuckles.wtf/laravel/contributing). diff --git a/vendor/knuckleswtf/scribe/camel/BaseDTO.php b/vendor/knuckleswtf/scribe/camel/BaseDTO.php new file mode 100644 index 00000000..00284197 --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/BaseDTO.php @@ -0,0 +1,172 @@ +initializeProperties(); + + foreach ($parameters as $key => $value) { + if (property_exists($this, $key)) { + $this->{$key} = $this->castProperty($key, $value); + } + } + } + + public static function create(array|self $data, array|self $inheritFrom = []): static + { + if ($data instanceof static) { + return $data; + } + + $mergedData = $inheritFrom instanceof static ? $inheritFrom->toArray() : $inheritFrom; + + foreach ($data as $property => $value) { + $mergedData[$property] = $value; + } + + return new static($mergedData); + } + + public function toArray(): array + { + $array = []; + foreach (get_object_vars($this) as $property => $value) { + $array[$property] = $value; + } + + return $this->parseArray($array); + } + + public static function make(array|self $data): static + { + return $data instanceof static ? $data : new static($data); + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->{$offset}); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->{$offset}; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->{$offset} = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->{$offset}); + } + + public function except(string ...$keys): array + { + $array = []; + foreach (get_object_vars($this) as $property => $value) { + if (! in_array($property, $keys)) { + $array[$property] = $value; + } + } + + return $this->parseArray($array); + } + + public static function arrayOf(array $items): array + { + return array_map(function ($item) { + return $item instanceof static ? $item : new static($item); + }, $items); + } + + protected function initializeProperties(): void + { + $reflection = new \ReflectionClass($this); + + foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + + // Skip if already initialized (has a default value) + if ($property->hasDefaultValue()) { + continue; + } + + $type = $property->getType(); + if ($type && $type->allowsNull()) { + $this->{$name} = null; + } + } + } + + protected function castProperty(string $key, mixed $value): mixed + { + // If the value is already the correct type, return it as-is + if (! is_array($value)) { + return $value; + } + + // Get property type through reflection + $reflection = new \ReflectionClass($this); + if (! $reflection->hasProperty($key)) { + return $value; + } + + $property = $reflection->getProperty($key); + $type = $property->getType(); + + if ($type && $type instanceof \ReflectionNamedType && ! $type->isBuiltin()) { + $className = $type->getName(); + + // If it's a DTO class in our namespace, instantiate it + if (class_exists($className) && is_subclass_of($className, self::class)) { + return new $className($value); + } + + // If it's another class in our namespace that has a constructor accepting arrays + if (class_exists($className)) { + try { + return new $className($value); + } catch (\Throwable $e) { + // If instantiation fails, return the original value + return $value; + } + } + } + + return $value; + } + + protected function parseArray(array $array): array + { + // Reimplementing here so our DTOCollection items can be recursively toArray'ed + foreach ($array as $key => $value) { + if ($value instanceof Arrayable) { + $array[$key] = $value->toArray(); + + continue; + } + + if (! is_array($value)) { + continue; + } + + $array[$key] = $this->parseArray($value); + } + + return $array; + } +} diff --git a/vendor/knuckleswtf/scribe/camel/BaseDTOCollection.php b/vendor/knuckleswtf/scribe/camel/BaseDTOCollection.php new file mode 100644 index 00000000..870ee42c --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/BaseDTOCollection.php @@ -0,0 +1,50 @@ + is_array($item) ? new static::$base($item) : $item, + $items instanceof Collection ? $items->toArray() : $items + ); + + parent::__construct($items); + } + + /** + * Append items to the collection, mutating it. + * + * @param array[]|T[] $items + */ + public function concat($items) + { + foreach ($items as $item) { + $this->push(is_array($item) ? new static::$base($item) : $item); + } + + return $this; + } + + public function toArray(): array + { + return array_map( + fn ($item) => $item instanceof Arrayable ? $item->toArray() : $item, + $this->items + ); + } +} diff --git a/vendor/knuckleswtf/scribe/camel/Camel.php b/vendor/knuckleswtf/scribe/camel/Camel.php new file mode 100644 index 00000000..bbb6053c --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/Camel.php @@ -0,0 +1,231 @@ +intermediateOutputPath('endpoints.cache'); + } + + public static function camelDir(PathConfig $paths): string + { + return $paths->intermediateOutputPath('endpoints'); + } + + /** + * Load endpoints from the Camel files into groups (arrays). + * + * @return array[] each array is a group with keys including `name` and `endpoints` + */ + public static function loadEndpointsIntoGroups(string $folder): array + { + $groups = []; + self::loadEndpointsFromCamelFiles($folder, function (array $group) use (&$groups) { + $groups[$group['name']] = $group; + }); + + return $groups; + } + + /** + * Load endpoints from the Camel files into a flat list of endpoint arrays. + * Useful when we don't care about groups, but simply want to compare endpoints contents + * to see if anything changed. + * + * @return array[] list of endpoint arrays + */ + public static function loadEndpointsToFlatPrimitivesArray(string $folder): array + { + $endpoints = []; + self::loadEndpointsFromCamelFiles($folder, function (array $group) use (&$endpoints) { + foreach ($group['endpoints'] as $endpoint) { + $endpoints[] = $endpoint; + } + }); + + return $endpoints; + } + + public static function loadEndpointsFromCamelFiles(string $folder, callable $callback): void + { + $contents = Utils::listDirectoryContents($folder); + + foreach ($contents as $object) { + if ( + $object->isFile() + && Str::endsWith(basename($object->path()), '.yaml') + && ! Str::startsWith(basename($object->path()), 'custom.') + ) { + $group = Yaml::parseFile($object['path']); + $callback($group); + } + } + } + + public static function loadUserDefinedEndpoints(string $folder): array + { + $contents = Utils::listDirectoryContents($folder); + + $userDefinedEndpoints = []; + foreach ($contents as $object) { + if ( + $object->isFile() + && Str::endsWith(basename($object->path()), '.yaml') + && Str::startsWith(basename($object->path()), 'custom.') + ) { + $endpoints = Yaml::parseFile($object->path()); + foreach (($endpoints ?: []) as $endpoint) { + $userDefinedEndpoints[] = $endpoint; + } + } + } + + return $userDefinedEndpoints; + } + + public static function doesGroupContainEndpoint(array $group, OutputEndpointData $endpoint): bool + { + return (bool) (Arr::first($group['endpoints'], function ($e) use ($endpoint) { + return $e->endpointId() === $endpoint->endpointId(); + })); + } + + /** + * @param array[] $groupedEndpoints + * @param array $configFileOrder the order for groups that users specified in their config file + * @return array[] + */ + public static function sortByConfigFileOrder(array $groupedEndpoints, array $configFileOrder): array + { + if (empty($configFileOrder)) { + ksort($groupedEndpoints, SORT_NATURAL); + + return $groupedEndpoints; + } + + // First, sort groups + $groupsOrder = Utils::getTopLevelItemsFromMixedConfigList($configFileOrder); + $groupsCollection = collect($groupedEndpoints); + $wildcardPosition = array_search('*', $groupsOrder); + if ($wildcardPosition !== false) { + $promotedGroups = array_splice($groupsOrder, 0, $wildcardPosition); + $demotedGroups = array_splice($groupsOrder, 1); + + $promotedOrderedGroups = $groupsCollection->filter(fn ($group, $groupName) => in_array($groupName, $promotedGroups)) + ->sortKeysUsing(self::getOrderListComparator($promotedGroups)); + $demotedOrderedGroups = $groupsCollection->filter(fn ($group, $groupName) => in_array($groupName, $demotedGroups)) + ->sortKeysUsing(self::getOrderListComparator($demotedGroups)); + + $nonWildcardGroups = array_merge($promotedGroups, $demotedGroups); + $wildCardOrderedGroups = $groupsCollection->filter(fn ($group, $groupName) => ! in_array($groupName, $nonWildcardGroups)) + ->sortKeysUsing(self::getOrderListComparator($demotedGroups)); + + $groupedEndpoints = $promotedOrderedGroups->merge($wildCardOrderedGroups) + ->merge($demotedOrderedGroups); + } else { + $groupedEndpoints = $groupsCollection->sortKeysUsing(self::getOrderListComparator($groupsOrder)); + } + + return $groupedEndpoints->map(function (array $group, string $groupName) use ($configFileOrder) { + $sortedEndpoints = collect($group['endpoints']); + + if (isset($configFileOrder[$groupName])) { + // Second-level order list. Can contain endpoint or subgroup names + $level2Order = Utils::getTopLevelItemsFromMixedConfigList($configFileOrder[$groupName]); + $sortedEndpoints = $sortedEndpoints->sortBy( + function (OutputEndpointData $e) use ($configFileOrder, $level2Order) { + $endpointIdentifier = $e->httpMethods[0].' /'.$e->uri; + + // First, check if there's an ordering specified for the endpoint itself + $indexOfEndpointInL2Order = array_search($endpointIdentifier, $level2Order); + if ($indexOfEndpointInL2Order !== false) { + return $indexOfEndpointInL2Order; + } + + // Check if there's an ordering for the endpoint's subgroup + $indexOfSubgroupInL2Order = array_search($e->metadata->subgroup, $level2Order); + if ($indexOfSubgroupInL2Order !== false) { + // There's a subgroup order; check if there's an endpoints order within that + $orderOfEndpointsInSubgroup = $configFileOrder[$e->metadata->groupName][$e->metadata->subgroup] ?? []; + $indexOfEndpointInSubGroup = array_search($endpointIdentifier, $orderOfEndpointsInSubgroup); + + return ($indexOfEndpointInSubGroup === false) + ? $indexOfSubgroupInL2Order + : ($indexOfSubgroupInL2Order + ($indexOfEndpointInSubGroup * 0.1)); + } + + return INF; + }, + ); + } + + return [ + 'name' => $groupName, + 'description' => $group['description'], + 'endpoints' => $sortedEndpoints->all(), + ]; + })->values()->all(); + } + + /** + * Prepare endpoints to be turned into HTML. + * Map them into OutputEndpointData DTOs, and sort them by the specified order in the config file. + * + * @param array $groupedEndpoints + */ + public static function prepareGroupedEndpointsForOutput(array $groupedEndpoints, array $configFileOrder = []): array + { + $groups = array_map(function (array $group) { + return [ + 'name' => $group['name'], + 'description' => $group['description'], + 'endpoints' => array_map( + fn (array $endpoint) => OutputEndpointData::fromExtractedEndpointArray($endpoint), + $group['endpoints'] + ), + ]; + }, $groupedEndpoints); + + return self::sortByConfigFileOrder($groups, $configFileOrder); + } + + /** + * Given an $order list like ['first', 'second', ...], return a compare function that can be used to sort + * a list of strings based on the order of items in $order. + * Any strings not in the list are sorted with natural sort. + */ + protected static function getOrderListComparator(array $order): \Closure + { + return function ($a, $b) use ($order) { + $indexOfA = array_search($a, $order); + $indexOfB = array_search($b, $order); + + // If both are in the $order list, compare them normally based on their position in the list + if ($indexOfA !== false && $indexOfB !== false) { + return $indexOfA <=> $indexOfB; + } + + // If only A is in the $order list, then it must come before B. + if ($indexOfA !== false) { + return -1; + } + + // If only B is in the $order list, then it must come before A. + if ($indexOfB !== false) { + return 1; + } + + // If neither is present, fall back to natural sort + return strnatcmp($a, $b); + }; + } +} diff --git a/vendor/knuckleswtf/scribe/camel/Extraction/ExtractedEndpointData.php b/vendor/knuckleswtf/scribe/camel/Extraction/ExtractedEndpointData.php new file mode 100644 index 00000000..97130d62 --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/Extraction/ExtractedEndpointData.php @@ -0,0 +1,169 @@ + + */ + public array $httpMethods; + + public string $uri; + + public Metadata $metadata; + + /** + * @var array + */ + public array $headers = []; + + /** + * @var array + */ + public array $urlParameters = []; + + /** + * @var array + */ + public array $cleanUrlParameters = []; + + /** + * @var array + */ + public array $queryParameters = []; + + /** + * @var array + */ + public array $cleanQueryParameters = []; + + /** + * @var array + */ + public array $bodyParameters = []; + + /** + * @var array + */ + public array $cleanBodyParameters = []; + + /** + * @var array + */ + public array $fileParameters = []; + + public ResponseCollection $responses; + + /** + * @var array + */ + public array $responseFields = []; + + /** + * Authentication info for this endpoint. In the form [{where}, {name}, {sample}] + * Example: ["queryParameters", "api_key", "njiuyiw97865rfyvgfvb1"]. + */ + public array $auth = []; + + public ?\ReflectionClass $controller; + + public ?\ReflectionFunctionAbstract $method; + + public ?Route $route; + + public function __construct(array $parameters = []) + { + $parameters['metadata'] ??= new Metadata([]); + $parameters['responses'] ??= new ResponseCollection([]); + + parent::__construct($parameters); + + $defaultNormalizer = fn () => UrlParamsNormalizer::normalizeParameterNamesInRouteUri($this->route, $this->method); + $this->uri = match (is_callable(Globals::$__normalizeEndpointUrlUsing)) { + true => call_user_func_array( + Globals::$__normalizeEndpointUrlUsing, + [$this->route->uri, $this->route, $this->method, $this->controller, $defaultNormalizer] + ), + default => $defaultNormalizer(), + }; + } + + /** + * @param array $extras Only used for quick overrides in tests + * + * @throws \ReflectionException + */ + public static function fromRoute(Route $route, array $extras = []): self + { + $httpMethods = self::getMethods($route); + $uri = $route->uri(); + + [$controllerName, $methodName] = u::getRouteClassAndMethodNames($route); + $controller = new \ReflectionClass($controllerName); + $method = u::getReflectedRouteMethod([$controllerName, $methodName]); + + $data = compact('httpMethods', 'uri', 'controller', 'method', 'route'); + $data = array_merge($data, $extras); + + return new self($data); + } + + /** + * @return array + */ + public static function getMethods(Route $route): array + { + $methods = $route->methods(); + + // Laravel adds an automatic "HEAD" endpoint for each GET request, so we'll strip that out, + // but not if there's only one method (means it was intentional) + if (count($methods) === 1) { + return $methods; + } + + return array_diff($methods, ['HEAD']); + } + + public function name() + { + return sprintf("[%s] {$this->route->uri}.", implode(',', $this->route->methods)); + } + + public function endpointId() + { + return $this->httpMethods[0].str_replace(['/', '?', '{', '}', ':', '\\', '+', '|'], '-', $this->uri); + } + + /** + * Prepare the endpoint data for serialising. + */ + public function forSerialisation() + { + $copyArray = $this->except( + // Get rid of all duplicate data + 'cleanQueryParameters', + 'cleanUrlParameters', + 'fileParameters', + 'cleanBodyParameters', + // and objects used only in extraction + 'route', + 'controller', + 'method', + 'auth', + ); + // Remove these, since they're on the parent group object + if (isset($copyArray['metadata']) && $copyArray['metadata'] instanceof Metadata) { + $copyArray['metadata'] = $copyArray['metadata']->except('groupName', 'groupDescription'); + } + + return $copyArray; + } +} diff --git a/vendor/knuckleswtf/scribe/camel/Extraction/Metadata.php b/vendor/knuckleswtf/scribe/camel/Extraction/Metadata.php new file mode 100644 index 00000000..4c36b3ec --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/Extraction/Metadata.php @@ -0,0 +1,24 @@ +status; + if ($this->description) { + $description .= ", {$this->description}"; + } + + return $description; + } + + public function isBinary(): bool + { + return is_string($this->content) && Str::startsWith($this->content, '<>'); + } +} diff --git a/vendor/knuckleswtf/scribe/camel/Extraction/ResponseCollection.php b/vendor/knuckleswtf/scribe/camel/Extraction/ResponseCollection.php new file mode 100644 index 00000000..f0798e21 --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/Extraction/ResponseCollection.php @@ -0,0 +1,20 @@ + + */ +class ResponseCollection extends BaseDTOCollection +{ + public static string $base = Response::class; + + public function hasSuccessResponse(): bool + { + return $this->first( + fn ($response) => '2' === ((string) ($response->status))[0] + ) !== null; + } +} diff --git a/vendor/knuckleswtf/scribe/camel/Extraction/ResponseField.php b/vendor/knuckleswtf/scribe/camel/Extraction/ResponseField.php new file mode 100644 index 00000000..c728081e --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/Extraction/ResponseField.php @@ -0,0 +1,31 @@ + + */ + public array $httpMethods; + + public string $uri; + + public Metadata $metadata; + + /** + * @var array + */ + public array $headers = []; + + /** + * @var array + */ + public array $urlParameters = []; + + /** + * @var array + */ + public array $cleanUrlParameters = []; + + /** + * @var array + */ + public array $queryParameters = []; + + /** + * @var array + */ + public array $cleanQueryParameters = []; + + /** + * @var array + */ + public array $bodyParameters = []; + + /** + * @var array + */ + public array $cleanBodyParameters = []; + + /** + * @var array + */ + public array $fileParameters = []; + + public ResponseCollection $responses; + + /** + * @var array + */ + public array $responseFields = []; + + /** + * The same as bodyParameters, but organized in a hierarchy. + * So, top-level items first, with a __fields property containing their children, and so on. + * Useful so we can easily render and nest on the frontend. + * + * @var array + */ + public array $nestedBodyParameters = []; + + /** + * @var array + */ + public array $nestedResponseFields = []; + + public ?string $boundUri; + + public function __construct(array $parameters = []) + { + // spatie/dto currently doesn't auto-cast nested DTOs like that + $parameters['responses'] = new ResponseCollection($parameters['responses'] ?? []); + $parameters['bodyParameters'] = array_map(fn ($param) => new Parameter($param), $parameters['bodyParameters'] ?? []); + $parameters['queryParameters'] = array_map(fn ($param) => new Parameter($param), $parameters['queryParameters'] ?? []); + $parameters['urlParameters'] = array_map(fn ($param) => new Parameter($param), $parameters['urlParameters'] ?? []); + $parameters['responseFields'] = array_map(fn ($param) => new ResponseField($param), $parameters['responseFields'] ?? []); + + parent::__construct($parameters); + + $this->cleanBodyParameters = Extractor::cleanParams($this->bodyParameters); + $this->cleanQueryParameters = Extractor::cleanParams($this->queryParameters); + $this->cleanUrlParameters = Extractor::cleanParams($this->urlParameters); + $this->nestedBodyParameters = self::nestArrayAndObjectFields($this->bodyParameters, $this->cleanBodyParameters); + $this->nestedResponseFields = self::nestArrayAndObjectFields($this->responseFields); + + $this->boundUri = u::getUrlWithBoundParameters($this->uri, $this->cleanUrlParameters); + + [$files, $regularParameters] = static::splitIntoFileAndRegularParameters($this->cleanBodyParameters); + + if (count($files)) { + $this->headers['Content-Type'] = 'multipart/form-data'; + } + $this->fileParameters = $files; + $this->cleanBodyParameters = $regularParameters; + } + + /** + * @return array + */ + public static function getMethods(Route $route): array + { + $methods = $route->methods(); + + // Laravel adds an automatic "HEAD" endpoint for each GET request, so we'll strip that out, + // but not if there's only one method (means it was intentional) + if (count($methods) === 1) { + return $methods; + } + + return array_diff($methods, ['HEAD']); + } + + public static function fromExtractedEndpointArray(array $endpoint): self + { + return new self($endpoint); + } + + /** + * Transform body parameters such that object fields have a `fields` property containing a list of all subfields + * Subfields will be removed from the main parameter map + * For instance, if $parameters is [ + * 'dad' => new Parameter(...), + * 'dad.age' => new Parameter(...), + * 'dad.cars[]' => new Parameter(...), + * 'dad.cars[].model' => new Parameter(...), + * 'dad.cars[].price' => new Parameter(...), + * ], + * normalise this into [ + * 'dad' => [ + * ..., + * '__fields' => [ + * 'dad.age' => [...], + * 'dad.cars' => [ + * ..., + * '__fields' => [ + * 'model' => [...], + * 'price' => [...], + * ], + * ], + * ], + * ]]. + */ + public static function nestArrayAndObjectFields(array $parameters, array $cleanParameters = []): array + { + // First, we'll make sure all object fields have parent fields properly set + $normalisedParameters = []; + foreach ($parameters as $name => $parameter) { + if (Str::contains($name, '.')) { + // If the user didn't add a parent field, we'll helpfully add it for them + $ancestors = []; + + $parts = explode('.', $name); + $fieldName = array_pop($parts); + $parentName = mb_rtrim(implode('.', $parts), '[]'); + + // When the body is an array, param names will be "[].paramname", + // so $parentName is empty. Let's fix that. + if (empty($parentName)) { + $parentName = '[]'; + } + + while ($parentName) { + if (! empty($normalisedParameters[$parentName])) { + break; + } + + $details = [ + 'name' => $parentName, + 'type' => $parentName === '[]' ? 'object[]' : 'object', + 'description' => '', + 'required' => false, + ]; + + if ($parameter instanceof ResponseField) { + $ancestors[] = [$parentName, new ResponseField($details)]; + } else { + $lastParentExample = $details['example'] + = [$fieldName => $lastParentExample ?? $parameter->example]; + $ancestors[] = [$parentName, new Parameter($details)]; + } + + $fieldName = array_pop($parts); + $parentName = mb_rtrim(implode('.', $parts), '[]'); + } + + // We add ancestors in reverse so we can iterate over parents first in the next section + foreach (array_reverse($ancestors) as [$ancestorName, $ancestor]) { + $normalisedParameters[$ancestorName] = $ancestor; + } + } + + $normalisedParameters[$name] = $parameter; + unset($lastParentExample); + } + + $finalParameters = []; + foreach ($normalisedParameters as $name => $parameter) { + $parameter = $parameter->toArray(); + if (Str::contains($name, '.')) { // An object field + // Get the various pieces of the name + $parts = explode('.', $name); + $fieldName = array_pop($parts); + $baseName = implode('.__fields.', $parts); + + // For subfields, the type is indicated in the source object + // eg test.items[].more and test.items.more would both have parent field with name `items` and containing __fields => more + // The difference would be in the parent field's `type` property (object[] vs object) + // So we can get rid of all [] to get the parent name + $dotPathToParent = str_replace('[]', '', $baseName); + // When the body is an array, param names will be "[].paramname", + // so $parts is ['[]'] + if ($parts[0] === '[]') { + $dotPathToParent = '[]'.$dotPathToParent; + } + + $dotPath = $dotPathToParent.'.__fields.'.$fieldName; + Arr::set($finalParameters, $dotPath, $parameter); + } else { // A regular field, not a subfield of anything + // Note: we're assuming any subfields of this field are listed *after* it, + // and will set __fields correctly when we iterate over them + // Hence why we create a new "normalisedParameters" array above and push the parent to that first + $parameter['__fields'] = []; + $finalParameters[$name] = $parameter; + } + } + + // Finally, if the body is an array, remove any other items. + if (isset($finalParameters['[]'])) { + $finalParameters = ['[]' => $finalParameters['[]']]; + // At this point, the examples are likely [[], []], + // but have been correctly set in clean parameters, so let's update them + if ($finalParameters['[]']['example'][0] === [] && ! empty($cleanParameters)) { + $finalParameters['[]']['example'] = $cleanParameters; + } + } + + return $finalParameters; + } + + public function endpointId(): string + { + return $this->httpMethods[0].str_replace(['/', '?', '{', '}', ':', '\\', '+', '|', '.'], '-', $this->uri); + } + + public function name(): string + { + return $this->metadata->title ?: ($this->httpMethods[0].' '.$this->uri); + } + + public function fullSlug(): string + { + $groupSlug = Str::slug($this->metadata->groupName); + $endpointId = $this->endpointId(); + + return "{$groupSlug}-{$endpointId}"; + } + + public function hasResponses(): bool + { + return count($this->responses) > 0; + } + + public function hasFiles(): bool + { + return count($this->fileParameters) > 0; + } + + public function isArrayBody(): bool + { + return count($this->nestedBodyParameters) === 1 + && array_keys($this->nestedBodyParameters)[0] === '[]'; + } + + public function isGet(): bool + { + return in_array('GET', $this->httpMethods); + } + + public function isAuthed(): bool + { + return $this->metadata->authenticated; + } + + public function hasJsonBody(): bool + { + if ($this->hasFiles() || empty($this->nestedBodyParameters)) { + return false; + } + + $contentType = data_get($this->headers, 'Content-Type', data_get($this->headers, 'content-type', '')); + + return str_contains($contentType, 'json'); + } + + public function getSampleBody() + { + return WritingUtils::getSampleBody($this->nestedBodyParameters); + } + + public function hasHeadersOrQueryOrBodyParams(): bool + { + return ! empty($this->headers) + || ! empty($this->cleanQueryParameters) + || ! empty($this->cleanBodyParameters); + } + + public static function splitIntoFileAndRegularParameters(array $parameters): array + { + $files = []; + $regularParameters = []; + foreach ($parameters as $name => $example) { + if ($example instanceof UploadedFile) { + $files[$name] = $example; + } elseif (is_array($example) && ! empty($example)) { + [$subFiles, $subRegulars] = static::splitIntoFileAndRegularParameters($example); + foreach ($subFiles as $subName => $subExample) { + $files[$name][$subName] = $subExample; + } + foreach ($subRegulars as $subName => $subExample) { + $regularParameters[$name][$subName] = $subExample; + } + } else { + $regularParameters[$name] = $example; + } + } + + return [$files, $regularParameters]; + } +} diff --git a/vendor/knuckleswtf/scribe/camel/Output/Parameter.php b/vendor/knuckleswtf/scribe/camel/Output/Parameter.php new file mode 100644 index 00000000..d32bb767 --- /dev/null +++ b/vendor/knuckleswtf/scribe/camel/Output/Parameter.php @@ -0,0 +1,13 @@ +except('__fields'); + } +} diff --git a/vendor/knuckleswtf/scribe/composer.json b/vendor/knuckleswtf/scribe/composer.json new file mode 100644 index 00000000..984a6c01 --- /dev/null +++ b/vendor/knuckleswtf/scribe/composer.json @@ -0,0 +1,98 @@ +{ + "name": "knuckleswtf/scribe", + "description": "Generate API documentation for humans from your Laravel codebase.✍", + "license": "MIT", + "keywords": [ + "API", + "documentation", + "laravel" + ], + "authors": [ + { + "name": "Shalvah" + } + ], + "homepage": "https://github.com/knuckleswtf/scribe", + "funding": [ + { + "type": "patreon", + "url": "https://patreon.com/shalvah" + } + ], + "require": { + "php": ">=8.1", + "ext-fileinfo": "*", + "ext-json": "*", + "ext-pdo": "*", + "fakerphp/faker": "^1.23.1", + "laravel/framework": "^9.21 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "league/flysystem": "^3.0", + "mpociot/reflection-docblock": "^1.0.1", + "nikic/php-parser": "^5.0", + "nunomaduro/collision": "^6.0 || ^7.0 || ^8.0", + "parsedown/parsedown": "^1.7", + "ramsey/uuid": "^4.2.2", + "shalvah/upgrader": "^0.6.0", + "symfony/var-exporter": "^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "laravel/legacy-factories": "^1.3.0", + "laravel/pint": "^1.20", + "league/fractal": "^0.20", + "nikic/fast-route": "^1.3", + "orchestra/testbench": "^7.0 || ^8.0 || ^9.10 || ^10.0 || ^11.0", + "pestphp/pest": "^1.21 || ^2.0 || ^3.0 || ^4.0", + "phpstan/phpstan": "^2.1.5", + "phpunit/phpunit": "^9.0 || ^10.0 || ^11.0 || ^12.0", + "spatie/ray": "^1.41", + "symfony/css-selector": "^6.0 || ^7.0 || ^8.0", + "symfony/dom-crawler": "^6.0 || ^7.0 || ^8.0" + }, + "replace": { + "mpociot/laravel-apidoc-generator": "*" + }, + "autoload": { + "psr-4": { + "Knuckles\\Camel\\": "camel/", + "Knuckles\\Scribe\\": "src/" + }, + "files": [ + "src/Config/helpers.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Knuckles\\Scribe\\Tests\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + }, + "preferred-install": "dist", + "process-timeout": 600, + "sort-packages": true + }, + "extra": { + "laravel": { + "providers": [ + "Knuckles\\Scribe\\ScribeServiceProvider" + ] + } + }, + "scripts": { + "post-install-cmd": [ + "git config core.hooksPath .githooks" + ], + "post-update-cmd": [ + "git config core.hooksPath .githooks" + ], + "lint": "phpstan analyse -c ./phpstan.neon src camel --memory-limit 1G", + "style:check": "pint . --test --parallel --ansi", + "style:fix": "pint . --parallel --ansi", + "test": "pest --stop-on-failure --colors", + "test-ci": "pest" + } +} diff --git a/vendor/knuckleswtf/scribe/config/scribe.php b/vendor/knuckleswtf/scribe/config/scribe.php new file mode 100644 index 00000000..8fdbe8a8 --- /dev/null +++ b/vendor/knuckleswtf/scribe/config/scribe.php @@ -0,0 +1,259 @@ + for the generated documentation. + 'title' => config('app.name').' API Documentation', + + // A short description of your API. Will be included in the docs webpage, Postman collection and OpenAPI spec. + 'description' => '', + + // Text to place in the "Introduction" section, right after the `description`. Markdown and HTML are supported. + 'intro_text' => <<<'INTRO' + This documentation aims to provide all the information you need to work with our API. + + + INTRO, + + // The base URL displayed in the docs. + // If you're using `laravel` type, you can set this to a dynamic string, like '{{ config("app.tenant_url") }}' to get a dynamic base URL. + 'base_url' => config('app.url'), + + // Routes to include in the docs + 'routes' => [ + [ + 'match' => [ + // Match only routes whose paths match this pattern (use * as a wildcard to match any characters). Example: 'users/*'. + 'prefixes' => ['api/*'], + + // Match only routes whose domains match this pattern (use * as a wildcard to match any characters). Example: 'api.*'. + 'domains' => ['*'], + ], + + // Include these routes even if they did not match the rules above. + 'include' => [ + // 'users.index', 'POST /new', '/auth/*' + ], + + // Exclude these routes even if they matched the rules above. + 'exclude' => [ + // 'GET /health', 'admin.*' + ], + ], + ], + + // The type of documentation output to generate. + // - "static" will generate a static HTMl page in the /public/docs folder, + // - "laravel" will generate the documentation as a Blade view, so you can add routing and authentication. + // - "external_static" and "external_laravel" do the same as above, but pass the OpenAPI spec as a URL to an external UI template + 'type' => 'laravel', + + // See https://scribe.knuckles.wtf/laravel/reference/config#theme for supported options + 'theme' => 'default', + + 'static' => [ + // HTML documentation, assets and Postman collection will be generated to this folder. + // Source Markdown will still be in resources/docs. + 'output_path' => 'public/docs', + ], + + 'laravel' => [ + // Whether to automatically create a docs route for you to view your generated docs. You can still set up routing manually. + 'add_routes' => true, + + // URL path to use for the docs endpoint (if `add_routes` is true). + // By default, `/docs` opens the HTML page, `/docs.postman` opens the Postman collection, and `/docs.openapi` the OpenAPI spec. + 'docs_url' => '/docs', + + // Directory within `public` in which to store CSS and JS assets. + // By default, assets are stored in `public/vendor/scribe`. + // If set, assets will be stored in `public/{{assets_directory}}` + 'assets_directory' => null, + + // Middleware to attach to the docs endpoint (if `add_routes` is true). + 'middleware' => [], + ], + + 'external' => [ + 'html_attributes' => [], + ], + + 'try_it_out' => [ + // Add a Try It Out button to your endpoints so consumers can test endpoints right from their browser. + // Don't forget to enable CORS headers for your endpoints. + 'enabled' => true, + + // The base URL to use in the API tester. Leave as null to be the same as the displayed URL (`scribe.base_url`). + 'base_url' => null, + + // [Laravel Sanctum] Fetch a CSRF token before each request, and add it as an X-XSRF-TOKEN header. + 'use_csrf' => false, + + // The URL to fetch the CSRF token from (if `use_csrf` is true). + 'csrf_url' => '/sanctum/csrf-cookie', + ], + + // How is your API authenticated? This information will be used in the displayed docs, generated examples and response calls. + 'auth' => [ + // Set this to true if ANY endpoints in your API use authentication. + 'enabled' => false, + + // Set this to true if your API should be authenticated by default. If so, you must also set `enabled` (above) to true. + // You can then use @unauthenticated or @authenticated on individual endpoints to change their status from the default. + 'default' => false, + + // Where is the auth value meant to be sent in a request? + 'in' => AuthIn::BEARER->value, + + // The name of the auth parameter (e.g. token, key, apiKey) or header (e.g. Authorization, Api-Key). + 'name' => 'key', + + // The value of the parameter to be used by Scribe to authenticate response calls. + // This will NOT be included in the generated documentation. If empty, Scribe will use a random value. + 'use_value' => env('SCRIBE_AUTH_KEY'), + + // Placeholder your users will see for the auth parameter in the example requests. + // Set this to null if you want Scribe to use a random value as placeholder instead. + 'placeholder' => '{YOUR_AUTH_KEY}', + + // Any extra authentication-related info for your users. Markdown and HTML are supported. + 'extra_info' => 'You can retrieve your token by visiting your dashboard and clicking Generate API token.', + ], + + // Example requests for each endpoint will be shown in each of these languages. + // Supported options are: bash, javascript, php, python + // To add a language of your own, see https://scribe.knuckles.wtf/laravel/advanced/example-requests + // Note: does not work for `external` docs types + 'example_languages' => [ + 'bash', + 'javascript', + ], + + // Generate a Postman collection (v2.1.0) in addition to HTML docs. + // For 'static' docs, the collection will be generated to public/docs/collection.json. + // For 'laravel' docs, it will be generated to storage/app/scribe/collection.json. + // Setting `laravel.add_routes` to true (above) will also add a route for the collection. + 'postman' => [ + 'enabled' => true, + + 'overrides' => [ + // 'info.version' => '2.0.0', + ], + ], + + // Generate an OpenAPI spec in addition to docs webpage. + // For 'static' docs, the collection will be generated to public/docs/openapi.yaml. + // For 'laravel' docs, it will be generated to storage/app/scribe/openapi.yaml. + // Setting `laravel.add_routes` to true (above) will also add a route for the spec. + 'openapi' => [ + 'enabled' => true, + + // The OpenAPI spec version to generate. Supported versions: '3.0.3', '3.1.0'. + // OpenAPI 3.1 is more compatible with JSON Schema and is becoming the dominant version. + // See https://spec.openapis.org/oas/v3.1.0 for details on 3.1 changes. + 'version' => '3.0.3', + + 'overrides' => [ + // 'info.version' => '2.0.0', + ], + + // Additional generators to use when generating the OpenAPI spec. + // Should extend `Knuckles\Scribe\Writing\OpenApiSpecGenerators\OpenApiGenerator`. + 'generators' => [], + ], + + 'groups' => [ + // Endpoints which don't have a @group will be placed in this default group. + 'default' => 'Endpoints', + + // By default, Scribe will sort groups alphabetically, and endpoints in the order their routes are defined. + // You can override this by listing the groups, subgroups and endpoints here in the order you want them. + // See https://scribe.knuckles.wtf/blog/laravel-v4#easier-sorting and https://scribe.knuckles.wtf/laravel/reference/config#order for details + // Note: does not work for `external` docs types + 'order' => [], + ], + + // Custom logo path. This will be used as the value of the src attribute for the tag, + // so make sure it points to an accessible URL or path. Set to false to not use a logo. + // For example, if your logo is in public/img: + // - 'logo' => '../img/logo.png' // for `static` type (output folder is public/docs) + // - 'logo' => 'img/logo.png' // for `laravel` type + 'logo' => false, + + // Customize the "Last updated" value displayed in the docs by specifying tokens and formats. + // Examples: + // - {date:F j Y} => March 28, 2022 + // - {git:short} => Short hash of the last Git commit + // Available tokens are `{date:}` and `{git:}`. + // The format you pass to `date` will be passed to PHP's `date()` function. + // The format you pass to `git` can be either "short" or "long". + // Note: does not work for `external` docs types + 'last_updated' => 'Last updated: {date:F j, Y}', + + 'examples' => [ + // Set this to any number to generate the same example values for parameters on each run, + 'faker_seed' => 1234, + + // With API resources and transformers, Scribe tries to generate example models to use in your API responses. + // By default, Scribe will try the model's factory, and if that fails, try fetching the first from the database. + // You can reorder or remove strategies here. + 'models_source' => ['factoryCreate', 'factoryMake', 'databaseFirst'], + ], + + // The strategies Scribe will use to extract information about your routes at each stage. + // Use configureStrategy() to specify settings for a strategy in the list. + // Use removeStrategies() to remove an included strategy. + 'strategies' => [ + 'metadata' => [ + ...Defaults::METADATA_STRATEGIES, + ], + 'headers' => [ + ...Defaults::HEADERS_STRATEGIES, + Strategies\StaticData::withSettings(data: [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ]), + ], + 'urlParameters' => [ + ...Defaults::URL_PARAMETERS_STRATEGIES, + ], + 'queryParameters' => [ + ...Defaults::QUERY_PARAMETERS_STRATEGIES, + ], + 'bodyParameters' => [ + ...Defaults::BODY_PARAMETERS_STRATEGIES, + ], + 'responses' => configureStrategy( + Defaults::RESPONSES_STRATEGIES, + Strategies\Responses\ResponseCalls::withSettings( + only: ['GET *'], + // Recommended: disable debug mode in response calls to avoid error stack traces in responses + config: [ + 'app.debug' => false, + ] + ) + ), + 'responseFields' => [ + ...Defaults::RESPONSE_FIELDS_STRATEGIES, + ], + ], + + // For response calls, API resource responses and transformer responses, + // Scribe will try to start database transactions, so no changes are persisted to your database. + // Tell Scribe which connections should be transacted here. If you only use one db connection, you can leave this as is. + 'database_connections_to_transact' => [config('database.default')], + + 'fractal' => [ + // If you are using a custom serializer with league/fractal, you can specify it here. + 'serializer' => null, + ], +]; diff --git a/vendor/knuckleswtf/scribe/lang/scribe.php b/vendor/knuckleswtf/scribe/lang/scribe.php new file mode 100644 index 00000000..bdba874d --- /dev/null +++ b/vendor/knuckleswtf/scribe/lang/scribe.php @@ -0,0 +1,77 @@ + [ + 'search' => 'Search', + 'base_url' => 'Base URL', + ], + + 'auth' => [ + 'none' => 'This API is not authenticated.', + 'instruction' => [ + 'query' => <<<'TEXT' + To authenticate requests, include a query parameter **`:parameterName`** in the request. + TEXT, + 'body' => <<<'TEXT' + To authenticate requests, include a parameter **`:parameterName`** in the body of the request. + TEXT, + 'query_or_body' => <<<'TEXT' + To authenticate requests, include a parameter **`:parameterName`** either in the query string or in the request body. + TEXT, + 'bearer' => <<<'TEXT' + To authenticate requests, include an **`Authorization`** header with the value **`"Bearer :placeholder"`**. + TEXT, + 'basic' => <<<'TEXT' + To authenticate requests, include an **`Authorization`** header in the form **`"Basic {credentials}"`**. + The value of `{credentials}` should be your username/id and your password, joined with a colon (:), + and then base64-encoded. + TEXT, + 'header' => <<<'TEXT' + To authenticate requests, include a **`:parameterName`** header with the value **`":placeholder"`**. + TEXT, + ], + 'details' => <<<'TEXT' + All authenticated endpoints are marked with a `requires authentication` badge in the documentation below. + TEXT, + ], + + 'headings' => [ + 'introduction' => 'Introduction', + 'auth' => 'Authenticating requests', + ], + + 'endpoint' => [ + 'request' => 'Request', + 'headers' => 'Headers', + 'url_parameters' => 'URL Parameters', + 'body_parameters' => 'Body Parameters', + 'query_parameters' => 'Query Parameters', + 'response' => 'Response', + 'response_fields' => 'Response Fields', + 'example_request' => 'Example request', + 'example_response' => 'Example response', + 'responses' => [ + 'binary' => 'Binary data', + 'empty' => 'Empty response', + ], + ], + + 'try_it_out' => [ + 'open' => 'Try it out ⚡', + 'cancel' => 'Cancel 🛑', + 'send' => 'Send Request 💥', + 'loading' => '⏱ Sending...', + 'received_response' => 'Received response', + 'request_failed' => 'Request failed with error', + 'error_help' => <<<'TEXT' + Tip: Check that you're properly connected to the network. + If you're a maintainer of ths API, verify that your API is running and you've enabled CORS. + You can check the Dev Tools console for debugging information. + TEXT, + ], + + 'links' => [ + 'postman' => 'View Postman collection', + 'openapi' => 'View OpenAPI spec', + ], +]; diff --git a/vendor/knuckleswtf/scribe/logo-scribe.png b/vendor/knuckleswtf/scribe/logo-scribe.png new file mode 100644 index 0000000000000000000000000000000000000000..e17ab83096a638ea3701f1ecd2cf057fb8840967 GIT binary patch literal 10029 zcmYLv1yoc~7w!dVi5a?M=nxPF=^hZIRJxHIq?8b(LBXM=rFGDuyQI6z5h-a95CNrI zc-Q~`-+OnhnKkR)GyCpy&fe#oZ+~CZL*4siB#a~g0FY^FARYq%PA2#}l!yTQ7L%(G z004GC6QOM2XSEwd{Epe7?nHW0K7faVm2hZZz4Jb;?Xf`|@FokM5mMSmx{4WG(TOq5YvF^li-GjvR4w~)OLjv^QdNICGHt?mUu$hKRR%b z-l6R(vS5n-5w7%!^F3GQmR5m?dY8v7rJzrOdI&;s#gR$9);@}a_(9DK`-hV z)kUJS+1ZKkNW$bf$#`DWq z{*(_lIH5esg**`Y9tdmg_0Vyai;DT9$KZR0qC4EtrZQ_dFqw+dq=6sYY5^5BJOGsjKk$r$rBcQ?1hk)seYL&9`tvnOKg-7tU|)!E z@!3Ns(zQcaahgef3R(&<4LTT08+# z9F9y9@psd!Bv`+=!dPCgl_TD3GT)94efQQI{ z5LX30`J_L*4ua;Vm)YV2|3wakpq~>N)ZL>(7a0vlTZJnaf@MNlw&v&`1uaxa1& z50_&gIhjY?Hs)1iwXs^{Do2$_hFb_|qGIQ5D$Zh4~Xh)8D9k_zhkP?K`lwVnu*}fS`>4X!$Yu&J9Ze5F@Te z_xARx4FrxUXkJvA>ixBw9Gp1DcEzWONZYCz1Iy_OwVvCqR%Y(MNw16B?2yQx!W_(! zmC_z_i;IiQBoVAYsQ8XQ6f3^veQ{V~ROY`lqpJ+x##ye3LZ}Fg$VjSo-ibhxM3&3Q zOwnVJSmY5NaE1fyxY#N4#2=#ysjxUNzcaouy^q-BN!C2^<`Qgrlb{h0aJl&5;m-yD zGAzEc1lPG78A=tj9#@5XK(S6qgFhH4dQ(0KfpJi@Z=P?2!qn8%IQ#j%Yo%Av`e5e2 z(wg2Zp%XCHa%wx%-AD@fo%j8IK!shubLY;f77*r3k$xNf$X|dYfdEw~n4#UaoRcD! zL<0^e>pYX})J`a-p65#@oI+i27gDDL$0EJOpbM{1EwdhN?`+DW``3tpFNg0dM)MSJ zCNeP#WPrHacMNo2aNqBWcXig!OK}b|`U_DO6K)D5DTnx85D{Ui zUxv79xxMmb3!(c}Fxg#uSYXIJYaolQZD{yIM-p)s5mGz&%^5f|vbJ8b+@30%+uFE? z>a|_-vAiLNS=rb0Pv{{E3h!YSM!4O4IV){C!>7Du{vzWadVXeKC4oJ~36(ZQH%et_ zFFN&=<>#+;HAPeEJ`WPSEl~aNWvjT- z>o&TPpEbc+5crSnF#0S_%;Q0fSyUv2RTy4+bGfe z08%ii2vJ>RgkIr_2KLx6pYB+v)i{o2&x$@@?a_iHw%2iZpFdK|@ND+#40rnQ2Nwt(VHwSzn5xtF0+yS_ox?dwLaB~vu1>#(b z6OpjQfos%&b(@#YMk}~5%b}WR)Ki)8LookEy`pZE($#wtoEzHxR&| ztB=#kqCNt`o^ocI2)&@cPsgzef92c&#oh&{iRR6QaFc1;9=UoCKjNt0Pp+-*r?Nw6 zIJA-|f!3qO2C?8njeZ`Qp2O>Pwcp?EGi^!e(Xvlq;kOb&bN`;9I@~Bxlj8LF*z(7{ z*xAl94jlnJoZIM?9h`n1$|y1#w0dteKO(u`TlfEl5Oj3D(d)FRq-3o%LpR@)`=G>j zNiUb!T^U|oy2a3Sa*G%{v%D5*FOce59QALxvTZ6?yakjI)I88Fp={L-my3hwye#8H z*oB2~aOp?c>+2x$3#Gew5`E4N+U#)eUS>;sofMapT;jumhu9$nBr*c53GK{893>`1 z9E=gqcQrP$i*%lK%2TNLT@bo?#0Zvu#!VBEOlv=LIxKS1iAbBxmdL*{PKA!fMI*&I zG7pv<9nw3BN0hjEr?TY(UIcl09SR|S+$v=w2%U!R$6+v-I%Tb&0th>Qyd8~=l1qv1 zvI+AKY$}yL`>k(to7xX>npPs2mL=QI;|>~vbY}_8=Hdip*MkU3qL66pPfEZ9sbYY_ zv^1EoJ67) zE{L!8;!Y&JEsUT-oxXQc*)w)`aoM)8oTdXh?nC!g8#|+ZvRRCMZg0OauA>bz=0>d5 z<~|<#vF&ZyURzMG$xoOuDBnzI2#&h_qvxt(((Fqf$goo~dwpjveK&+%)qQ;`j#3MW z6co7SWIr-HPIU>q&Le%(P1kZTB$yQx)QpFGCWuz=jC9@G0U+=!5-=WvkUQbO{p- z#?i@@J12!T^{R0{9D}|v*3ufLQ8phw-HtFGL?qv7@fC+!KJ9u#)qM>Cq~`DgDzb66 zo=paYqHdS~Vd3e{l}z4D6*-2(?q|o5ZhNm92nB5Q`h@HypgJdESNzZ%*Kaju*2S|; zQogX&^o6$fQAo~ox%mFIl7*vnx_bcgm+#*%PS=OiJ;tXcU6Q2{)Dj}=&#kS-ZiRB) zM5uglSDff9JlPBXjeX`Yqs`-W3$SqR$Q;%Ult|RLGOOZ>^x7O-Xl`q}ma?2~&(%`M z+`ZHQ!=+buCvCjkJ*2`Jy&rC$)>6j;e)3_OnD1$0CV5Es(5JLKQ75;J^yhM{n}o^p z3S7#rA!jRZ^cYxK+mZWVNd>W*z%bl#idV zy?1Tj!BM-0n~|1%b$#~hbn(|&BQJ&fh?t~7`vfX;pBFF2M(o8bf zbxlVcEcGp=V-@c_8igbsKP78RP{!%7WG2#y!miWHw}&#h&spbHLZ;z&^0zOxt!R{L ziWo0s5m)|ltkzEcns!38vRSCl8gsen3NT?NW@fo*XZxg==TWWbhkV{w;O2evu9tu_ zaUc+J<*QHN((pxk(O8ssxc2a&Kpc{qO~+-{+*d_Rx`W0_Ve{{$MUh~;5gE49x7wkX zcBZcCb2VEXLTpk4UYb0bSwBaYo4(7^F1O25SH$Qaz(?IQ_OyH2HUZ7cOwP6q=Tnj( zfG%We^U#fFOV*lqsfdbHw5WHRdtt*zok4}q{U z8e#e2nYuk3j?4uE;#x(Dd85l?<0+=sHdoU2VWL@I^-~N}0;aV!NQIADh7f2Y2Y%i1 zYOV_)hW?p=?`l?{o2s>}d~rIANdZ~|E$8d2^Eb%MzI7-$Jl62Qb%RC+t}VvIHbZpr z-fD-8OjmOQBiYc+5>hcQ&mjJAlA82=6LIyVNWum;Hz3vJfJ9;KlrCQ#sqj#Kd7SmD-&a*cf*MDRegL(QfZv z(z*y-CqR^%1kOBiv)(Sty>nOU@s0XffjCkG_UI7y<12Ci zD&wMj&|Zue^SJ0(cQ@s4*EKPaZU6I7g(q2BKJ`KCu6H&P?oS5HXS@92!H+RSRgTWL zuf3hO7n|Kiv}tfvi@?NI_QWUxpt0jlR8c)lXx)kUJWV6;q;t-bS~*N3)iRDjNTgsZL|G7wcbeiJyNS6kQ>5I$X~w>>+y^g%o-} zDI+U;`n}F2ot7km9ENV)(e7Zn?C$Qq!euV}A4vJGw)c!k44BtKI5GD7RNvURdIW0< z+*@T?kOnaY5zK!K67g+)BQ{YjlkISRr+0pSdb;7`TT(pcuO-_TTfmpQa&mz$1yUd9 zu>b)6`Cp;r!R(4(cK5-f5LY-#vHt90a~aB0n2Jal3|10lc>X$nC`Ry1$@L!^1FpM8 z-L0w}_G%(dHmIN>Gl4h&x$L~aYkl|piu$kI{B%3n7-&e?D)wFzBij?9(~k)|+PyY&7*VK7uy?WMUcKaD@> zVH+o2ODpvJJMzfPU5=5pn;0v#ATGdYf{%s#7nl_5_zR*}5(rKZ)C@E;f=$o=;TS`E ztNZu+m7B`F+*`e9ULShbdw;g`m{?_=DFzoU3t!*U8}Vg{gXbxO_TSI4Nuey`7HjOB zuc4l%GBr&&;E`j;t|0zx?8GqHAe^~MHow2PZf=E&uHDGco)fWl6whDGVtn0#2S|x> z_)3qNy!TUX(?Ar^QVIvnJb63Ui`m-M;YID}JzfM#CECKxs&W z1v+A8RbD!d|L{N@eXmyCHe+Ref|7#6~5P9O!`v6bT@c2NR|9( z`P0`FjI&E~bMxf_!3SzirE(87h|WU|4CA0WsxPLW?}mQ%kTj6zwSIGgg)X_aIxLYB znphIK(e{}|q|sE<;i~%d9g_9;_I7jO+n|gqBC`h3JDaAFu1-}3bWsxa%u!x+!`9zy=^MK&@2SA8qrG|id)TL8Jd7fmj>dtvNrmT9=pHPdjqeWraIqj8 zMykNiOLrFjY{dDEX*n7{c-L((v2~OTh$;%I=~-GXL3Jp)yB}ZYFTSbyhHsl*y2)XV&upoW@mtALKMoSZPvfWwk z&5ygWZRB(&=>2T_;3|F(88t~R{X4mFkhqHR>B7$DrY1&00s^U}nOrZ%7ySCv!;VXC z?(SA5t|~q8@$nSfP1gYmcXAhE&h!fSOj=95ijYH9*B(5K|G|fwAJO^cfrzz}mS`5o z=tI%^;pi$8ZNtcUssP!#vbIjiyO^Cd5OloBnhF)-zp4j5FAGrQr2AdmHF}T2Jl5Uru)aSmV8Xaw=~}f;}Vo^RAP|dUA;Bd-UJzKa!G?A^9^7HMVtzt)ojDlN@BxOnqpUuUL>CWPN@)Mu5PsKCpY1)8c>9Zv1RP%_=nu{xwooorLZ z;x;m~Gsfu>0#>lg$`3A@hmIL)Mv%D2tW4~N!<%VQl|sL zn!r``wECz3LcbP@D;c`_2^}~nEx&8^6#q`KYm7e#w^sZu!;F(PuZJEsaN`cQ3NNf7tukZrx|z-uj-*Ab#+ZTcn@r@(V;( z$`Gq4!okR2_a~zQ6_olGjv15;cCjHZ#laXOpIPl6Wwm7Nz?peR z#3Sr9_;Qgii2Qp6j#ZF7>$80^UgLc(@uZzW|NNyW2x~nQy$=})CI4hnW1M^bF#83? zNDMEMt8kAp^?b|~rJkYL5o10uh+`QORPoR5Pj}{gS47QLqE6+Sh^+e7VX_NFqzH@b zWMX{oThehZ#F_)*HRs&qQ_fULn^au&#H5~vb(V&cLIC}U=XM>8k&W}>v(cy<$}eIe ziN2r&>+^RSQOiC(c0Xf2b1wM=^g8a;zukF*e+)`ev2|TeWZs zJu$PIbZq*iRz_mKC;p|&w(V|_fB0wSt-3f|HF)iT-OFRI2V#GB&7UlHU=bL`z`VZ{ zq5Oob?|c^@!@=S-{*XhKop$P|oWq+0c9^h9suu4Dfh}`dhY>E5-xVG)g=Ke#pU@Ur z1$Iwvy-}-Y?(xN0c3H*x1!|SZWv-vHklY`e8}{V6&-DuL?_qW#sa~yzIg@to2~I+t zpsN%|X4kWl*H(!d_92KLDK}nZ2WCby2H72_LPd^Mei?^#saZ&Bj8FF^p69lFo_>PIeb zun-=N*AI)vs6OX!X%Y9=h8Y+QiQR!p?aK;2@tlScUygb~BiJ^S+w*%CL@JSFk`rb?)_XKAa5N zUnAAwqRfaxwc`N_sEp>|)Is|(-A|9r4bB8{3BLk+jh>Td`xlx*f>_cM@~*klcDJGl zIt;w))G3y5RNw>meO@{Zv7OP{(G0>5l{MnBR<|k`^-z80DLNEas-vT$=^`Mx(A-dC zk}$Qn*k;W~z1n4bpV6FS)kjB1r$I{NT#+X<^w2M&du5!?d~B8v#2#72+ZPU~Gamp6 zzrxJx6ER{7yIz%%44nrl40df~U;ju!!1hxXS_`DSYSK`;!p1plB6-Qxlw<{YD{y%6 zF!A6|r7zPf?s`0u#&1s?J;jKzelJ}T{HXE}J-lFrzwp3E9J-LK&9k)s0#lX0@2CJ% zczR&}dF6J7_QsKcfWzMi2YTVG-HgFsh4YG3SYIZ3$2=Ndx#qlHbsRQj~4tcKPS!B!AdOWg%1Bsy_%;azK%&Khh z+xHH6(O$xtv~lWsKq%X@hxtY3%GQO^@~pa&H>rMd*U2jOo1ZYxqynK(OSc1?*wz1G zL;f+Qd%qqF8J<^qhLIwA=}u$hn_>SLF^BPe zr}MPRq2Kjr3I>?CN+sL6jxUa`Wn$#D5FxSsYfLyD*C`eL_kw3fxX>V$qaiYQ|kjjv9tg3-E^8(%VI0Q^PaFb(I60kv&eXp+;K=q{$+$r!?g(7NrdCj;e zrD>pfV7TTV+JZR&vH|Rt)6&ByjT@ISC|1=Tt@+rDI0ZWq3&Z&IQ#Y$I$E;vhU=a-F zTe{DO>el|g|M201Hb>CHSgMP)=f5(SKgJ{MMq->{uy#iRhcV0iy~BY4G6xbX>L_SO zjHUKx+T1Pp|0NLd!`cn~wifMu81NA+AN=a;>l@u5c{h9a+3)$rfa&RJHcdo2Zs=sx z8aiFkwWWq_YI1S`dHiXZX}PSd>fToYNCMD%R6H^5evQD+(0@nkoEPV28+L0>`Qb%8a+zKzRMncxcv2p^oodI^&5vT;7v*OROEg)DXa(7$fc^yB(CPCk zcZvv=>Bi&Q`Optf&0nAg?~5HL?sp*cZ>K|aw#u^QJ4aXgTPi$jZEr1=J-kv22nY$e znF!9E8}xV5PRVANac}Q^a7_(dy+s9^NAt-wus6B=NkLX?y=8u(U_9+}CsCtvQwd}i zWq{9%oh{lUA!8Mjl-L#%XXmISA@#r}Rqw#4uTU=F5jTgFn zyTu->iwN9{zj3Lv?-~{iS>+ZCehczezTlO|7H%3hxM<3gyz<3qG4^*gI}5qrvT8cW zpdEq~sop<|v^~Z1LOwkl8LhJt?zJ(J@#WjMk3PP>;zm~fpW;`RZPKEscv<66#@``h z1c>>y)3A^gM*2ZHQI5AoMtW%Cj(#OZAE9V8a+>svxCy22r6ag8g z|3nF@??DQK=4;>p$D59>mo>|0KKc1Oo?JD7!@_5iZfs#xiVCxec)0Lth)!rM2l}5{ z!igy5HBxGnG&ycRQ^JKd+xx4gsQWZ~?9v5=-|xoF_w6CRv!SETEK}r;>euZqi+Fz5 zZQXMEQUf5Pfj27y%L0`O)X_?sDD~7&=d}~Mul}AcvAy^`Pn;lsLhriRGW(ctY~yOp zC5V_`gj&-2f*5;-Tk;>JhJ!lj?f*Ng77YzMUiIh3vJC!^VX3vgWf8I%)^SrdRk95p z-;1XHhpS4OT4VgN)5P}AKtW02OS}gvaC56g+&_P(3`ss@N|N4UM{qWTfi4-`!sX^* zBzF$>+RA!Qfe-uGY`epQ(7%hJR0g#Z9Ry)X5VCQPbZvd1kLpS&_1~ob>=EESP=}M! zIuR@r!S`LW7i3i6W6zR|K>Z8E74N|VI4QnJ{&Sm-j!c{Z(p~Z=X_6vLr4yFJn7R(*6PmD8wKaalQizCw~7EP9&Hb9)m2ofLF9wxDq>fhxnuE z1Kt!c?<&(W@WRa0lu#I>J9(4l|5OsdBWnmB4#hb$mzZSaQW_LK?= zWApQ4KG;<$gc7Qcl`sIy;0Jyc2F0Q>_6*=+n+PJXU^>`N#e9dufR(#bud;T9Tn~(2l7M#n{JH zEM7k?EQiIZBkevuJ|0TfZ&oNG-dJiZp3KLKRoaSZLyj~v-3ry;Lt5ybx^{hgU zFL?2|{v%GhLI6&S_q-y@x3{md5q;!5xS5zy3*X76&r676OFk7wbI2I^l(Fg7To?$Cdv?RLbvDh+;W16CG;*tCb=E zbUsn5Zyba=l~sNCo*Aw&&B92yLQ5$uk>ZhD?g(p<0hfw)d z?{-151nQf4o#J`*Z@U9ihB#T;VJ@Yj{862Rgri&<7u?hb_XcM$W literal 0 HcmV?d00001 diff --git a/vendor/knuckleswtf/scribe/resources/css/theme-default.print.css b/vendor/knuckleswtf/scribe/resources/css/theme-default.print.css new file mode 100644 index 00000000..18ab760e --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/css/theme-default.print.css @@ -0,0 +1,393 @@ +/* Copied from https://github.com/slatedocs/slate/blob/c4b4c0b8f83e891ca9fab6bbe9a1a88d5fe41292/stylesheets/print.css and unminified */ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block +} + +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline +} + +audio:not([controls]) { + display: none; + height: 0 +} + +[hidden], +template { + display: none +} + +a { + background-color: transparent +} + +a:active, +a:hover { + outline: 0 +} + +abbr[title] { + border-bottom: 1px dotted +} + +b, +strong { + font-weight: bold +} + +dfn { + font-style: italic +} + +h1 { + font-size: 2em; + margin: 0.67em 0 +} + +mark { + background: #ff0; + color: #000 +} + +small { + font-size: 80% +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sup { + top: -0.5em +} + +sub { + bottom: -0.25em +} + +img { + border: 0 +} + +svg:not(:root) { + overflow: hidden +} + +figure { + margin: 1em 40px +} + +hr { + box-sizing: content-box; + height: 0 +} + +pre { + overflow: auto +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em +} + +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0 +} + +button { + overflow: visible +} + +button, +select { + text-transform: none +} + +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer +} + +button[disabled], +html input[disabled] { + cursor: default +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0 +} + +input { + line-height: normal +} + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0 +} + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto +} + +input[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none +} + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em +} + +legend { + border: 0; + padding: 0 +} + +textarea { + overflow: auto +} + +optgroup { + font-weight: bold +} + +table { + border-collapse: collapse; + border-spacing: 0 +} + +td, +th { + padding: 0 +} + +.content h1, +.content h2, +.content h3, +.content h4, +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 14px +} + +.content h1, +.content h2, +.content h3, +.content h4 { + font-weight: bold +} + +.content pre, +.content code { + font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif; + font-size: 12px; + line-height: 1.5 +} + +.content pre, +.content code { + word-break: break-all; + -webkit-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto +} + +@font-face { + font-family: 'slate'; + src: url(../fonts/slate.eot?-syv14m); + src: url(../fonts/slate.eot?#iefix-syv14m) format("embedded-opentype"), url(../fonts/slate.woff2?-syv14m) format("woff2"), url(../fonts/slate.woff?-syv14m) format("woff"), url(../fonts/slate.ttf?-syv14m) format("truetype"), url(../fonts/slate.svg?-syv14m#slate) format("svg"); + font-weight: normal; + font-style: normal +} + +.content aside.warning:before, +.content aside.notice:before, +.content aside.success:before { + font-family: 'slate'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1 +} + +.content aside.warning:before { + content: "\e600" +} + +.content aside.notice:before { + content: "\e602" +} + +.content aside.success:before { + content: "\e606" +} + +.tocify, +.toc-footer, +.lang-selector, +.search, +#nav-button { + display: none +} + +.tocify-wrapper>img { + margin: 0 auto; + display: block +} + +.content { + font-size: 12px +} + +.content pre, +.content code { + border: 1px solid #999; + border-radius: 5px; + font-size: 0.8em +} + +.content pre code { + border: 0 +} + +.content pre { + padding: 1.3em +} + +.content code { + padding: 0.2em +} + +.content table { + border: 1px solid #999 +} + +.content table tr { + border-bottom: 1px solid #999 +} + +.content table td, +.content table th { + padding: 0.7em +} + +.content p { + line-height: 1.5 +} + +.content a { + text-decoration: none; + color: #000 +} + +.content h1 { + font-size: 2.5em; + padding-top: 0.5em; + padding-bottom: 0.5em; + margin-top: 1em; + margin-bottom: 21px; + border: 2px solid #ccc; + border-width: 2px 0; + text-align: center +} + +.content h2 { + font-size: 1.8em; + margin-top: 2em; + border-top: 2px solid #ccc; + padding-top: 0.8em +} + +.content h1+h2, +.content h1+div+h2 { + border-top: none; + padding-top: 0; + margin-top: 0 +} + +.content h3, +.content h4 { + font-size: 0.8em; + margin-top: 1.5em; + margin-bottom: 0.8em; + text-transform: uppercase +} + +.content h5, +.content h6 { + text-transform: uppercase +} + +.content aside { + padding: 1em; + border: 1px solid #ccc; + border-radius: 5px; + margin-top: 1.5em; + margin-bottom: 1.5em; + line-height: 1.6 +} + +.content aside:before { + vertical-align: middle; + padding-right: 0.5em; + font-size: 14px +} diff --git a/vendor/knuckleswtf/scribe/resources/css/theme-default.style.css b/vendor/knuckleswtf/scribe/resources/css/theme-default.style.css new file mode 100644 index 00000000..08f2e93a --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/css/theme-default.style.css @@ -0,0 +1,1094 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +html { + font-family: 'Open Sans', sans-serif; + font-size: 1.2em; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section { + display: block +} + +summary { + cursor: pointer; +} + +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline +} + +audio:not([controls]) { + display: none; + height: 0 +} + +[hidden], +template { + display: none +} + +a { + background-color: transparent +} + +a:active, +a:hover { + outline: 0 +} + +abbr[title] { + border-bottom: 1px dotted +} + +b, +strong { + font-weight: 700 +} + +dfn { + font-style: italic +} + +h1 { + font-size: 2em; + margin: .67em 0 +} + +mark { + background: #ff0; + color: #000 +} + +small { + font-size: 80% +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sup { + top: -.5em +} + +sub { + bottom: -.25em +} + +img { + border: 0 +} + +svg:not(:root) { + overflow: hidden +} + +figure { + margin: 1em 40px +} + +hr { + box-sizing: content-box; + height: 0 +} + +pre { + overflow: auto +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em +} + +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0 +} + +button { + overflow: visible +} + +button, +select { + text-transform: none +} + +button, +html input[type=button], +input[type=reset], +input[type=submit] { + -webkit-appearance: button; + cursor: pointer +} + +button[disabled], +html input[disabled] { + cursor: default +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0 +} + +input { + line-height: normal +} + +input[type=checkbox], +input[type=radio] { + box-sizing: border-box; + padding: 0 +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + height: auto +} + +input[type=search] { + -webkit-appearance: textfield; + box-sizing: content-box +} + +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +fieldset { + border: 1px solid silver; + margin: 0 2px; + padding: .35em .625em .75em +} + +legend { + border: 0; + padding: 0 +} + +textarea { + overflow: auto +} + +optgroup { + font-weight: 700 +} + +table { + border-collapse: collapse; + border-spacing: 0 +} + +td, +th { + padding: 0 +} + +body, +html { + font-family: 'Open Sans', Helvetica Neue, Helvetica, Arial, Microsoft Yahei, 微软雅黑, STXihei, 华文细黑, sans-serif; + font-size: 16px; +} + +.content h1, +.content h2, +.content h3, +.content h4, +.content h5, +.content h6 { + font-family: 'Open Sans', Helvetica Neue, Helvetica, Arial, Microsoft Yahei, 微软雅黑, STXihei, 华文细黑, sans-serif; +} + +.content h1, +.content h2, +.content h3, +.content h4, +.content h5, +.content h6 { + font-weight: 700 +} + +.content code, +.content pre { + font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif; + font-size: 14px; + line-height: 1.5 +} + +.content code { + word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -ms-hyphens: auto; + hyphens: auto +} + +.content aside.notice:before, +.content aside.success:before, +.content aside.warning:before, +.tocify-wrapper>.search:before { + font-family: 'Open Sans', sans-serif; + speak: none; + font-style: normal; + font-variant: normal; + text-transform: none; + line-height: 1 +} + +.content aside.warning:before { + content: "✋" +} + +.content aside.notice:before { + content: "ℹ" +} + +.content aside.success:before { + content: "✅" +} + +.tocify-wrapper>.search:before { + content: "🔎" +} + +.highlight .c, +.highlight .c1, +.highlight .cm, +.highlight .cs { + color: #909090 +} + +.highlight, +.highlight .w { + background-color: #292929 +} + +.hljs { + display: block; + overflow-x: auto; + padding: .5em; + background: #23241f +} + +.hljs, +.hljs-subst, +.hljs-tag { + color: #f8f8f2 +} + +.hljs-emphasis, +.hljs-strong { + color: #a8a8a2 +} + +.hljs-bullet, +.hljs-link, +.hljs-literal, +.hljs-number, +.hljs-quote, +.hljs-regexp { + color: #ae81ff +} + +.hljs-code, +.hljs-section, +.hljs-selector-class, +.hljs-title { + color: #a6e22e +} + +.hljs-strong { + font-weight: 700 +} + +.hljs-emphasis { + font-style: italic +} + +.hljs-attr, +.hljs-keyword, +.hljs-name, +.hljs-selector-tag { + color: #f92672 +} + +.hljs-attribute, +.hljs-symbol { + color: #66d9ef +} + +.hljs-class .hljs-title, +.hljs-params { + color: #f8f8f2 +} + +.hljs-addition, +.hljs-built_in, +.hljs-builtin-name, +.hljs-selector-attr, +.hljs-selector-id, +.hljs-selector-pseudo, +.hljs-string, +.hljs-template-variable, +.hljs-type, +.hljs-variable { + color: #e6db74 +} + +.hljs-comment, +.hljs-deletion, +.hljs-meta { + color: #75715e +} + +body, +html { + color: #333; + padding: 0; + margin: 0; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: whitesmoke; + height: 100%; + -webkit-text-size-adjust: none +} + +#toc>ul>li>a>span { + float: right; + background-color: #2484ff; + border-radius: 40px; + width: 20px +} + +.tocify-wrapper { + transition: left .3s ease-in-out; + overflow-y: auto; + overflow-x: hidden; + position: fixed; + z-index: 30; + top: 0; + left: 0; + bottom: 0; + width: 230px; + background-color: #393939; + font-size: 13px; + font-weight: 700 +} + +.tocify-wrapper .lang-selector { + display: none +} + +.tocify-wrapper .lang-selector a { + padding-top: .5em; + padding-bottom: .5em +} + +.tocify-wrapper>img { + display: block +} + +.tocify-wrapper>.search { + position: relative +} + +.tocify-wrapper>.search input { + background: #393939; + border-width: 0 0 1px; + border-color: #666; + padding: 6px 0 6px 20px; + box-sizing: border-box; + margin: 10px 15px; + width: 200px; + outline: none; + color: #fff; + border-radius: 0 +} + +.tocify-wrapper>.search:before { + position: absolute; + top: 17px; + left: 15px; + color: #fff +} + +.tocify-wrapper img+.tocify { + margin-top: 20px +} + +.tocify-wrapper .search-results { + margin-top: 0; + box-sizing: border-box; + height: 0; + overflow-y: auto; + overflow-x: hidden; + transition-property: height, margin; + transition-duration: .18s; + transition-timing-function: ease-in-out; + background: linear-gradient(180deg, rgba(0, 0, 0, .2), transparent 8px), linear-gradient(0deg, rgba(0, 0, 0, .2), transparent 8px), linear-gradient(180deg, #000, transparent 1.5px), linear-gradient(0deg, #939393, hsla(0, 0%, 58%, 0) 1.5px), #262626 +} + +.tocify-wrapper .search-results.visible { + height: 30%; + margin-bottom: 1em +} + +.tocify-wrapper .search-results li { + margin: 1em 15px; + line-height: 1 +} + +.tocify-wrapper a { + color: #fff; + text-decoration: none +} + +.tocify-wrapper .search-results a:hover { + text-decoration: underline +} + +.tocify-wrapper .toc-footer li, +.tocify-wrapper .tocify-item>a { + padding: 0 15px; + display: block; + overflow-x: hidden; + white-space: nowrap; + text-overflow: ellipsis +} +.tocify-wrapper .tocify-item.level-3>a { + padding: 0 25px; +} + +.tocify-wrapper li, +.tocify-wrapper ul { + list-style: none; + margin: 0; + padding: 0; + line-height: 28px +} + +.tocify-wrapper li { + color: #fff; + transition-property: background; + transition-timing-function: linear; + transition-duration: .23s +} + +.tocify-wrapper .tocify-focus { + box-shadow: 0 1px 0 #000; + background-color: #2467af; + color: #fff; + font-weight: bold; +} + +.tocify-wrapper .tocify-subheader { + display: none; + background-color: #262626; + font-weight: 500; + background: linear-gradient(180deg, rgba(0, 0, 0, .2), transparent 8px), linear-gradient(0deg, rgba(0, 0, 0, .2), transparent 8px), linear-gradient(180deg, #000, transparent 1.5px), linear-gradient(0deg, #939393, hsla(0, 0%, 58%, 0) 1.5px), #262626 +} + +.tocify-wrapper .jets-searching .tocify-subheader, +.tocify-wrapper .tocify-subheader.visible { + display: block; +} + +.tocify-wrapper .tocify-subheader .tocify-item>a { + padding-left: 25px; + font-size: 12px +} + +.tocify-wrapper .tocify-subheader .tocify-item.level-3>a { + padding-left: 35px; +} + +.tocify-wrapper .tocify-subheader>li:last-child { + box-shadow: none +} + +.tocify-wrapper .toc-footer { + padding: 1em 0; + margin-top: 1em; + border-top: 1px dashed #666 +} + +.tocify-wrapper .toc-footer a, +.tocify-wrapper .toc-footer li { + color: #fff; + text-decoration: none +} + +.tocify-wrapper .toc-footer a:hover { + text-decoration: underline +} + +.tocify-wrapper .toc-footer li { + font-size: .8em; + line-height: 1.7; + text-decoration: none +} + +#nav-button { + padding: 0 1.5em 5em 0; + display: none; + position: fixed; + top: 0; + left: 0; + z-index: 100; + color: #000; + text-decoration: none; + font-weight: 700; + opacity: .7; + line-height: 16px; + transition: left .3s ease-in-out +} + +#nav-button span { + display: block; + padding: 6px; + background-color: rgba(234, 242, 246, .7); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: rotate(-90deg) translate(-100%); + transform: rotate(-90deg) translate(-100%); + border-radius: 0 0 0 5px +} + +#nav-button img { + height: 16px; + vertical-align: bottom +} + +#nav-button:hover { + opacity: 1 +} + +#nav-button.open { + left: 230px +} + +.page-wrapper { + margin-left: 230px; + position: relative; + z-index: 10; + background-color: #eaf2f6; + min-height: 100%; + padding-bottom: 1px +} + +.page-wrapper .dark-box { + width: 50%; + background-color: #393939; + position: absolute; + right: 0; + top: 0; + bottom: 0 +} + +.page-wrapper .lang-selector { + position: fixed; + z-index: 50; + border-bottom: 5px solid #393939 +} + +.lang-selector { + background-color: #222; + width: 100%; + font-weight: 700 +} + +.lang-selector button { + display: block; + float: left; + color: #fff; + text-decoration: none; + padding: 0 10px; + line-height: 30px; + outline: 0; + background: transparent; + border: none; +} + +.lang-selector button:active, +.lang-selector button:hover, +.lang-selector button:focus { + background-color: #111; + color: #fff +} + +.lang-selector button.active { + background-color: #393939; + color: #fff +} + +.lang-selector:after { + content: ''; + clear: both; + display: block +} + +.content { + position: relative; + z-index: 30 +} + +.content:after { + content: ''; + display: block; + clear: both +} + +.content>aside, +.content>details, +.content>dl, +.content>h1, +.content>h2, +.content>h3, +.content>h4, +.content>h5, +.content>h6, +.content>ol, +.content>p, +.content>table, +.content>ul, +.content>div, +.content>form>aside, +.content>form>details, +.content>form>h1, +.content>form>h2, +.content>form>h3, +.content>form>h4, +.content>form>h5, +.content>form>h6, +.content>form>p, +.content>form>table, +.content>form>ul, +.content>form>div { + margin-right: 50%; + padding: 0 28px; + box-sizing: border-box; + display: block; + text-shadow: 0 1px 0 #fff +} + +.content>ol, +.content>ul { + padding-left: 43px +} + +.content>div, +.content>h1, +.content>h2 { + clear: both +} + +.content h1 { + font-size: 30px; + padding-top: .5em; + padding-bottom: .5em; + border-bottom: 1px solid #ccc; + margin-bottom: 21px; + margin-top: 2em; + border-top: 1px solid #ddd; + background-image: linear-gradient(180deg, #fff, #f9f9f9) +} + +.content div:first-child+h1, +.content h1:first-child { + border-top-width: 0; + margin-top: 0 +} + +.content h2 { + font-size: 20px; + margin-top: 4em; + margin-bottom: 0; + border-top: 1px solid #ccc; + padding-top: 1.2em; + padding-bottom: 1.2em; + background-image: linear-gradient(180deg, hsla(0, 0%, 100%, .4), hsla(0, 0%, 100%, 0)) +} + +.content h1+div+h2, +.content h1+h2 { + margin-top: -21px; + border-top: none +} + +.content h3, +.content h4, +.content h5, +.content h6 { + font-size: 15px; + margin-top: 2.5em; + margin-bottom: .8em +} + +.content h4, +.content h5, +.content h6 { + font-size: 10px +} + +.content hr { + margin: 2em 0; + border-top: 2px solid #393939; + border-bottom: 2px solid #eaf2f6 +} + +.content table { + margin-bottom: 1em; + overflow: auto +} + +.content table td, +.content table th { + text-align: left; + vertical-align: top; + line-height: 1.6 +} + +.content table th { + padding: 5px 10px; + border-bottom: 1px solid #ccc; + vertical-align: bottom +} + +.content table td { + padding: 10px +} + +.content table tr:last-child { + border-bottom: 1px solid #ccc +} + +.content table tr:nth-child(odd)>td { + background-color: #ebf3f6 +} + +.content table tr:nth-child(even)>td { + background-color: #ebf2f6 +} + +.content dt { + font-weight: 700 +} + +.content dd { + margin-left: 15px +} + +.content dd, +.content dt, +.content li, +.content p { + line-height: 1.6; + margin-top: 0 +} + +.content img { + max-width: 100% +} + +.content code { + padding: 3px; + border-radius: 3px +} + +.content pre>code { + background-color: transparent; + padding: 0 +} + +.content aside { + padding-top: 1em; + padding-bottom: 1em; + margin-top: 1.5em; + margin-bottom: 1.5em; + background: #292929; + line-height: 1.6; + color: #c8c8c8; + text-shadow: none; +} + +.content aside.info { + background: #8fbcd4; + text-shadow: 0 1px 0 #a0c6da; + color: initial; +} + +.content aside.warning { + background-color: #c97a7e; + text-shadow: 0 1px 0 #d18e91; + color: initial; +} + +.content aside.success { + background-color: #6ac174; + text-shadow: 0 1px 0 #80ca89; + color: initial; +} + +.content aside:before { + vertical-align: middle; + padding-right: .5em; + font-size: 14px +} + +.content .search-highlight { + padding: 2px; + margin: -2px; + border-radius: 4px; + border: 1px solid #f7e633; + text-shadow: 1px 1px 0 #666; + background: linear-gradient(to top left, #f7e633, #f1d32f) +} + +.content blockquote, +.content pre { + background-color: #292929; + color: #fff; + padding: 1.5em 28px; + margin: 0; + width: 50%; + float: right; + clear: right; + box-sizing: border-box; + text-shadow: 0 1px 2px rgba(0, 0, 0, .4) +} + +.content blockquote pre.sf-dump, +.content pre pre.sf-dump { + width: 100%; +} + +.content .annotation { + background-color: #292929; + color: #fff; + padding: 0 28px; + margin: 0; + width: 50%; + float: right; + clear: right; + box-sizing: border-box; + text-shadow: 0 1px 2px rgba(0, 0, 0, .4) +} + +.content .annotation pre { + padding: 0 0; + width: 100%; + float: none; +} + +.content blockquote>p, +.content pre>p { + margin: 0 +} + +.content blockquote a, +.content pre a { + color: #fff; + text-decoration: none; + border-bottom: 1px dashed #ccc +} + +.content blockquote>p { + background-color: #1c1c1c; + border-radius: 5px; + padding: 13px; + color: #ccc; + border-top: 1px solid #000; + border-bottom: 1px solid #404040 +} + +@media (max-width:930px) { + .tocify-wrapper { + left: -230px + } + .tocify-wrapper.open { + left: 0 + } + .page-wrapper { + margin-left: 0 + } + #nav-button { + display: block + } + .tocify-wrapper .tocify-item>a { + padding-top: .3em; + padding-bottom: .3em + } +} + +@media (max-width:700px) { + .dark-box { + display: none + } + .tocify-wrapper .lang-selector { + display: block + } + .page-wrapper .lang-selector { + display: none + } + .content>aside, + .content>details, + .content>dl, + .content>h1, + .content>h2, + .content>h3, + .content>h4, + .content>h5, + .content>h6, + .content>ol, + .content>p, + .content>table, + .content>ul, + .content>div, + .content>form>aside, + .content>form>details, + .content>form>h1, + .content>form>h2, + .content>form>h3, + .content>form>h4, + .content>form>h5, + .content>form>h6, + .content>form>p, + .content>form>table, + .content>form>ul, + .content>form>div { + margin-right: 0; + } + .content blockquote, + .content pre { + float: none; + width: auto + } + .content .annotation { + float: none; + width: auto + } +} + +.badge { + padding: 1px 9px 2px; + white-space: nowrap; + -webkit-border-radius: 9px; + -moz-border-radius: 9px; + border-radius: 9px; + color: #ffffff; + text-shadow: none !important; + font-weight: bold; +} + +.badge.badge-darkred { + background-color: darkred; +} + +.badge.badge-red { + background-color: red; +} + +.badge.badge-blue { + background-color: blue; +} + +.badge.badge-darkblue { + background-color: darkblue; +} + +.badge.badge-green { + background-color: green; +} + +.badge.badge-darkgoldenrod { + background-color: darkgoldenrod; +} + +.badge.badge-darkgreen { + background-color: darkgreen; +} + +.badge.badge-purple { + background-color: purple; +} + +.badge.badge-black { + background-color: black; +} + +.badge.badge-grey { + background-color: grey; +} + +.fancy-heading-panel { + background-color: lightgrey; + border-radius: 5px; + padding-left: 5px !important; + padding-top: 5px !important; + padding-bottom: 5px !important; + margin-left: 25px; + margin-right: 10px; + width: 47%; +} + +@media screen and (max-width: 700px) { + .fancy-heading-panel { + width: 95%; + } + +} + +button { + border: none; +} + +* { + /* Foreground, Background */ + scrollbar-color: #3c4c67 transparent; +} +*::-webkit-scrollbar { /* Background */ + width: 10px; + height: 10px; + background: transparent; +} + +*::-webkit-scrollbar-thumb { /* Foreground */ + background: #626161; +} diff --git a/vendor/knuckleswtf/scribe/resources/css/theme-elements.style.css b/vendor/knuckleswtf/scribe/resources/css/theme-elements.style.css new file mode 100644 index 00000000..4189da41 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/css/theme-elements.style.css @@ -0,0 +1,13690 @@ +blockquote, dd, dl, figure, h1, h2, h3, h4, h5, h6, hr, p, pre { + margin: 0 +} + +button { + background-color: transparent; + background-image: none +} + +:focus { + outline: none +} + +fieldset, ol, ul { + margin: 0; + padding: 0 +} + +ol, ul { + list-style: none +} + +html { + font-family: var(--font-ui); + line-height: 1.5 +} + +body { + text-rendering: optimizeSpeed; + font-family: inherit; + line-height: inherit; + margin: 0; + min-height: 100vh +} + +*, :after, :before { + border: 0 solid var(--color-border, currentColor); + box-sizing: border-box +} + +hr { + border-top-width: 1px +} + +img { + border-style: solid +} + +textarea { + resize: vertical +} + +input::-ms-input-placeholder, textarea::-ms-input-placeholder { + color: #a1a1aa +} + +input::placeholder, textarea::placeholder { + color: #a1a1aa +} + +[role=button], button { + cursor: pointer +} + +table { + border-collapse: collapse +} + +h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit +} + +a { + color: inherit; + text-decoration: inherit +} + +button, input, optgroup, select, textarea { + color: inherit; + line-height: inherit; + padding: 0 +} + +code, kbd, pre, samp { + font-family: var(--font-mono) +} + +audio, canvas, embed, iframe, img, object, svg, video { + display: block; + vertical-align: middle +} + +img, video { + height: auto; + max-width: 100% +} + +button { + font-family: var(--font-ui) +} + +iframe { + border: 0 +} + +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: .01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: .01ms !important + } +} + +.sl-content-center { + align-content: center +} + +.sl-content-start { + align-content: flex-start +} + +.sl-content-end { + align-content: flex-end +} + +.sl-content-between { + align-content: space-between +} + +.sl-content-around { + align-content: space-around +} + +.sl-content-evenly { + align-content: space-evenly +} + +.sl-items-start { + align-items: flex-start +} + +.sl-items-end { + align-items: flex-end +} + +.sl-items-center { + align-items: center +} + +.sl-items-baseline { + align-items: baseline +} + +.sl-items-stretch { + align-items: stretch +} + +.sl-self-auto { + align-self: auto +} + +.sl-self-start { + align-self: flex-start +} + +.sl-self-end { + align-self: flex-end +} + +.sl-self-center { + align-self: center +} + +.sl-self-stretch { + align-self: stretch +} + +.sl-bg-transparent { + background-color: transparent +} + +.sl-bg-current { + background-color: currentColor +} + +.sl-bg-lighten-100 { + background-color: var(--color-lighten-100) +} + +.sl-bg-darken-100 { + background-color: var(--color-darken-100) +} + +.sl-bg-primary { + background-color: var(--color-primary) +} + +.sl-bg-primary-tint { + background-color: var(--color-primary-tint) +} + +.sl-bg-primary-light { + background-color: var(--color-primary-light) +} + +.sl-bg-primary-dark { + background-color: var(--color-primary-dark) +} + +.sl-bg-primary-darker { + background-color: var(--color-primary-darker) +} + +.sl-bg-success { + background-color: var(--color-success) +} + +.sl-bg-success-tint { + background-color: var(--color-success-tint) +} + +.sl-bg-success-light { + background-color: var(--color-success-light) +} + +.sl-bg-success-dark { + background-color: var(--color-success-dark) +} + +.sl-bg-success-darker { + background-color: var(--color-success-darker) +} + +.sl-bg-warning { + background-color: var(--color-warning) +} + +.sl-bg-warning-tint { + background-color: var(--color-warning-tint) +} + +.sl-bg-warning-light { + background-color: var(--color-warning-light) +} + +.sl-bg-warning-dark { + background-color: var(--color-warning-dark) +} + +.sl-bg-warning-darker { + background-color: var(--color-warning-darker) +} + +.sl-bg-danger { + background-color: var(--color-danger) +} + +.sl-bg-danger-tint { + background-color: var(--color-danger-tint) +} + +.sl-bg-danger-light { + background-color: var(--color-danger-light) +} + +.sl-bg-danger-dark { + background-color: var(--color-danger-dark) +} + +.sl-bg-danger-darker { + background-color: var(--color-danger-darker) +} + +.sl-bg-code { + background-color: var(--color-code) +} + +.sl-bg-on-code { + background-color: var(--color-on-code) +} + +.sl-bg-on-primary { + background-color: var(--color-on-primary) +} + +.sl-bg-on-success { + background-color: var(--color-on-success) +} + +.sl-bg-on-warning { + background-color: var(--color-on-warning) +} + +.sl-bg-on-danger { + background-color: var(--color-on-danger) +} + +.sl-bg-canvas-50 { + background-color: var(--color-canvas-50) +} + +.sl-bg-canvas-100 { + background-color: var(--color-canvas-100) +} + +.sl-bg-canvas-200 { + background-color: var(--color-canvas-200) +} + +.sl-bg-canvas-300 { + background-color: var(--color-canvas-300) +} + +.sl-bg-canvas-400 { + background-color: var(--color-canvas-400) +} + +.sl-bg-canvas-500 { + background-color: var(--color-canvas-500) +} + +.sl-bg-canvas-dark { + background-color: var(--color-canvas-dark) +} + +.sl-bg-canvas-pure { + background-color: var(--color-canvas-pure) +} + +.sl-bg-canvas { + background-color: var(--color-canvas) +} + +.sl-bg-canvas-tint { + background-color: var(--color-canvas-tint) +} + +.sl-bg-canvas-dialog { + background-color: var(--color-canvas-dialog) +} + +.sl-bg-body { + background-color: var(--color-text) +} + +.sl-bg-body-muted { + background-color: var(--color-text-muted) +} + +.sl-bg-body-light { + background-color: var(--color-text-light) +} + +.hover\:sl-bg-transparent:hover { + background-color: transparent +} + +.hover\:sl-bg-current:hover { + background-color: currentColor +} + +.hover\:sl-bg-lighten-100:hover { + background-color: var(--color-lighten-100) +} + +.hover\:sl-bg-darken-100:hover { + background-color: var(--color-darken-100) +} + +.hover\:sl-bg-primary:hover { + background-color: var(--color-primary) +} + +.hover\:sl-bg-primary-tint:hover { + background-color: var(--color-primary-tint) +} + +.hover\:sl-bg-primary-light:hover { + background-color: var(--color-primary-light) +} + +.hover\:sl-bg-primary-dark:hover { + background-color: var(--color-primary-dark) +} + +.hover\:sl-bg-primary-darker:hover { + background-color: var(--color-primary-darker) +} + +.hover\:sl-bg-success:hover { + background-color: var(--color-success) +} + +.hover\:sl-bg-success-tint:hover { + background-color: var(--color-success-tint) +} + +.hover\:sl-bg-success-light:hover { + background-color: var(--color-success-light) +} + +.hover\:sl-bg-success-dark:hover { + background-color: var(--color-success-dark) +} + +.hover\:sl-bg-success-darker:hover { + background-color: var(--color-success-darker) +} + +.hover\:sl-bg-warning:hover { + background-color: var(--color-warning) +} + +.hover\:sl-bg-warning-tint:hover { + background-color: var(--color-warning-tint) +} + +.hover\:sl-bg-warning-light:hover { + background-color: var(--color-warning-light) +} + +.hover\:sl-bg-warning-dark:hover { + background-color: var(--color-warning-dark) +} + +.hover\:sl-bg-warning-darker:hover { + background-color: var(--color-warning-darker) +} + +.hover\:sl-bg-danger:hover { + background-color: var(--color-danger) +} + +.hover\:sl-bg-danger-tint:hover { + background-color: var(--color-danger-tint) +} + +.hover\:sl-bg-danger-light:hover { + background-color: var(--color-danger-light) +} + +.hover\:sl-bg-danger-dark:hover { + background-color: var(--color-danger-dark) +} + +.hover\:sl-bg-danger-darker:hover { + background-color: var(--color-danger-darker) +} + +.hover\:sl-bg-code:hover { + background-color: var(--color-code) +} + +.hover\:sl-bg-on-code:hover { + background-color: var(--color-on-code) +} + +.hover\:sl-bg-on-primary:hover { + background-color: var(--color-on-primary) +} + +.hover\:sl-bg-on-success:hover { + background-color: var(--color-on-success) +} + +.hover\:sl-bg-on-warning:hover { + background-color: var(--color-on-warning) +} + +.hover\:sl-bg-on-danger:hover { + background-color: var(--color-on-danger) +} + +.hover\:sl-bg-canvas-50:hover { + background-color: var(--color-canvas-50) +} + +.hover\:sl-bg-canvas-100:hover { + background-color: var(--color-canvas-100) +} + +.hover\:sl-bg-canvas-200:hover { + background-color: var(--color-canvas-200) +} + +.hover\:sl-bg-canvas-300:hover { + background-color: var(--color-canvas-300) +} + +.hover\:sl-bg-canvas-400:hover { + background-color: var(--color-canvas-400) +} + +.hover\:sl-bg-canvas-500:hover { + background-color: var(--color-canvas-500) +} + +.hover\:sl-bg-canvas-dark:hover { + background-color: var(--color-canvas-dark) +} + +.hover\:sl-bg-canvas-pure:hover { + background-color: var(--color-canvas-pure) +} + +.hover\:sl-bg-canvas:hover { + background-color: var(--color-canvas) +} + +.hover\:sl-bg-canvas-tint:hover { + background-color: var(--color-canvas-tint) +} + +.hover\:sl-bg-canvas-dialog:hover { + background-color: var(--color-canvas-dialog) +} + +.hover\:sl-bg-body:hover { + background-color: var(--color-text) +} + +.hover\:sl-bg-body-muted:hover { + background-color: var(--color-text-muted) +} + +.hover\:sl-bg-body-light:hover { + background-color: var(--color-text-light) +} + +.focus\:sl-bg-transparent:focus { + background-color: transparent +} + +.focus\:sl-bg-current:focus { + background-color: currentColor +} + +.focus\:sl-bg-lighten-100:focus { + background-color: var(--color-lighten-100) +} + +.focus\:sl-bg-darken-100:focus { + background-color: var(--color-darken-100) +} + +.focus\:sl-bg-primary:focus { + background-color: var(--color-primary) +} + +.focus\:sl-bg-primary-tint:focus { + background-color: var(--color-primary-tint) +} + +.focus\:sl-bg-primary-light:focus { + background-color: var(--color-primary-light) +} + +.focus\:sl-bg-primary-dark:focus { + background-color: var(--color-primary-dark) +} + +.focus\:sl-bg-primary-darker:focus { + background-color: var(--color-primary-darker) +} + +.focus\:sl-bg-success:focus { + background-color: var(--color-success) +} + +.focus\:sl-bg-success-tint:focus { + background-color: var(--color-success-tint) +} + +.focus\:sl-bg-success-light:focus { + background-color: var(--color-success-light) +} + +.focus\:sl-bg-success-dark:focus { + background-color: var(--color-success-dark) +} + +.focus\:sl-bg-success-darker:focus { + background-color: var(--color-success-darker) +} + +.focus\:sl-bg-warning:focus { + background-color: var(--color-warning) +} + +.focus\:sl-bg-warning-tint:focus { + background-color: var(--color-warning-tint) +} + +.focus\:sl-bg-warning-light:focus { + background-color: var(--color-warning-light) +} + +.focus\:sl-bg-warning-dark:focus { + background-color: var(--color-warning-dark) +} + +.focus\:sl-bg-warning-darker:focus { + background-color: var(--color-warning-darker) +} + +.focus\:sl-bg-danger:focus { + background-color: var(--color-danger) +} + +.focus\:sl-bg-danger-tint:focus { + background-color: var(--color-danger-tint) +} + +.focus\:sl-bg-danger-light:focus { + background-color: var(--color-danger-light) +} + +.focus\:sl-bg-danger-dark:focus { + background-color: var(--color-danger-dark) +} + +.focus\:sl-bg-danger-darker:focus { + background-color: var(--color-danger-darker) +} + +.focus\:sl-bg-code:focus { + background-color: var(--color-code) +} + +.focus\:sl-bg-on-code:focus { + background-color: var(--color-on-code) +} + +.focus\:sl-bg-on-primary:focus { + background-color: var(--color-on-primary) +} + +.focus\:sl-bg-on-success:focus { + background-color: var(--color-on-success) +} + +.focus\:sl-bg-on-warning:focus { + background-color: var(--color-on-warning) +} + +.focus\:sl-bg-on-danger:focus { + background-color: var(--color-on-danger) +} + +.focus\:sl-bg-canvas-50:focus { + background-color: var(--color-canvas-50) +} + +.focus\:sl-bg-canvas-100:focus { + background-color: var(--color-canvas-100) +} + +.focus\:sl-bg-canvas-200:focus { + background-color: var(--color-canvas-200) +} + +.focus\:sl-bg-canvas-300:focus { + background-color: var(--color-canvas-300) +} + +.focus\:sl-bg-canvas-400:focus { + background-color: var(--color-canvas-400) +} + +.focus\:sl-bg-canvas-500:focus { + background-color: var(--color-canvas-500) +} + +.focus\:sl-bg-canvas-dark:focus { + background-color: var(--color-canvas-dark) +} + +.focus\:sl-bg-canvas-pure:focus { + background-color: var(--color-canvas-pure) +} + +.focus\:sl-bg-canvas:focus { + background-color: var(--color-canvas) +} + +.focus\:sl-bg-canvas-tint:focus { + background-color: var(--color-canvas-tint) +} + +.focus\:sl-bg-canvas-dialog:focus { + background-color: var(--color-canvas-dialog) +} + +.focus\:sl-bg-body:focus { + background-color: var(--color-text) +} + +.focus\:sl-bg-body-muted:focus { + background-color: var(--color-text-muted) +} + +.focus\:sl-bg-body-light:focus { + background-color: var(--color-text-light) +} + +.active\:sl-bg-transparent:active { + background-color: transparent +} + +.active\:sl-bg-current:active { + background-color: currentColor +} + +.active\:sl-bg-lighten-100:active { + background-color: var(--color-lighten-100) +} + +.active\:sl-bg-darken-100:active { + background-color: var(--color-darken-100) +} + +.active\:sl-bg-primary:active { + background-color: var(--color-primary) +} + +.active\:sl-bg-primary-tint:active { + background-color: var(--color-primary-tint) +} + +.active\:sl-bg-primary-light:active { + background-color: var(--color-primary-light) +} + +.active\:sl-bg-primary-dark:active { + background-color: var(--color-primary-dark) +} + +.active\:sl-bg-primary-darker:active { + background-color: var(--color-primary-darker) +} + +.active\:sl-bg-success:active { + background-color: var(--color-success) +} + +.active\:sl-bg-success-tint:active { + background-color: var(--color-success-tint) +} + +.active\:sl-bg-success-light:active { + background-color: var(--color-success-light) +} + +.active\:sl-bg-success-dark:active { + background-color: var(--color-success-dark) +} + +.active\:sl-bg-success-darker:active { + background-color: var(--color-success-darker) +} + +.active\:sl-bg-warning:active { + background-color: var(--color-warning) +} + +.active\:sl-bg-warning-tint:active { + background-color: var(--color-warning-tint) +} + +.active\:sl-bg-warning-light:active { + background-color: var(--color-warning-light) +} + +.active\:sl-bg-warning-dark:active { + background-color: var(--color-warning-dark) +} + +.active\:sl-bg-warning-darker:active { + background-color: var(--color-warning-darker) +} + +.active\:sl-bg-danger:active { + background-color: var(--color-danger) +} + +.active\:sl-bg-danger-tint:active { + background-color: var(--color-danger-tint) +} + +.active\:sl-bg-danger-light:active { + background-color: var(--color-danger-light) +} + +.active\:sl-bg-danger-dark:active { + background-color: var(--color-danger-dark) +} + +.active\:sl-bg-danger-darker:active { + background-color: var(--color-danger-darker) +} + +.active\:sl-bg-code:active { + background-color: var(--color-code) +} + +.active\:sl-bg-on-code:active { + background-color: var(--color-on-code) +} + +.active\:sl-bg-on-primary:active { + background-color: var(--color-on-primary) +} + +.active\:sl-bg-on-success:active { + background-color: var(--color-on-success) +} + +.active\:sl-bg-on-warning:active { + background-color: var(--color-on-warning) +} + +.active\:sl-bg-on-danger:active { + background-color: var(--color-on-danger) +} + +.active\:sl-bg-canvas-50:active { + background-color: var(--color-canvas-50) +} + +.active\:sl-bg-canvas-100:active { + background-color: var(--color-canvas-100) +} + +.active\:sl-bg-canvas-200:active { + background-color: var(--color-canvas-200) +} + +.active\:sl-bg-canvas-300:active { + background-color: var(--color-canvas-300) +} + +.active\:sl-bg-canvas-400:active { + background-color: var(--color-canvas-400) +} + +.active\:sl-bg-canvas-500:active { + background-color: var(--color-canvas-500) +} + +.active\:sl-bg-canvas-dark:active { + background-color: var(--color-canvas-dark) +} + +.active\:sl-bg-canvas-pure:active { + background-color: var(--color-canvas-pure) +} + +.active\:sl-bg-canvas:active { + background-color: var(--color-canvas) +} + +.active\:sl-bg-canvas-tint:active { + background-color: var(--color-canvas-tint) +} + +.active\:sl-bg-canvas-dialog:active { + background-color: var(--color-canvas-dialog) +} + +.active\:sl-bg-body:active { + background-color: var(--color-text) +} + +.active\:sl-bg-body-muted:active { + background-color: var(--color-text-muted) +} + +.active\:sl-bg-body-light:active { + background-color: var(--color-text-light) +} + +.disabled\:sl-bg-transparent:disabled { + background-color: transparent +} + +.disabled\:sl-bg-current:disabled { + background-color: currentColor +} + +.disabled\:sl-bg-lighten-100:disabled { + background-color: var(--color-lighten-100) +} + +.disabled\:sl-bg-darken-100:disabled { + background-color: var(--color-darken-100) +} + +.disabled\:sl-bg-primary:disabled { + background-color: var(--color-primary) +} + +.disabled\:sl-bg-primary-tint:disabled { + background-color: var(--color-primary-tint) +} + +.disabled\:sl-bg-primary-light:disabled { + background-color: var(--color-primary-light) +} + +.disabled\:sl-bg-primary-dark:disabled { + background-color: var(--color-primary-dark) +} + +.disabled\:sl-bg-primary-darker:disabled { + background-color: var(--color-primary-darker) +} + +.disabled\:sl-bg-success:disabled { + background-color: var(--color-success) +} + +.disabled\:sl-bg-success-tint:disabled { + background-color: var(--color-success-tint) +} + +.disabled\:sl-bg-success-light:disabled { + background-color: var(--color-success-light) +} + +.disabled\:sl-bg-success-dark:disabled { + background-color: var(--color-success-dark) +} + +.disabled\:sl-bg-success-darker:disabled { + background-color: var(--color-success-darker) +} + +.disabled\:sl-bg-warning:disabled { + background-color: var(--color-warning) +} + +.disabled\:sl-bg-warning-tint:disabled { + background-color: var(--color-warning-tint) +} + +.disabled\:sl-bg-warning-light:disabled { + background-color: var(--color-warning-light) +} + +.disabled\:sl-bg-warning-dark:disabled { + background-color: var(--color-warning-dark) +} + +.disabled\:sl-bg-warning-darker:disabled { + background-color: var(--color-warning-darker) +} + +.disabled\:sl-bg-danger:disabled { + background-color: var(--color-danger) +} + +.disabled\:sl-bg-danger-tint:disabled { + background-color: var(--color-danger-tint) +} + +.disabled\:sl-bg-danger-light:disabled { + background-color: var(--color-danger-light) +} + +.disabled\:sl-bg-danger-dark:disabled { + background-color: var(--color-danger-dark) +} + +.disabled\:sl-bg-danger-darker:disabled { + background-color: var(--color-danger-darker) +} + +.disabled\:sl-bg-code:disabled { + background-color: var(--color-code) +} + +.disabled\:sl-bg-on-code:disabled { + background-color: var(--color-on-code) +} + +.disabled\:sl-bg-on-primary:disabled { + background-color: var(--color-on-primary) +} + +.disabled\:sl-bg-on-success:disabled { + background-color: var(--color-on-success) +} + +.disabled\:sl-bg-on-warning:disabled { + background-color: var(--color-on-warning) +} + +.disabled\:sl-bg-on-danger:disabled { + background-color: var(--color-on-danger) +} + +.disabled\:sl-bg-canvas-50:disabled { + background-color: var(--color-canvas-50) +} + +.disabled\:sl-bg-canvas-100:disabled { + background-color: var(--color-canvas-100) +} + +.disabled\:sl-bg-canvas-200:disabled { + background-color: var(--color-canvas-200) +} + +.disabled\:sl-bg-canvas-300:disabled { + background-color: var(--color-canvas-300) +} + +.disabled\:sl-bg-canvas-400:disabled { + background-color: var(--color-canvas-400) +} + +.disabled\:sl-bg-canvas-500:disabled { + background-color: var(--color-canvas-500) +} + +.disabled\:sl-bg-canvas-dark:disabled { + background-color: var(--color-canvas-dark) +} + +.disabled\:sl-bg-canvas-pure:disabled { + background-color: var(--color-canvas-pure) +} + +.disabled\:sl-bg-canvas:disabled { + background-color: var(--color-canvas) +} + +.disabled\:sl-bg-canvas-tint:disabled { + background-color: var(--color-canvas-tint) +} + +.disabled\:sl-bg-canvas-dialog:disabled { + background-color: var(--color-canvas-dialog) +} + +.disabled\:sl-bg-body:disabled { + background-color: var(--color-text) +} + +.disabled\:sl-bg-body-muted:disabled { + background-color: var(--color-text-muted) +} + +.disabled\:sl-bg-body-light:disabled { + background-color: var(--color-text-light) +} + +.sl-bg-none { + background-image: none +} + +.sl-bg-gradient-to-t { + background-image: linear-gradient(to top, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-tr { + background-image: linear-gradient(to top right, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-br { + background-image: linear-gradient(to bottom right, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-b { + background-image: linear-gradient(to bottom, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-bl { + background-image: linear-gradient(to bottom left, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-l { + background-image: linear-gradient(to left, var(--tw-gradient-stops)) +} + +.sl-bg-gradient-to-tl { + background-image: linear-gradient(to top left, var(--tw-gradient-stops)) +} + +.sl-blur-0, .sl-blur-none { + --tw-blur: blur(0) +} + +.sl-blur-sm { + --tw-blur: blur(4px) +} + +.sl-blur { + --tw-blur: blur(8px) +} + +.sl-blur-md { + --tw-blur: blur(12px) +} + +.sl-blur-lg { + --tw-blur: blur(16px) +} + +.sl-blur-xl { + --tw-blur: blur(24px) +} + +.sl-blur-2xl { + --tw-blur: blur(40px) +} + +.sl-blur-3xl { + --tw-blur: blur(64px) +} + +.sl-border-transparent { + border-color: transparent +} + +.sl-border-current { + border-color: currentColor +} + +.sl-border-lighten-100 { + border-color: var(--color-lighten-100) +} + +.sl-border-darken-100 { + border-color: var(--color-darken-100) +} + +.sl-border-primary { + border-color: var(--color-primary) +} + +.sl-border-primary-tint { + border-color: var(--color-primary-tint) +} + +.sl-border-primary-light { + border-color: var(--color-primary-light) +} + +.sl-border-primary-dark { + border-color: var(--color-primary-dark) +} + +.sl-border-primary-darker { + border-color: var(--color-primary-darker) +} + +.sl-border-success { + border-color: var(--color-success) +} + +.sl-border-success-tint { + border-color: var(--color-success-tint) +} + +.sl-border-success-light { + border-color: var(--color-success-light) +} + +.sl-border-success-dark { + border-color: var(--color-success-dark) +} + +.sl-border-success-darker { + border-color: var(--color-success-darker) +} + +.sl-border-warning { + border-color: var(--color-warning) +} + +.sl-border-warning-tint { + border-color: var(--color-warning-tint) +} + +.sl-border-warning-light { + border-color: var(--color-warning-light) +} + +.sl-border-warning-dark { + border-color: var(--color-warning-dark) +} + +.sl-border-warning-darker { + border-color: var(--color-warning-darker) +} + +.sl-border-danger { + border-color: var(--color-danger) +} + +.sl-border-danger-tint { + border-color: var(--color-danger-tint) +} + +.sl-border-danger-light { + border-color: var(--color-danger-light) +} + +.sl-border-danger-dark { + border-color: var(--color-danger-dark) +} + +.sl-border-danger-darker { + border-color: var(--color-danger-darker) +} + +.sl-border-code { + border-color: var(--color-code) +} + +.sl-border-on-code { + border-color: var(--color-on-code) +} + +.sl-border-on-primary { + border-color: var(--color-on-primary) +} + +.sl-border-on-success { + border-color: var(--color-on-success) +} + +.sl-border-on-warning { + border-color: var(--color-on-warning) +} + +.sl-border-on-danger { + border-color: var(--color-on-danger) +} + +.sl-border-light { + border-color: var(--color-border-light) +} + +.sl-border-dark { + border-color: var(--color-border-dark) +} + +.sl-border-button { + border-color: var(--color-border-button) +} + +.sl-border-input { + border-color: var(--color-border-input) +} + +.sl-border-body { + border-color: var(--color-text) +} + +.hover\:sl-border-transparent:hover { + border-color: transparent +} + +.hover\:sl-border-current:hover { + border-color: currentColor +} + +.hover\:sl-border-lighten-100:hover { + border-color: var(--color-lighten-100) +} + +.hover\:sl-border-darken-100:hover { + border-color: var(--color-darken-100) +} + +.hover\:sl-border-primary:hover { + border-color: var(--color-primary) +} + +.hover\:sl-border-primary-tint:hover { + border-color: var(--color-primary-tint) +} + +.hover\:sl-border-primary-light:hover { + border-color: var(--color-primary-light) +} + +.hover\:sl-border-primary-dark:hover { + border-color: var(--color-primary-dark) +} + +.hover\:sl-border-primary-darker:hover { + border-color: var(--color-primary-darker) +} + +.hover\:sl-border-success:hover { + border-color: var(--color-success) +} + +.hover\:sl-border-success-tint:hover { + border-color: var(--color-success-tint) +} + +.hover\:sl-border-success-light:hover { + border-color: var(--color-success-light) +} + +.hover\:sl-border-success-dark:hover { + border-color: var(--color-success-dark) +} + +.hover\:sl-border-success-darker:hover { + border-color: var(--color-success-darker) +} + +.hover\:sl-border-warning:hover { + border-color: var(--color-warning) +} + +.hover\:sl-border-warning-tint:hover { + border-color: var(--color-warning-tint) +} + +.hover\:sl-border-warning-light:hover { + border-color: var(--color-warning-light) +} + +.hover\:sl-border-warning-dark:hover { + border-color: var(--color-warning-dark) +} + +.hover\:sl-border-warning-darker:hover { + border-color: var(--color-warning-darker) +} + +.hover\:sl-border-danger:hover { + border-color: var(--color-danger) +} + +.hover\:sl-border-danger-tint:hover { + border-color: var(--color-danger-tint) +} + +.hover\:sl-border-danger-light:hover { + border-color: var(--color-danger-light) +} + +.hover\:sl-border-danger-dark:hover { + border-color: var(--color-danger-dark) +} + +.hover\:sl-border-danger-darker:hover { + border-color: var(--color-danger-darker) +} + +.hover\:sl-border-code:hover { + border-color: var(--color-code) +} + +.hover\:sl-border-on-code:hover { + border-color: var(--color-on-code) +} + +.hover\:sl-border-on-primary:hover { + border-color: var(--color-on-primary) +} + +.hover\:sl-border-on-success:hover { + border-color: var(--color-on-success) +} + +.hover\:sl-border-on-warning:hover { + border-color: var(--color-on-warning) +} + +.hover\:sl-border-on-danger:hover { + border-color: var(--color-on-danger) +} + +.hover\:sl-border-light:hover { + border-color: var(--color-border-light) +} + +.hover\:sl-border-dark:hover { + border-color: var(--color-border-dark) +} + +.hover\:sl-border-button:hover { + border-color: var(--color-border-button) +} + +.hover\:sl-border-input:hover { + border-color: var(--color-border-input) +} + +.hover\:sl-border-body:hover { + border-color: var(--color-text) +} + +.focus\:sl-border-transparent:focus { + border-color: transparent +} + +.focus\:sl-border-current:focus { + border-color: currentColor +} + +.focus\:sl-border-lighten-100:focus { + border-color: var(--color-lighten-100) +} + +.focus\:sl-border-darken-100:focus { + border-color: var(--color-darken-100) +} + +.focus\:sl-border-primary:focus { + border-color: var(--color-primary) +} + +.focus\:sl-border-primary-tint:focus { + border-color: var(--color-primary-tint) +} + +.focus\:sl-border-primary-light:focus { + border-color: var(--color-primary-light) +} + +.focus\:sl-border-primary-dark:focus { + border-color: var(--color-primary-dark) +} + +.focus\:sl-border-primary-darker:focus { + border-color: var(--color-primary-darker) +} + +.focus\:sl-border-success:focus { + border-color: var(--color-success) +} + +.focus\:sl-border-success-tint:focus { + border-color: var(--color-success-tint) +} + +.focus\:sl-border-success-light:focus { + border-color: var(--color-success-light) +} + +.focus\:sl-border-success-dark:focus { + border-color: var(--color-success-dark) +} + +.focus\:sl-border-success-darker:focus { + border-color: var(--color-success-darker) +} + +.focus\:sl-border-warning:focus { + border-color: var(--color-warning) +} + +.focus\:sl-border-warning-tint:focus { + border-color: var(--color-warning-tint) +} + +.focus\:sl-border-warning-light:focus { + border-color: var(--color-warning-light) +} + +.focus\:sl-border-warning-dark:focus { + border-color: var(--color-warning-dark) +} + +.focus\:sl-border-warning-darker:focus { + border-color: var(--color-warning-darker) +} + +.focus\:sl-border-danger:focus { + border-color: var(--color-danger) +} + +.focus\:sl-border-danger-tint:focus { + border-color: var(--color-danger-tint) +} + +.focus\:sl-border-danger-light:focus { + border-color: var(--color-danger-light) +} + +.focus\:sl-border-danger-dark:focus { + border-color: var(--color-danger-dark) +} + +.focus\:sl-border-danger-darker:focus { + border-color: var(--color-danger-darker) +} + +.focus\:sl-border-code:focus { + border-color: var(--color-code) +} + +.focus\:sl-border-on-code:focus { + border-color: var(--color-on-code) +} + +.focus\:sl-border-on-primary:focus { + border-color: var(--color-on-primary) +} + +.focus\:sl-border-on-success:focus { + border-color: var(--color-on-success) +} + +.focus\:sl-border-on-warning:focus { + border-color: var(--color-on-warning) +} + +.focus\:sl-border-on-danger:focus { + border-color: var(--color-on-danger) +} + +.focus\:sl-border-light:focus { + border-color: var(--color-border-light) +} + +.focus\:sl-border-dark:focus { + border-color: var(--color-border-dark) +} + +.focus\:sl-border-button:focus { + border-color: var(--color-border-button) +} + +.focus\:sl-border-input:focus { + border-color: var(--color-border-input) +} + +.focus\:sl-border-body:focus { + border-color: var(--color-text) +} + +.focus-within\:sl-border-transparent:focus-within { + border-color: transparent +} + +.focus-within\:sl-border-current:focus-within { + border-color: currentColor +} + +.focus-within\:sl-border-lighten-100:focus-within { + border-color: var(--color-lighten-100) +} + +.focus-within\:sl-border-darken-100:focus-within { + border-color: var(--color-darken-100) +} + +.focus-within\:sl-border-primary:focus-within { + border-color: var(--color-primary) +} + +.focus-within\:sl-border-primary-tint:focus-within { + border-color: var(--color-primary-tint) +} + +.focus-within\:sl-border-primary-light:focus-within { + border-color: var(--color-primary-light) +} + +.focus-within\:sl-border-primary-dark:focus-within { + border-color: var(--color-primary-dark) +} + +.focus-within\:sl-border-primary-darker:focus-within { + border-color: var(--color-primary-darker) +} + +.focus-within\:sl-border-success:focus-within { + border-color: var(--color-success) +} + +.focus-within\:sl-border-success-tint:focus-within { + border-color: var(--color-success-tint) +} + +.focus-within\:sl-border-success-light:focus-within { + border-color: var(--color-success-light) +} + +.focus-within\:sl-border-success-dark:focus-within { + border-color: var(--color-success-dark) +} + +.focus-within\:sl-border-success-darker:focus-within { + border-color: var(--color-success-darker) +} + +.focus-within\:sl-border-warning:focus-within { + border-color: var(--color-warning) +} + +.focus-within\:sl-border-warning-tint:focus-within { + border-color: var(--color-warning-tint) +} + +.focus-within\:sl-border-warning-light:focus-within { + border-color: var(--color-warning-light) +} + +.focus-within\:sl-border-warning-dark:focus-within { + border-color: var(--color-warning-dark) +} + +.focus-within\:sl-border-warning-darker:focus-within { + border-color: var(--color-warning-darker) +} + +.focus-within\:sl-border-danger:focus-within { + border-color: var(--color-danger) +} + +.focus-within\:sl-border-danger-tint:focus-within { + border-color: var(--color-danger-tint) +} + +.focus-within\:sl-border-danger-light:focus-within { + border-color: var(--color-danger-light) +} + +.focus-within\:sl-border-danger-dark:focus-within { + border-color: var(--color-danger-dark) +} + +.focus-within\:sl-border-danger-darker:focus-within { + border-color: var(--color-danger-darker) +} + +.focus-within\:sl-border-code:focus-within { + border-color: var(--color-code) +} + +.focus-within\:sl-border-on-code:focus-within { + border-color: var(--color-on-code) +} + +.focus-within\:sl-border-on-primary:focus-within { + border-color: var(--color-on-primary) +} + +.focus-within\:sl-border-on-success:focus-within { + border-color: var(--color-on-success) +} + +.focus-within\:sl-border-on-warning:focus-within { + border-color: var(--color-on-warning) +} + +.focus-within\:sl-border-on-danger:focus-within { + border-color: var(--color-on-danger) +} + +.focus-within\:sl-border-light:focus-within { + border-color: var(--color-border-light) +} + +.focus-within\:sl-border-dark:focus-within { + border-color: var(--color-border-dark) +} + +.focus-within\:sl-border-button:focus-within { + border-color: var(--color-border-button) +} + +.focus-within\:sl-border-input:focus-within { + border-color: var(--color-border-input) +} + +.focus-within\:sl-border-body:focus-within { + border-color: var(--color-text) +} + +.active\:sl-border-transparent:active { + border-color: transparent +} + +.active\:sl-border-current:active { + border-color: currentColor +} + +.active\:sl-border-lighten-100:active { + border-color: var(--color-lighten-100) +} + +.active\:sl-border-darken-100:active { + border-color: var(--color-darken-100) +} + +.active\:sl-border-primary:active { + border-color: var(--color-primary) +} + +.active\:sl-border-primary-tint:active { + border-color: var(--color-primary-tint) +} + +.active\:sl-border-primary-light:active { + border-color: var(--color-primary-light) +} + +.active\:sl-border-primary-dark:active { + border-color: var(--color-primary-dark) +} + +.active\:sl-border-primary-darker:active { + border-color: var(--color-primary-darker) +} + +.active\:sl-border-success:active { + border-color: var(--color-success) +} + +.active\:sl-border-success-tint:active { + border-color: var(--color-success-tint) +} + +.active\:sl-border-success-light:active { + border-color: var(--color-success-light) +} + +.active\:sl-border-success-dark:active { + border-color: var(--color-success-dark) +} + +.active\:sl-border-success-darker:active { + border-color: var(--color-success-darker) +} + +.active\:sl-border-warning:active { + border-color: var(--color-warning) +} + +.active\:sl-border-warning-tint:active { + border-color: var(--color-warning-tint) +} + +.active\:sl-border-warning-light:active { + border-color: var(--color-warning-light) +} + +.active\:sl-border-warning-dark:active { + border-color: var(--color-warning-dark) +} + +.active\:sl-border-warning-darker:active { + border-color: var(--color-warning-darker) +} + +.active\:sl-border-danger:active { + border-color: var(--color-danger) +} + +.active\:sl-border-danger-tint:active { + border-color: var(--color-danger-tint) +} + +.active\:sl-border-danger-light:active { + border-color: var(--color-danger-light) +} + +.active\:sl-border-danger-dark:active { + border-color: var(--color-danger-dark) +} + +.active\:sl-border-danger-darker:active { + border-color: var(--color-danger-darker) +} + +.active\:sl-border-code:active { + border-color: var(--color-code) +} + +.active\:sl-border-on-code:active { + border-color: var(--color-on-code) +} + +.active\:sl-border-on-primary:active { + border-color: var(--color-on-primary) +} + +.active\:sl-border-on-success:active { + border-color: var(--color-on-success) +} + +.active\:sl-border-on-warning:active { + border-color: var(--color-on-warning) +} + +.active\:sl-border-on-danger:active { + border-color: var(--color-on-danger) +} + +.active\:sl-border-light:active { + border-color: var(--color-border-light) +} + +.active\:sl-border-dark:active { + border-color: var(--color-border-dark) +} + +.active\:sl-border-button:active { + border-color: var(--color-border-button) +} + +.active\:sl-border-input:active { + border-color: var(--color-border-input) +} + +.active\:sl-border-body:active { + border-color: var(--color-text) +} + +.sl-rounded-none { + border-radius: 0 +} + +.sl-rounded-sm { + border-radius: 1px +} + +.sl-rounded { + border-radius: 2px +} + +.sl-rounded-lg { + border-radius: 5px +} + +.sl-rounded-xl { + border-radius: 7px +} + +.sl-rounded-full { + border-radius: 9999px +} + +.sl-rounded-t-none { + border-top-left-radius: 0; + border-top-right-radius: 0 +} + +.sl-rounded-r-none { + border-bottom-right-radius: 0; + border-top-right-radius: 0 +} + +.sl-rounded-b-none { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0 +} + +.sl-rounded-l-none { + border-bottom-left-radius: 0; + border-top-left-radius: 0 +} + +.sl-rounded-t-sm { + border-top-left-radius: 1px; + border-top-right-radius: 1px +} + +.sl-rounded-r-sm { + border-bottom-right-radius: 1px; + border-top-right-radius: 1px +} + +.sl-rounded-b-sm { + border-bottom-left-radius: 1px; + border-bottom-right-radius: 1px +} + +.sl-rounded-l-sm { + border-bottom-left-radius: 1px; + border-top-left-radius: 1px +} + +.sl-rounded-t { + border-top-left-radius: 2px +} + +.sl-rounded-r, .sl-rounded-t { + border-top-right-radius: 2px +} + +.sl-rounded-b, .sl-rounded-r { + border-bottom-right-radius: 2px +} + +.sl-rounded-b, .sl-rounded-l { + border-bottom-left-radius: 2px +} + +.sl-rounded-l { + border-top-left-radius: 2px +} + +.sl-rounded-t-lg { + border-top-left-radius: 5px; + border-top-right-radius: 5px +} + +.sl-rounded-r-lg { + border-bottom-right-radius: 5px; + border-top-right-radius: 5px +} + +.sl-rounded-b-lg { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px +} + +.sl-rounded-l-lg { + border-bottom-left-radius: 5px; + border-top-left-radius: 5px +} + +.sl-rounded-t-xl { + border-top-left-radius: 7px; + border-top-right-radius: 7px +} + +.sl-rounded-r-xl { + border-bottom-right-radius: 7px; + border-top-right-radius: 7px +} + +.sl-rounded-b-xl { + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px +} + +.sl-rounded-l-xl { + border-bottom-left-radius: 7px; + border-top-left-radius: 7px +} + +.sl-rounded-t-full { + border-top-left-radius: 9999px; + border-top-right-radius: 9999px +} + +.sl-rounded-r-full { + border-bottom-right-radius: 9999px; + border-top-right-radius: 9999px +} + +.sl-rounded-b-full { + border-bottom-left-radius: 9999px; + border-bottom-right-radius: 9999px +} + +.sl-rounded-l-full { + border-bottom-left-radius: 9999px; + border-top-left-radius: 9999px +} + +.sl-rounded-tl-none { + border-top-left-radius: 0 +} + +.sl-rounded-tr-none { + border-top-right-radius: 0 +} + +.sl-rounded-br-none { + border-bottom-right-radius: 0 +} + +.sl-rounded-bl-none { + border-bottom-left-radius: 0 +} + +.sl-rounded-tl-sm { + border-top-left-radius: 1px +} + +.sl-rounded-tr-sm { + border-top-right-radius: 1px +} + +.sl-rounded-br-sm { + border-bottom-right-radius: 1px +} + +.sl-rounded-bl-sm { + border-bottom-left-radius: 1px +} + +.sl-rounded-tl { + border-top-left-radius: 2px +} + +.sl-rounded-tr { + border-top-right-radius: 2px +} + +.sl-rounded-br { + border-bottom-right-radius: 2px +} + +.sl-rounded-bl { + border-bottom-left-radius: 2px +} + +.sl-rounded-tl-lg { + border-top-left-radius: 5px +} + +.sl-rounded-tr-lg { + border-top-right-radius: 5px +} + +.sl-rounded-br-lg { + border-bottom-right-radius: 5px +} + +.sl-rounded-bl-lg { + border-bottom-left-radius: 5px +} + +.sl-rounded-tl-xl { + border-top-left-radius: 7px +} + +.sl-rounded-tr-xl { + border-top-right-radius: 7px +} + +.sl-rounded-br-xl { + border-bottom-right-radius: 7px +} + +.sl-rounded-bl-xl { + border-bottom-left-radius: 7px +} + +.sl-rounded-tl-full { + border-top-left-radius: 9999px +} + +.sl-rounded-tr-full { + border-top-right-radius: 9999px +} + +.sl-rounded-br-full { + border-bottom-right-radius: 9999px +} + +.sl-rounded-bl-full { + border-bottom-left-radius: 9999px +} + +.sl-border-solid { + border-style: solid +} + +.sl-border-dashed { + border-style: dashed +} + +.sl-border-dotted { + border-style: dotted +} + +.sl-border-double { + border-style: double +} + +.sl-border-none { + border-style: none +} + +.sl-border-0 { + border-width: 0 +} + +.sl-border-2 { + border-width: 2px +} + +.sl-border-4 { + border-width: 4px +} + +.sl-border-8 { + border-width: 8px +} + +.sl-border { + border-width: 1px +} + +.sl-border-t-0 { + border-top-width: 0 +} + +.sl-border-r-0 { + border-right-width: 0 +} + +.sl-border-b-0 { + border-bottom-width: 0 +} + +.sl-border-l-0 { + border-left-width: 0 +} + +.sl-border-t-2 { + border-top-width: 2px +} + +.sl-border-r-2 { + border-right-width: 2px +} + +.sl-border-b-2 { + border-bottom-width: 2px +} + +.sl-border-l-2 { + border-left-width: 2px +} + +.sl-border-t-4 { + border-top-width: 4px +} + +.sl-border-r-4 { + border-right-width: 4px +} + +.sl-border-b-4 { + border-bottom-width: 4px +} + +.sl-border-l-4 { + border-left-width: 4px +} + +.sl-border-t-8 { + border-top-width: 8px +} + +.sl-border-r-8 { + border-right-width: 8px +} + +.sl-border-b-8 { + border-bottom-width: 8px +} + +.sl-border-l-8 { + border-left-width: 8px +} + +.sl-border-t { + border-top-width: 1px +} + +.sl-border-r { + border-right-width: 1px +} + +.sl-border-b { + border-bottom-width: 1px +} + +.sl-border-l { + border-left-width: 1px +} + +* { + --tw-shadow: 0 0 #0000 +} + +.sl-shadow-sm { + --tw-shadow: var(--shadow-sm); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.sl-shadow, .sl-shadow-md { + --tw-shadow: var(--shadow-md) +} + +.sl-shadow, .sl-shadow-lg, .sl-shadow-md { + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.sl-shadow-lg { + --tw-shadow: var(--shadow-lg) +} + +.sl-shadow-xl { + --tw-shadow: var(--shadow-xl) +} + +.sl-shadow-2xl, .sl-shadow-xl { + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.sl-shadow-2xl { + --tw-shadow: var(--shadow-2xl) +} + +.hover\:sl-shadow-sm:hover { + --tw-shadow: var(--shadow-sm); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.hover\:sl-shadow-md:hover, .hover\:sl-shadow:hover { + --tw-shadow: var(--shadow-md) +} + +.hover\:sl-shadow-lg:hover, .hover\:sl-shadow-md:hover, .hover\:sl-shadow:hover { + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.hover\:sl-shadow-lg:hover { + --tw-shadow: var(--shadow-lg) +} + +.hover\:sl-shadow-xl:hover { + --tw-shadow: var(--shadow-xl) +} + +.hover\:sl-shadow-2xl:hover, .hover\:sl-shadow-xl:hover { + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.hover\:sl-shadow-2xl:hover { + --tw-shadow: var(--shadow-2xl) +} + +.focus\:sl-shadow-sm:focus { + --tw-shadow: var(--shadow-sm); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.focus\:sl-shadow-md:focus, .focus\:sl-shadow:focus { + --tw-shadow: var(--shadow-md) +} + +.focus\:sl-shadow-lg:focus, .focus\:sl-shadow-md:focus, .focus\:sl-shadow:focus { + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.focus\:sl-shadow-lg:focus { + --tw-shadow: var(--shadow-lg) +} + +.focus\:sl-shadow-xl:focus { + --tw-shadow: var(--shadow-xl) +} + +.focus\:sl-shadow-2xl:focus, .focus\:sl-shadow-xl:focus { + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.focus\:sl-shadow-2xl:focus { + --tw-shadow: var(--shadow-2xl) +} + +.sl-box-border { + box-sizing: border-box +} + +.sl-box-content { + box-sizing: content-box +} + +.sl-cursor-auto { + cursor: auto +} + +.sl-cursor { + cursor: default +} + +.sl-cursor-pointer { + cursor: pointer +} + +.sl-cursor-wait { + cursor: wait +} + +.sl-cursor-text { + cursor: text +} + +.sl-cursor-move { + cursor: move +} + +.sl-cursor-not-allowed { + cursor: not-allowed +} + +.sl-cursor-zoom-in { + cursor: zoom-in +} + +.sl-cursor-zoom-out { + cursor: zoom-out +} + +.sl-block { + display: block +} + +.sl-inline-block { + display: inline-block +} + +.sl-inline { + display: inline +} + +.sl-flex { + display: flex +} + +.sl-inline-flex { + display: inline-flex +} + +.sl-table { + display: table +} + +.sl-inline-table { + display: inline-table +} + +.sl-table-caption { + display: table-caption +} + +.sl-table-cell { + display: table-cell +} + +.sl-table-column { + display: table-column +} + +.sl-table-column-group { + display: table-column-group +} + +.sl-table-footer-group { + display: table-footer-group +} + +.sl-table-header-group { + display: table-header-group +} + +.sl-table-row-group { + display: table-row-group +} + +.sl-table-row { + display: table-row +} + +.sl-flow-root { + display: flow-root +} + +.sl-grid { + display: grid +} + +.sl-inline-grid { + display: inline-grid +} + +.sl-contents { + display: contents +} + +.sl-list-item { + display: list-item +} + +.sl-hidden { + display: none +} + +.sl-drop-shadow { + --tw-drop-shadow: drop-shadow(var(--drop-shadow-default1)) drop-shadow(var(--drop-shadow-default2)) +} + +.sl-filter { + --tw-blur: var(--tw-empty, /*!*/ /*!*/); + --tw-brightness: var(--tw-empty, /*!*/ /*!*/); + --tw-contrast: var(--tw-empty, /*!*/ /*!*/); + --tw-grayscale: var(--tw-empty, /*!*/ /*!*/); + --tw-hue-rotate: var(--tw-empty, /*!*/ /*!*/); + --tw-invert: var(--tw-empty, /*!*/ /*!*/); + --tw-saturate: var(--tw-empty, /*!*/ /*!*/); + --tw-sepia: var(--tw-empty, /*!*/ /*!*/); + --tw-drop-shadow: var(--tw-empty, /*!*/ /*!*/); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) +} + +.sl-filter-none { + filter: none +} + +.sl-flex-1 { + flex: 1 1 +} + +.sl-flex-auto { + flex: 1 1 auto +} + +.sl-flex-initial { + flex: 0 1 auto +} + +.sl-flex-none { + flex: none +} + +.sl-flex-row { + flex-direction: row +} + +.sl-flex-row-reverse { + flex-direction: row-reverse +} + +.sl-flex-col { + flex-direction: column +} + +.sl-flex-col-reverse { + flex-direction: column-reverse +} + +.sl-flex-grow-0 { + flex-grow: 0 +} + +.sl-flex-grow { + flex-grow: 1 +} + +.sl-flex-shrink-0 { + flex-shrink: 0 +} + +.sl-flex-shrink { + flex-shrink: 1 +} + +.sl-flex-wrap { + flex-wrap: wrap +} + +.sl-flex-wrap-reverse { + flex-wrap: wrap-reverse +} + +.sl-flex-nowrap { + flex-wrap: nowrap +} + +.sl-font-sans, .sl-font-ui { + font-family: var(--font-ui) +} + +.sl-font-prose { + font-family: var(--font-prose) +} + +.sl-font-mono { + font-family: var(--font-mono) +} + +.sl-text-2xs { + font-size: 9px +} + +.sl-text-xs { + font-size: 10px +} + +.sl-text-sm { + font-size: 11px +} + +.sl-text-base { + font-size: 12px +} + +.sl-text-lg { + font-size: 14px +} + +.sl-text-xl { + font-size: 16px +} + +.sl-text-2xl { + font-size: 20px +} + +.sl-text-3xl { + font-size: 24px +} + +.sl-text-4xl { + font-size: 28px +} + +.sl-text-5xl { + font-size: 36px +} + +.sl-text-6xl { + font-size: 44px +} + +.sl-text-paragraph-leading { + font-size: var(--fs-paragraph-leading) +} + +.sl-text-paragraph { + font-size: var(--fs-paragraph) +} + +.sl-text-paragraph-small { + font-size: var(--fs-paragraph-small) +} + +.sl-text-paragraph-tiny { + font-size: var(--fs-paragraph-tiny) +} + +.sl-antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +.sl-subpixel-antialiased { + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto +} + +.sl-italic { + font-style: italic +} + +.sl-not-italic { + font-style: normal +} + +.sl-font-light { + font-weight: 300 +} + +.sl-font-normal { + font-weight: 400 +} + +.sl-font-medium { + font-weight: 500 +} + +.sl-font-semibold { + font-weight: 600 +} + +.sl-font-bold { + font-weight: 700 +} + +.sl-h-0 { + height: 0 +} + +.sl-h-1 { + height: 4px +} + +.sl-h-2 { + height: 8px +} + +.sl-h-3 { + height: 12px +} + +.sl-h-4 { + height: 16px +} + +.sl-h-5 { + height: 20px +} + +.sl-h-6 { + height: 24px +} + +.sl-h-7 { + height: 28px +} + +.sl-h-8 { + height: 32px +} + +.sl-h-9 { + height: 36px +} + +.sl-h-10 { + height: 40px +} + +.sl-h-11 { + height: 44px +} + +.sl-h-12 { + height: 48px +} + +.sl-h-14 { + height: 56px +} + +.sl-h-16 { + height: 64px +} + +.sl-h-20 { + height: 80px +} + +.sl-h-24 { + height: 96px +} + +.sl-h-28 { + height: 112px +} + +.sl-h-32 { + height: 128px +} + +.sl-h-36 { + height: 144px +} + +.sl-h-40 { + height: 160px +} + +.sl-h-44 { + height: 176px +} + +.sl-h-48 { + height: 192px +} + +.sl-h-52 { + height: 208px +} + +.sl-h-56 { + height: 224px +} + +.sl-h-60 { + height: 240px +} + +.sl-h-64 { + height: 256px +} + +.sl-h-72 { + height: 288px +} + +.sl-h-80 { + height: 320px +} + +.sl-h-96 { + height: 384px +} + +.sl-h-auto { + height: auto +} + +.sl-h-px { + height: 1px +} + +.sl-h-0\.5 { + height: 2px +} + +.sl-h-1\.5 { + height: 6px +} + +.sl-h-2\.5 { + height: 10px +} + +.sl-h-3\.5 { + height: 14px +} + +.sl-h-4\.5 { + height: 18px +} + +.sl-h-xs { + height: 20px +} + +.sl-h-sm { + height: 24px +} + +.sl-h-md { + height: 32px +} + +.sl-h-lg { + height: 36px +} + +.sl-h-xl { + height: 44px +} + +.sl-h-2xl { + height: 52px +} + +.sl-h-3xl { + height: 60px +} + +.sl-h-full { + height: 100% +} + +.sl-h-screen { + height: 100vh +} + +.sl-inset-0 { + bottom: 0; + left: 0; + right: 0; + top: 0 +} + +.sl-inset-1 { + bottom: 4px; + left: 4px; + right: 4px; + top: 4px +} + +.sl-inset-2 { + bottom: 8px; + left: 8px; + right: 8px; + top: 8px +} + +.sl-inset-3 { + bottom: 12px; + left: 12px; + right: 12px; + top: 12px +} + +.sl-inset-4 { + bottom: 16px; + left: 16px; + right: 16px; + top: 16px +} + +.sl-inset-5 { + bottom: 20px; + left: 20px; + right: 20px; + top: 20px +} + +.sl-inset-6 { + bottom: 24px; + left: 24px; + right: 24px; + top: 24px +} + +.sl-inset-7 { + bottom: 28px; + left: 28px; + right: 28px; + top: 28px +} + +.sl-inset-8 { + bottom: 32px; + left: 32px; + right: 32px; + top: 32px +} + +.sl-inset-9 { + bottom: 36px; + left: 36px; + right: 36px; + top: 36px +} + +.sl-inset-10 { + bottom: 40px; + left: 40px; + right: 40px; + top: 40px +} + +.sl-inset-11 { + bottom: 44px; + left: 44px; + right: 44px; + top: 44px +} + +.sl-inset-12 { + bottom: 48px; + left: 48px; + right: 48px; + top: 48px +} + +.sl-inset-14 { + bottom: 56px; + left: 56px; + right: 56px; + top: 56px +} + +.sl-inset-16 { + bottom: 64px; + left: 64px; + right: 64px; + top: 64px +} + +.sl-inset-20 { + bottom: 80px; + left: 80px; + right: 80px; + top: 80px +} + +.sl-inset-24 { + bottom: 96px; + left: 96px; + right: 96px; + top: 96px +} + +.sl-inset-28 { + bottom: 112px; + left: 112px; + right: 112px; + top: 112px +} + +.sl-inset-32 { + bottom: 128px; + left: 128px; + right: 128px; + top: 128px +} + +.sl-inset-36 { + bottom: 144px; + left: 144px; + right: 144px; + top: 144px +} + +.sl-inset-40 { + bottom: 160px; + left: 160px; + right: 160px; + top: 160px +} + +.sl-inset-44 { + bottom: 176px; + left: 176px; + right: 176px; + top: 176px +} + +.sl-inset-48 { + bottom: 192px; + left: 192px; + right: 192px; + top: 192px +} + +.sl-inset-52 { + bottom: 208px; + left: 208px; + right: 208px; + top: 208px +} + +.sl-inset-56 { + bottom: 224px; + left: 224px; + right: 224px; + top: 224px +} + +.sl-inset-60 { + bottom: 240px; + left: 240px; + right: 240px; + top: 240px +} + +.sl-inset-64 { + bottom: 256px; + left: 256px; + right: 256px; + top: 256px +} + +.sl-inset-72 { + bottom: 288px; + left: 288px; + right: 288px; + top: 288px +} + +.sl-inset-80 { + bottom: 320px; + left: 320px; + right: 320px; + top: 320px +} + +.sl-inset-96 { + bottom: 384px; + left: 384px; + right: 384px; + top: 384px +} + +.sl-inset-auto { + bottom: auto; + left: auto; + right: auto; + top: auto +} + +.sl-inset-px { + bottom: 1px; + left: 1px; + right: 1px; + top: 1px +} + +.sl-inset-0\.5 { + bottom: 2px; + left: 2px; + right: 2px; + top: 2px +} + +.sl-inset-1\.5 { + bottom: 6px; + left: 6px; + right: 6px; + top: 6px +} + +.sl-inset-2\.5 { + bottom: 10px; + left: 10px; + right: 10px; + top: 10px +} + +.sl-inset-3\.5 { + bottom: 14px; + left: 14px; + right: 14px; + top: 14px +} + +.sl-inset-4\.5 { + bottom: 18px; + left: 18px; + right: 18px; + top: 18px +} + +.sl--inset-0 { + bottom: 0; + left: 0; + right: 0; + top: 0 +} + +.sl--inset-1 { + bottom: -4px; + left: -4px; + right: -4px; + top: -4px +} + +.sl--inset-2 { + bottom: -8px; + left: -8px; + right: -8px; + top: -8px +} + +.sl--inset-3 { + bottom: -12px; + left: -12px; + right: -12px; + top: -12px +} + +.sl--inset-4 { + bottom: -16px; + left: -16px; + right: -16px; + top: -16px +} + +.sl--inset-5 { + bottom: -20px; + left: -20px; + right: -20px; + top: -20px +} + +.sl--inset-6 { + bottom: -24px; + left: -24px; + right: -24px; + top: -24px +} + +.sl--inset-7 { + bottom: -28px; + left: -28px; + right: -28px; + top: -28px +} + +.sl--inset-8 { + bottom: -32px; + left: -32px; + right: -32px; + top: -32px +} + +.sl--inset-9 { + bottom: -36px; + left: -36px; + right: -36px; + top: -36px +} + +.sl--inset-10 { + bottom: -40px; + left: -40px; + right: -40px; + top: -40px +} + +.sl--inset-11 { + bottom: -44px; + left: -44px; + right: -44px; + top: -44px +} + +.sl--inset-12 { + bottom: -48px; + left: -48px; + right: -48px; + top: -48px +} + +.sl--inset-14 { + bottom: -56px; + left: -56px; + right: -56px; + top: -56px +} + +.sl--inset-16 { + bottom: -64px; + left: -64px; + right: -64px; + top: -64px +} + +.sl--inset-20 { + bottom: -80px; + left: -80px; + right: -80px; + top: -80px +} + +.sl--inset-24 { + bottom: -96px; + left: -96px; + right: -96px; + top: -96px +} + +.sl--inset-28 { + bottom: -112px; + left: -112px; + right: -112px; + top: -112px +} + +.sl--inset-32 { + bottom: -128px; + left: -128px; + right: -128px; + top: -128px +} + +.sl--inset-36 { + bottom: -144px; + left: -144px; + right: -144px; + top: -144px +} + +.sl--inset-40 { + bottom: -160px; + left: -160px; + right: -160px; + top: -160px +} + +.sl--inset-44 { + bottom: -176px; + left: -176px; + right: -176px; + top: -176px +} + +.sl--inset-48 { + bottom: -192px; + left: -192px; + right: -192px; + top: -192px +} + +.sl--inset-52 { + bottom: -208px; + left: -208px; + right: -208px; + top: -208px +} + +.sl--inset-56 { + bottom: -224px; + left: -224px; + right: -224px; + top: -224px +} + +.sl--inset-60 { + bottom: -240px; + left: -240px; + right: -240px; + top: -240px +} + +.sl--inset-64 { + bottom: -256px; + left: -256px; + right: -256px; + top: -256px +} + +.sl--inset-72 { + bottom: -288px; + left: -288px; + right: -288px; + top: -288px +} + +.sl--inset-80 { + bottom: -320px; + left: -320px; + right: -320px; + top: -320px +} + +.sl--inset-96 { + bottom: -384px; + left: -384px; + right: -384px; + top: -384px +} + +.sl--inset-px { + bottom: -1px; + left: -1px; + right: -1px; + top: -1px +} + +.sl--inset-0\.5 { + bottom: -2px; + left: -2px; + right: -2px; + top: -2px +} + +.sl--inset-1\.5 { + bottom: -6px; + left: -6px; + right: -6px; + top: -6px +} + +.sl--inset-2\.5 { + bottom: -10px; + left: -10px; + right: -10px; + top: -10px +} + +.sl--inset-3\.5 { + bottom: -14px; + left: -14px; + right: -14px; + top: -14px +} + +.sl--inset-4\.5 { + bottom: -18px; + left: -18px; + right: -18px; + top: -18px +} + +.sl-inset-y-0 { + bottom: 0; + top: 0 +} + +.sl-inset-x-0 { + left: 0; + right: 0 +} + +.sl-inset-y-1 { + bottom: 4px; + top: 4px +} + +.sl-inset-x-1 { + left: 4px; + right: 4px +} + +.sl-inset-y-2 { + bottom: 8px; + top: 8px +} + +.sl-inset-x-2 { + left: 8px; + right: 8px +} + +.sl-inset-y-3 { + bottom: 12px; + top: 12px +} + +.sl-inset-x-3 { + left: 12px; + right: 12px +} + +.sl-inset-y-4 { + bottom: 16px; + top: 16px +} + +.sl-inset-x-4 { + left: 16px; + right: 16px +} + +.sl-inset-y-5 { + bottom: 20px; + top: 20px +} + +.sl-inset-x-5 { + left: 20px; + right: 20px +} + +.sl-inset-y-6 { + bottom: 24px; + top: 24px +} + +.sl-inset-x-6 { + left: 24px; + right: 24px +} + +.sl-inset-y-7 { + bottom: 28px; + top: 28px +} + +.sl-inset-x-7 { + left: 28px; + right: 28px +} + +.sl-inset-y-8 { + bottom: 32px; + top: 32px +} + +.sl-inset-x-8 { + left: 32px; + right: 32px +} + +.sl-inset-y-9 { + bottom: 36px; + top: 36px +} + +.sl-inset-x-9 { + left: 36px; + right: 36px +} + +.sl-inset-y-10 { + bottom: 40px; + top: 40px +} + +.sl-inset-x-10 { + left: 40px; + right: 40px +} + +.sl-inset-y-11 { + bottom: 44px; + top: 44px +} + +.sl-inset-x-11 { + left: 44px; + right: 44px +} + +.sl-inset-y-12 { + bottom: 48px; + top: 48px +} + +.sl-inset-x-12 { + left: 48px; + right: 48px +} + +.sl-inset-y-14 { + bottom: 56px; + top: 56px +} + +.sl-inset-x-14 { + left: 56px; + right: 56px +} + +.sl-inset-y-16 { + bottom: 64px; + top: 64px +} + +.sl-inset-x-16 { + left: 64px; + right: 64px +} + +.sl-inset-y-20 { + bottom: 80px; + top: 80px +} + +.sl-inset-x-20 { + left: 80px; + right: 80px +} + +.sl-inset-y-24 { + bottom: 96px; + top: 96px +} + +.sl-inset-x-24 { + left: 96px; + right: 96px +} + +.sl-inset-y-28 { + bottom: 112px; + top: 112px +} + +.sl-inset-x-28 { + left: 112px; + right: 112px +} + +.sl-inset-y-32 { + bottom: 128px; + top: 128px +} + +.sl-inset-x-32 { + left: 128px; + right: 128px +} + +.sl-inset-y-36 { + bottom: 144px; + top: 144px +} + +.sl-inset-x-36 { + left: 144px; + right: 144px +} + +.sl-inset-y-40 { + bottom: 160px; + top: 160px +} + +.sl-inset-x-40 { + left: 160px; + right: 160px +} + +.sl-inset-y-44 { + bottom: 176px; + top: 176px +} + +.sl-inset-x-44 { + left: 176px; + right: 176px +} + +.sl-inset-y-48 { + bottom: 192px; + top: 192px +} + +.sl-inset-x-48 { + left: 192px; + right: 192px +} + +.sl-inset-y-52 { + bottom: 208px; + top: 208px +} + +.sl-inset-x-52 { + left: 208px; + right: 208px +} + +.sl-inset-y-56 { + bottom: 224px; + top: 224px +} + +.sl-inset-x-56 { + left: 224px; + right: 224px +} + +.sl-inset-y-60 { + bottom: 240px; + top: 240px +} + +.sl-inset-x-60 { + left: 240px; + right: 240px +} + +.sl-inset-y-64 { + bottom: 256px; + top: 256px +} + +.sl-inset-x-64 { + left: 256px; + right: 256px +} + +.sl-inset-y-72 { + bottom: 288px; + top: 288px +} + +.sl-inset-x-72 { + left: 288px; + right: 288px +} + +.sl-inset-y-80 { + bottom: 320px; + top: 320px +} + +.sl-inset-x-80 { + left: 320px; + right: 320px +} + +.sl-inset-y-96 { + bottom: 384px; + top: 384px +} + +.sl-inset-x-96 { + left: 384px; + right: 384px +} + +.sl-inset-y-auto { + bottom: auto; + top: auto +} + +.sl-inset-x-auto { + left: auto; + right: auto +} + +.sl-inset-y-px { + bottom: 1px; + top: 1px +} + +.sl-inset-x-px { + left: 1px; + right: 1px +} + +.sl-inset-y-0\.5 { + bottom: 2px; + top: 2px +} + +.sl-inset-x-0\.5 { + left: 2px; + right: 2px +} + +.sl-inset-y-1\.5 { + bottom: 6px; + top: 6px +} + +.sl-inset-x-1\.5 { + left: 6px; + right: 6px +} + +.sl-inset-y-2\.5 { + bottom: 10px; + top: 10px +} + +.sl-inset-x-2\.5 { + left: 10px; + right: 10px +} + +.sl-inset-y-3\.5 { + bottom: 14px; + top: 14px +} + +.sl-inset-x-3\.5 { + left: 14px; + right: 14px +} + +.sl-inset-y-4\.5 { + bottom: 18px; + top: 18px +} + +.sl-inset-x-4\.5 { + left: 18px; + right: 18px +} + +.sl--inset-y-0 { + bottom: 0; + top: 0 +} + +.sl--inset-x-0 { + left: 0; + right: 0 +} + +.sl--inset-y-1 { + bottom: -4px; + top: -4px +} + +.sl--inset-x-1 { + left: -4px; + right: -4px +} + +.sl--inset-y-2 { + bottom: -8px; + top: -8px +} + +.sl--inset-x-2 { + left: -8px; + right: -8px +} + +.sl--inset-y-3 { + bottom: -12px; + top: -12px +} + +.sl--inset-x-3 { + left: -12px; + right: -12px +} + +.sl--inset-y-4 { + bottom: -16px; + top: -16px +} + +.sl--inset-x-4 { + left: -16px; + right: -16px +} + +.sl--inset-y-5 { + bottom: -20px; + top: -20px +} + +.sl--inset-x-5 { + left: -20px; + right: -20px +} + +.sl--inset-y-6 { + bottom: -24px; + top: -24px +} + +.sl--inset-x-6 { + left: -24px; + right: -24px +} + +.sl--inset-y-7 { + bottom: -28px; + top: -28px +} + +.sl--inset-x-7 { + left: -28px; + right: -28px +} + +.sl--inset-y-8 { + bottom: -32px; + top: -32px +} + +.sl--inset-x-8 { + left: -32px; + right: -32px +} + +.sl--inset-y-9 { + bottom: -36px; + top: -36px +} + +.sl--inset-x-9 { + left: -36px; + right: -36px +} + +.sl--inset-y-10 { + bottom: -40px; + top: -40px +} + +.sl--inset-x-10 { + left: -40px; + right: -40px +} + +.sl--inset-y-11 { + bottom: -44px; + top: -44px +} + +.sl--inset-x-11 { + left: -44px; + right: -44px +} + +.sl--inset-y-12 { + bottom: -48px; + top: -48px +} + +.sl--inset-x-12 { + left: -48px; + right: -48px +} + +.sl--inset-y-14 { + bottom: -56px; + top: -56px +} + +.sl--inset-x-14 { + left: -56px; + right: -56px +} + +.sl--inset-y-16 { + bottom: -64px; + top: -64px +} + +.sl--inset-x-16 { + left: -64px; + right: -64px +} + +.sl--inset-y-20 { + bottom: -80px; + top: -80px +} + +.sl--inset-x-20 { + left: -80px; + right: -80px +} + +.sl--inset-y-24 { + bottom: -96px; + top: -96px +} + +.sl--inset-x-24 { + left: -96px; + right: -96px +} + +.sl--inset-y-28 { + bottom: -112px; + top: -112px +} + +.sl--inset-x-28 { + left: -112px; + right: -112px +} + +.sl--inset-y-32 { + bottom: -128px; + top: -128px +} + +.sl--inset-x-32 { + left: -128px; + right: -128px +} + +.sl--inset-y-36 { + bottom: -144px; + top: -144px +} + +.sl--inset-x-36 { + left: -144px; + right: -144px +} + +.sl--inset-y-40 { + bottom: -160px; + top: -160px +} + +.sl--inset-x-40 { + left: -160px; + right: -160px +} + +.sl--inset-y-44 { + bottom: -176px; + top: -176px +} + +.sl--inset-x-44 { + left: -176px; + right: -176px +} + +.sl--inset-y-48 { + bottom: -192px; + top: -192px +} + +.sl--inset-x-48 { + left: -192px; + right: -192px +} + +.sl--inset-y-52 { + bottom: -208px; + top: -208px +} + +.sl--inset-x-52 { + left: -208px; + right: -208px +} + +.sl--inset-y-56 { + bottom: -224px; + top: -224px +} + +.sl--inset-x-56 { + left: -224px; + right: -224px +} + +.sl--inset-y-60 { + bottom: -240px; + top: -240px +} + +.sl--inset-x-60 { + left: -240px; + right: -240px +} + +.sl--inset-y-64 { + bottom: -256px; + top: -256px +} + +.sl--inset-x-64 { + left: -256px; + right: -256px +} + +.sl--inset-y-72 { + bottom: -288px; + top: -288px +} + +.sl--inset-x-72 { + left: -288px; + right: -288px +} + +.sl--inset-y-80 { + bottom: -320px; + top: -320px +} + +.sl--inset-x-80 { + left: -320px; + right: -320px +} + +.sl--inset-y-96 { + bottom: -384px; + top: -384px +} + +.sl--inset-x-96 { + left: -384px; + right: -384px +} + +.sl--inset-y-px { + bottom: -1px; + top: -1px +} + +.sl--inset-x-px { + left: -1px; + right: -1px +} + +.sl--inset-y-0\.5 { + bottom: -2px; + top: -2px +} + +.sl--inset-x-0\.5 { + left: -2px; + right: -2px +} + +.sl--inset-y-1\.5 { + bottom: -6px; + top: -6px +} + +.sl--inset-x-1\.5 { + left: -6px; + right: -6px +} + +.sl--inset-y-2\.5 { + bottom: -10px; + top: -10px +} + +.sl--inset-x-2\.5 { + left: -10px; + right: -10px +} + +.sl--inset-y-3\.5 { + bottom: -14px; + top: -14px +} + +.sl--inset-x-3\.5 { + left: -14px; + right: -14px +} + +.sl--inset-y-4\.5 { + bottom: -18px; + top: -18px +} + +.sl--inset-x-4\.5 { + left: -18px; + right: -18px +} + +.sl-top-0 { + top: 0 +} + +.sl-right-0 { + right: 0 +} + +.sl-bottom-0 { + bottom: 0 +} + +.sl-left-0 { + left: 0 +} + +.sl-top-1 { + top: 4px +} + +.sl-right-1 { + right: 4px +} + +.sl-bottom-1 { + bottom: 4px +} + +.sl-left-1 { + left: 4px +} + +.sl-top-2 { + top: 8px +} + +.sl-right-2 { + right: 8px +} + +.sl-bottom-2 { + bottom: 8px +} + +.sl-left-2 { + left: 8px +} + +.sl-top-3 { + top: 12px +} + +.sl-right-3 { + right: 12px +} + +.sl-bottom-3 { + bottom: 12px +} + +.sl-left-3 { + left: 12px +} + +.sl-top-4 { + top: 16px +} + +.sl-right-4 { + right: 16px +} + +.sl-bottom-4 { + bottom: 16px +} + +.sl-left-4 { + left: 16px +} + +.sl-top-5 { + top: 20px +} + +.sl-right-5 { + right: 20px +} + +.sl-bottom-5 { + bottom: 20px +} + +.sl-left-5 { + left: 20px +} + +.sl-top-6 { + top: 24px +} + +.sl-right-6 { + right: 24px +} + +.sl-bottom-6 { + bottom: 24px +} + +.sl-left-6 { + left: 24px +} + +.sl-top-7 { + top: 28px +} + +.sl-right-7 { + right: 28px +} + +.sl-bottom-7 { + bottom: 28px +} + +.sl-left-7 { + left: 28px +} + +.sl-top-8 { + top: 32px +} + +.sl-right-8 { + right: 32px +} + +.sl-bottom-8 { + bottom: 32px +} + +.sl-left-8 { + left: 32px +} + +.sl-top-9 { + top: 36px +} + +.sl-right-9 { + right: 36px +} + +.sl-bottom-9 { + bottom: 36px +} + +.sl-left-9 { + left: 36px +} + +.sl-top-10 { + top: 40px +} + +.sl-right-10 { + right: 40px +} + +.sl-bottom-10 { + bottom: 40px +} + +.sl-left-10 { + left: 40px +} + +.sl-top-11 { + top: 44px +} + +.sl-right-11 { + right: 44px +} + +.sl-bottom-11 { + bottom: 44px +} + +.sl-left-11 { + left: 44px +} + +.sl-top-12 { + top: 48px +} + +.sl-right-12 { + right: 48px +} + +.sl-bottom-12 { + bottom: 48px +} + +.sl-left-12 { + left: 48px +} + +.sl-top-14 { + top: 56px +} + +.sl-right-14 { + right: 56px +} + +.sl-bottom-14 { + bottom: 56px +} + +.sl-left-14 { + left: 56px +} + +.sl-top-16 { + top: 64px +} + +.sl-right-16 { + right: 64px +} + +.sl-bottom-16 { + bottom: 64px +} + +.sl-left-16 { + left: 64px +} + +.sl-top-20 { + top: 80px +} + +.sl-right-20 { + right: 80px +} + +.sl-bottom-20 { + bottom: 80px +} + +.sl-left-20 { + left: 80px +} + +.sl-top-24 { + top: 96px +} + +.sl-right-24 { + right: 96px +} + +.sl-bottom-24 { + bottom: 96px +} + +.sl-left-24 { + left: 96px +} + +.sl-top-28 { + top: 112px +} + +.sl-right-28 { + right: 112px +} + +.sl-bottom-28 { + bottom: 112px +} + +.sl-left-28 { + left: 112px +} + +.sl-top-32 { + top: 128px +} + +.sl-right-32 { + right: 128px +} + +.sl-bottom-32 { + bottom: 128px +} + +.sl-left-32 { + left: 128px +} + +.sl-top-36 { + top: 144px +} + +.sl-right-36 { + right: 144px +} + +.sl-bottom-36 { + bottom: 144px +} + +.sl-left-36 { + left: 144px +} + +.sl-top-40 { + top: 160px +} + +.sl-right-40 { + right: 160px +} + +.sl-bottom-40 { + bottom: 160px +} + +.sl-left-40 { + left: 160px +} + +.sl-top-44 { + top: 176px +} + +.sl-right-44 { + right: 176px +} + +.sl-bottom-44 { + bottom: 176px +} + +.sl-left-44 { + left: 176px +} + +.sl-top-48 { + top: 192px +} + +.sl-right-48 { + right: 192px +} + +.sl-bottom-48 { + bottom: 192px +} + +.sl-left-48 { + left: 192px +} + +.sl-top-52 { + top: 208px +} + +.sl-right-52 { + right: 208px +} + +.sl-bottom-52 { + bottom: 208px +} + +.sl-left-52 { + left: 208px +} + +.sl-top-56 { + top: 224px +} + +.sl-right-56 { + right: 224px +} + +.sl-bottom-56 { + bottom: 224px +} + +.sl-left-56 { + left: 224px +} + +.sl-top-60 { + top: 240px +} + +.sl-right-60 { + right: 240px +} + +.sl-bottom-60 { + bottom: 240px +} + +.sl-left-60 { + left: 240px +} + +.sl-top-64 { + top: 256px +} + +.sl-right-64 { + right: 256px +} + +.sl-bottom-64 { + bottom: 256px +} + +.sl-left-64 { + left: 256px +} + +.sl-top-72 { + top: 288px +} + +.sl-right-72 { + right: 288px +} + +.sl-bottom-72 { + bottom: 288px +} + +.sl-left-72 { + left: 288px +} + +.sl-top-80 { + top: 320px +} + +.sl-right-80 { + right: 320px +} + +.sl-bottom-80 { + bottom: 320px +} + +.sl-left-80 { + left: 320px +} + +.sl-top-96 { + top: 384px +} + +.sl-right-96 { + right: 384px +} + +.sl-bottom-96 { + bottom: 384px +} + +.sl-left-96 { + left: 384px +} + +.sl-top-auto { + top: auto +} + +.sl-right-auto { + right: auto +} + +.sl-bottom-auto { + bottom: auto +} + +.sl-left-auto { + left: auto +} + +.sl-top-px { + top: 1px +} + +.sl-right-px { + right: 1px +} + +.sl-bottom-px { + bottom: 1px +} + +.sl-left-px { + left: 1px +} + +.sl-top-0\.5 { + top: 2px +} + +.sl-right-0\.5 { + right: 2px +} + +.sl-bottom-0\.5 { + bottom: 2px +} + +.sl-left-0\.5 { + left: 2px +} + +.sl-top-1\.5 { + top: 6px +} + +.sl-right-1\.5 { + right: 6px +} + +.sl-bottom-1\.5 { + bottom: 6px +} + +.sl-left-1\.5 { + left: 6px +} + +.sl-top-2\.5 { + top: 10px +} + +.sl-right-2\.5 { + right: 10px +} + +.sl-bottom-2\.5 { + bottom: 10px +} + +.sl-left-2\.5 { + left: 10px +} + +.sl-top-3\.5 { + top: 14px +} + +.sl-right-3\.5 { + right: 14px +} + +.sl-bottom-3\.5 { + bottom: 14px +} + +.sl-left-3\.5 { + left: 14px +} + +.sl-top-4\.5 { + top: 18px +} + +.sl-right-4\.5 { + right: 18px +} + +.sl-bottom-4\.5 { + bottom: 18px +} + +.sl-left-4\.5 { + left: 18px +} + +.sl--top-0 { + top: 0 +} + +.sl--right-0 { + right: 0 +} + +.sl--bottom-0 { + bottom: 0 +} + +.sl--left-0 { + left: 0 +} + +.sl--top-1 { + top: -4px +} + +.sl--right-1 { + right: -4px +} + +.sl--bottom-1 { + bottom: -4px +} + +.sl--left-1 { + left: -4px +} + +.sl--top-2 { + top: -8px +} + +.sl--right-2 { + right: -8px +} + +.sl--bottom-2 { + bottom: -8px +} + +.sl--left-2 { + left: -8px +} + +.sl--top-3 { + top: -12px +} + +.sl--right-3 { + right: -12px +} + +.sl--bottom-3 { + bottom: -12px +} + +.sl--left-3 { + left: -12px +} + +.sl--top-4 { + top: -16px +} + +.sl--right-4 { + right: -16px +} + +.sl--bottom-4 { + bottom: -16px +} + +.sl--left-4 { + left: -16px +} + +.sl--top-5 { + top: -20px +} + +.sl--right-5 { + right: -20px +} + +.sl--bottom-5 { + bottom: -20px +} + +.sl--left-5 { + left: -20px +} + +.sl--top-6 { + top: -24px +} + +.sl--right-6 { + right: -24px +} + +.sl--bottom-6 { + bottom: -24px +} + +.sl--left-6 { + left: -24px +} + +.sl--top-7 { + top: -28px +} + +.sl--right-7 { + right: -28px +} + +.sl--bottom-7 { + bottom: -28px +} + +.sl--left-7 { + left: -28px +} + +.sl--top-8 { + top: -32px +} + +.sl--right-8 { + right: -32px +} + +.sl--bottom-8 { + bottom: -32px +} + +.sl--left-8 { + left: -32px +} + +.sl--top-9 { + top: -36px +} + +.sl--right-9 { + right: -36px +} + +.sl--bottom-9 { + bottom: -36px +} + +.sl--left-9 { + left: -36px +} + +.sl--top-10 { + top: -40px +} + +.sl--right-10 { + right: -40px +} + +.sl--bottom-10 { + bottom: -40px +} + +.sl--left-10 { + left: -40px +} + +.sl--top-11 { + top: -44px +} + +.sl--right-11 { + right: -44px +} + +.sl--bottom-11 { + bottom: -44px +} + +.sl--left-11 { + left: -44px +} + +.sl--top-12 { + top: -48px +} + +.sl--right-12 { + right: -48px +} + +.sl--bottom-12 { + bottom: -48px +} + +.sl--left-12 { + left: -48px +} + +.sl--top-14 { + top: -56px +} + +.sl--right-14 { + right: -56px +} + +.sl--bottom-14 { + bottom: -56px +} + +.sl--left-14 { + left: -56px +} + +.sl--top-16 { + top: -64px +} + +.sl--right-16 { + right: -64px +} + +.sl--bottom-16 { + bottom: -64px +} + +.sl--left-16 { + left: -64px +} + +.sl--top-20 { + top: -80px +} + +.sl--right-20 { + right: -80px +} + +.sl--bottom-20 { + bottom: -80px +} + +.sl--left-20 { + left: -80px +} + +.sl--top-24 { + top: -96px +} + +.sl--right-24 { + right: -96px +} + +.sl--bottom-24 { + bottom: -96px +} + +.sl--left-24 { + left: -96px +} + +.sl--top-28 { + top: -112px +} + +.sl--right-28 { + right: -112px +} + +.sl--bottom-28 { + bottom: -112px +} + +.sl--left-28 { + left: -112px +} + +.sl--top-32 { + top: -128px +} + +.sl--right-32 { + right: -128px +} + +.sl--bottom-32 { + bottom: -128px +} + +.sl--left-32 { + left: -128px +} + +.sl--top-36 { + top: -144px +} + +.sl--right-36 { + right: -144px +} + +.sl--bottom-36 { + bottom: -144px +} + +.sl--left-36 { + left: -144px +} + +.sl--top-40 { + top: -160px +} + +.sl--right-40 { + right: -160px +} + +.sl--bottom-40 { + bottom: -160px +} + +.sl--left-40 { + left: -160px +} + +.sl--top-44 { + top: -176px +} + +.sl--right-44 { + right: -176px +} + +.sl--bottom-44 { + bottom: -176px +} + +.sl--left-44 { + left: -176px +} + +.sl--top-48 { + top: -192px +} + +.sl--right-48 { + right: -192px +} + +.sl--bottom-48 { + bottom: -192px +} + +.sl--left-48 { + left: -192px +} + +.sl--top-52 { + top: -208px +} + +.sl--right-52 { + right: -208px +} + +.sl--bottom-52 { + bottom: -208px +} + +.sl--left-52 { + left: -208px +} + +.sl--top-56 { + top: -224px +} + +.sl--right-56 { + right: -224px +} + +.sl--bottom-56 { + bottom: -224px +} + +.sl--left-56 { + left: -224px +} + +.sl--top-60 { + top: -240px +} + +.sl--right-60 { + right: -240px +} + +.sl--bottom-60 { + bottom: -240px +} + +.sl--left-60 { + left: -240px +} + +.sl--top-64 { + top: -256px +} + +.sl--right-64 { + right: -256px +} + +.sl--bottom-64 { + bottom: -256px +} + +.sl--left-64 { + left: -256px +} + +.sl--top-72 { + top: -288px +} + +.sl--right-72 { + right: -288px +} + +.sl--bottom-72 { + bottom: -288px +} + +.sl--left-72 { + left: -288px +} + +.sl--top-80 { + top: -320px +} + +.sl--right-80 { + right: -320px +} + +.sl--bottom-80 { + bottom: -320px +} + +.sl--left-80 { + left: -320px +} + +.sl--top-96 { + top: -384px +} + +.sl--right-96 { + right: -384px +} + +.sl--bottom-96 { + bottom: -384px +} + +.sl--left-96 { + left: -384px +} + +.sl--top-px { + top: -1px +} + +.sl--right-px { + right: -1px +} + +.sl--bottom-px { + bottom: -1px +} + +.sl--left-px { + left: -1px +} + +.sl--top-0\.5 { + top: -2px +} + +.sl--right-0\.5 { + right: -2px +} + +.sl--bottom-0\.5 { + bottom: -2px +} + +.sl--left-0\.5 { + left: -2px +} + +.sl--top-1\.5 { + top: -6px +} + +.sl--right-1\.5 { + right: -6px +} + +.sl--bottom-1\.5 { + bottom: -6px +} + +.sl--left-1\.5 { + left: -6px +} + +.sl--top-2\.5 { + top: -10px +} + +.sl--right-2\.5 { + right: -10px +} + +.sl--bottom-2\.5 { + bottom: -10px +} + +.sl--left-2\.5 { + left: -10px +} + +.sl--top-3\.5 { + top: -14px +} + +.sl--right-3\.5 { + right: -14px +} + +.sl--bottom-3\.5 { + bottom: -14px +} + +.sl--left-3\.5 { + left: -14px +} + +.sl--top-4\.5 { + top: -18px +} + +.sl--right-4\.5 { + right: -18px +} + +.sl--bottom-4\.5 { + bottom: -18px +} + +.sl--left-4\.5 { + left: -18px +} + +.sl-justify-start { + justify-content: flex-start +} + +.sl-justify-end { + justify-content: flex-end +} + +.sl-justify-center { + justify-content: center +} + +.sl-justify-between { + justify-content: space-between +} + +.sl-justify-around { + justify-content: space-around +} + +.sl-justify-evenly { + justify-content: space-evenly +} + +.sl-justify-items-start { + justify-items: start +} + +.sl-justify-items-end { + justify-items: end +} + +.sl-justify-items-center { + justify-items: center +} + +.sl-justify-items-stretch { + justify-items: stretch +} + +.sl-justify-self-auto { + justify-self: auto +} + +.sl-justify-self-start { + justify-self: start +} + +.sl-justify-self-end { + justify-self: end +} + +.sl-justify-self-center { + justify-self: center +} + +.sl-justify-self-stretch { + justify-self: stretch +} + +.sl-tracking-tight { + letter-spacing: -.025em +} + +.sl-tracking-normal { + letter-spacing: 0 +} + +.sl-tracking-wide { + letter-spacing: .025em +} + +.sl-leading-none { + line-height: 1 +} + +.sl-leading-tight { + line-height: 1.2 +} + +.sl-leading-snug { + line-height: 1.375 +} + +.sl-leading-normal { + line-height: 1.5 +} + +.sl-leading-relaxed { + line-height: 1.625 +} + +.sl-leading-loose { + line-height: 2 +} + +.sl-leading-paragraph-leading { + line-height: var(--lh-paragraph-leading) +} + +.sl-leading-paragraph { + line-height: var(--lh-paragraph) +} + +.sl-leading-paragraph-small { + line-height: var(--lh-paragraph-small) +} + +.sl-leading-paragraph-tiny { + line-height: var(--lh-paragraph-tiny) +} + +.sl-m-0 { + margin: 0 +} + +.sl-m-1 { + margin: 4px +} + +.sl-m-2 { + margin: 8px +} + +.sl-m-3 { + margin: 12px +} + +.sl-m-4 { + margin: 16px +} + +.sl-m-5 { + margin: 20px +} + +.sl-m-6 { + margin: 24px +} + +.sl-m-7 { + margin: 28px +} + +.sl-m-8 { + margin: 32px +} + +.sl-m-9 { + margin: 36px +} + +.sl-m-10 { + margin: 40px +} + +.sl-m-11 { + margin: 44px +} + +.sl-m-12 { + margin: 48px +} + +.sl-m-14 { + margin: 56px +} + +.sl-m-16 { + margin: 64px +} + +.sl-m-20 { + margin: 80px +} + +.sl-m-24 { + margin: 96px +} + +.sl-m-28 { + margin: 112px +} + +.sl-m-32 { + margin: 128px +} + +.sl-m-36 { + margin: 144px +} + +.sl-m-40 { + margin: 160px +} + +.sl-m-44 { + margin: 176px +} + +.sl-m-48 { + margin: 192px +} + +.sl-m-52 { + margin: 208px +} + +.sl-m-56 { + margin: 224px +} + +.sl-m-60 { + margin: 240px +} + +.sl-m-64 { + margin: 256px +} + +.sl-m-72 { + margin: 288px +} + +.sl-m-80 { + margin: 320px +} + +.sl-m-96 { + margin: 384px +} + +.sl-m-auto { + margin: auto +} + +.sl-m-px { + margin: 1px +} + +.sl-m-0\.5 { + margin: 2px +} + +.sl-m-1\.5 { + margin: 6px +} + +.sl-m-2\.5 { + margin: 10px +} + +.sl-m-3\.5 { + margin: 14px +} + +.sl-m-4\.5 { + margin: 18px +} + +.sl--m-0 { + margin: 0 +} + +.sl--m-1 { + margin: -4px +} + +.sl--m-2 { + margin: -8px +} + +.sl--m-3 { + margin: -12px +} + +.sl--m-4 { + margin: -16px +} + +.sl--m-5 { + margin: -20px +} + +.sl--m-6 { + margin: -24px +} + +.sl--m-7 { + margin: -28px +} + +.sl--m-8 { + margin: -32px +} + +.sl--m-9 { + margin: -36px +} + +.sl--m-10 { + margin: -40px +} + +.sl--m-11 { + margin: -44px +} + +.sl--m-12 { + margin: -48px +} + +.sl--m-14 { + margin: -56px +} + +.sl--m-16 { + margin: -64px +} + +.sl--m-20 { + margin: -80px +} + +.sl--m-24 { + margin: -96px +} + +.sl--m-28 { + margin: -112px +} + +.sl--m-32 { + margin: -128px +} + +.sl--m-36 { + margin: -144px +} + +.sl--m-40 { + margin: -160px +} + +.sl--m-44 { + margin: -176px +} + +.sl--m-48 { + margin: -192px +} + +.sl--m-52 { + margin: -208px +} + +.sl--m-56 { + margin: -224px +} + +.sl--m-60 { + margin: -240px +} + +.sl--m-64 { + margin: -256px +} + +.sl--m-72 { + margin: -288px +} + +.sl--m-80 { + margin: -320px +} + +.sl--m-96 { + margin: -384px +} + +.sl--m-px { + margin: -1px +} + +.sl--m-0\.5 { + margin: -2px +} + +.sl--m-1\.5 { + margin: -6px +} + +.sl--m-2\.5 { + margin: -10px +} + +.sl--m-3\.5 { + margin: -14px +} + +.sl--m-4\.5 { + margin: -18px +} + +.sl-my-0 { + margin-bottom: 0; + margin-top: 0 +} + +.sl-mx-0 { + margin-left: 0; + margin-right: 0 +} + +.sl-my-1 { + margin-bottom: 4px; + margin-top: 4px +} + +.sl-mx-1 { + margin-left: 4px; + margin-right: 4px +} + +.sl-my-2 { + margin-bottom: 8px; + margin-top: 8px +} + +.sl-mx-2 { + margin-left: 8px; + margin-right: 8px +} + +.sl-my-3 { + margin-bottom: 12px; + margin-top: 12px +} + +.sl-mx-3 { + margin-left: 12px; + margin-right: 12px +} + +.sl-my-4 { + margin-bottom: 16px; + margin-top: 16px +} + +.sl-mx-4 { + margin-left: 16px; + margin-right: 16px +} + +.sl-my-5 { + margin-bottom: 20px; + margin-top: 20px +} + +.sl-mx-5 { + margin-left: 20px; + margin-right: 20px +} + +.sl-my-6 { + margin-bottom: 24px; + margin-top: 24px +} + +.sl-mx-6 { + margin-left: 24px; + margin-right: 24px +} + +.sl-my-7 { + margin-bottom: 28px; + margin-top: 28px +} + +.sl-mx-7 { + margin-left: 28px; + margin-right: 28px +} + +.sl-my-8 { + margin-bottom: 32px; + margin-top: 32px +} + +.sl-mx-8 { + margin-left: 32px; + margin-right: 32px +} + +.sl-my-9 { + margin-bottom: 36px; + margin-top: 36px +} + +.sl-mx-9 { + margin-left: 36px; + margin-right: 36px +} + +.sl-my-10 { + margin-bottom: 40px; + margin-top: 40px +} + +.sl-mx-10 { + margin-left: 40px; + margin-right: 40px +} + +.sl-my-11 { + margin-bottom: 44px; + margin-top: 44px +} + +.sl-mx-11 { + margin-left: 44px; + margin-right: 44px +} + +.sl-my-12 { + margin-bottom: 48px; + margin-top: 48px +} + +.sl-mx-12 { + margin-left: 48px; + margin-right: 48px +} + +.sl-my-14 { + margin-bottom: 56px; + margin-top: 56px +} + +.sl-mx-14 { + margin-left: 56px; + margin-right: 56px +} + +.sl-my-16 { + margin-bottom: 64px; + margin-top: 64px +} + +.sl-mx-16 { + margin-left: 64px; + margin-right: 64px +} + +.sl-my-20 { + margin-bottom: 80px; + margin-top: 80px +} + +.sl-mx-20 { + margin-left: 80px; + margin-right: 80px +} + +.sl-my-24 { + margin-bottom: 96px; + margin-top: 96px +} + +.sl-mx-24 { + margin-left: 96px; + margin-right: 96px +} + +.sl-my-28 { + margin-bottom: 112px; + margin-top: 112px +} + +.sl-mx-28 { + margin-left: 112px; + margin-right: 112px +} + +.sl-my-32 { + margin-bottom: 128px; + margin-top: 128px +} + +.sl-mx-32 { + margin-left: 128px; + margin-right: 128px +} + +.sl-my-36 { + margin-bottom: 144px; + margin-top: 144px +} + +.sl-mx-36 { + margin-left: 144px; + margin-right: 144px +} + +.sl-my-40 { + margin-bottom: 160px; + margin-top: 160px +} + +.sl-mx-40 { + margin-left: 160px; + margin-right: 160px +} + +.sl-my-44 { + margin-bottom: 176px; + margin-top: 176px +} + +.sl-mx-44 { + margin-left: 176px; + margin-right: 176px +} + +.sl-my-48 { + margin-bottom: 192px; + margin-top: 192px +} + +.sl-mx-48 { + margin-left: 192px; + margin-right: 192px +} + +.sl-my-52 { + margin-bottom: 208px; + margin-top: 208px +} + +.sl-mx-52 { + margin-left: 208px; + margin-right: 208px +} + +.sl-my-56 { + margin-bottom: 224px; + margin-top: 224px +} + +.sl-mx-56 { + margin-left: 224px; + margin-right: 224px +} + +.sl-my-60 { + margin-bottom: 240px; + margin-top: 240px +} + +.sl-mx-60 { + margin-left: 240px; + margin-right: 240px +} + +.sl-my-64 { + margin-bottom: 256px; + margin-top: 256px +} + +.sl-mx-64 { + margin-left: 256px; + margin-right: 256px +} + +.sl-my-72 { + margin-bottom: 288px; + margin-top: 288px +} + +.sl-mx-72 { + margin-left: 288px; + margin-right: 288px +} + +.sl-my-80 { + margin-bottom: 320px; + margin-top: 320px +} + +.sl-mx-80 { + margin-left: 320px; + margin-right: 320px +} + +.sl-my-96 { + margin-bottom: 384px; + margin-top: 384px +} + +.sl-mx-96 { + margin-left: 384px; + margin-right: 384px +} + +.sl-my-auto { + margin-bottom: auto; + margin-top: auto +} + +.sl-mx-auto { + margin-left: auto; + margin-right: auto +} + +.sl-my-px { + margin-bottom: 1px; + margin-top: 1px +} + +.sl-mx-px { + margin-left: 1px; + margin-right: 1px +} + +.sl-my-0\.5 { + margin-bottom: 2px; + margin-top: 2px +} + +.sl-mx-0\.5 { + margin-left: 2px; + margin-right: 2px +} + +.sl-my-1\.5 { + margin-bottom: 6px; + margin-top: 6px +} + +.sl-mx-1\.5 { + margin-left: 6px; + margin-right: 6px +} + +.sl-my-2\.5 { + margin-bottom: 10px; + margin-top: 10px +} + +.sl-mx-2\.5 { + margin-left: 10px; + margin-right: 10px +} + +.sl-my-3\.5 { + margin-bottom: 14px; + margin-top: 14px +} + +.sl-mx-3\.5 { + margin-left: 14px; + margin-right: 14px +} + +.sl-my-4\.5 { + margin-bottom: 18px; + margin-top: 18px +} + +.sl-mx-4\.5 { + margin-left: 18px; + margin-right: 18px +} + +.sl--my-0 { + margin-bottom: 0; + margin-top: 0 +} + +.sl--mx-0 { + margin-left: 0; + margin-right: 0 +} + +.sl--my-1 { + margin-bottom: -4px; + margin-top: -4px +} + +.sl--mx-1 { + margin-left: -4px; + margin-right: -4px +} + +.sl--my-2 { + margin-bottom: -8px; + margin-top: -8px +} + +.sl--mx-2 { + margin-left: -8px; + margin-right: -8px +} + +.sl--my-3 { + margin-bottom: -12px; + margin-top: -12px +} + +.sl--mx-3 { + margin-left: -12px; + margin-right: -12px +} + +.sl--my-4 { + margin-bottom: -16px; + margin-top: -16px +} + +.sl--mx-4 { + margin-left: -16px; + margin-right: -16px +} + +.sl--my-5 { + margin-bottom: -20px; + margin-top: -20px +} + +.sl--mx-5 { + margin-left: -20px; + margin-right: -20px +} + +.sl--my-6 { + margin-bottom: -24px; + margin-top: -24px +} + +.sl--mx-6 { + margin-left: -24px; + margin-right: -24px +} + +.sl--my-7 { + margin-bottom: -28px; + margin-top: -28px +} + +.sl--mx-7 { + margin-left: -28px; + margin-right: -28px +} + +.sl--my-8 { + margin-bottom: -32px; + margin-top: -32px +} + +.sl--mx-8 { + margin-left: -32px; + margin-right: -32px +} + +.sl--my-9 { + margin-bottom: -36px; + margin-top: -36px +} + +.sl--mx-9 { + margin-left: -36px; + margin-right: -36px +} + +.sl--my-10 { + margin-bottom: -40px; + margin-top: -40px +} + +.sl--mx-10 { + margin-left: -40px; + margin-right: -40px +} + +.sl--my-11 { + margin-bottom: -44px; + margin-top: -44px +} + +.sl--mx-11 { + margin-left: -44px; + margin-right: -44px +} + +.sl--my-12 { + margin-bottom: -48px; + margin-top: -48px +} + +.sl--mx-12 { + margin-left: -48px; + margin-right: -48px +} + +.sl--my-14 { + margin-bottom: -56px; + margin-top: -56px +} + +.sl--mx-14 { + margin-left: -56px; + margin-right: -56px +} + +.sl--my-16 { + margin-bottom: -64px; + margin-top: -64px +} + +.sl--mx-16 { + margin-left: -64px; + margin-right: -64px +} + +.sl--my-20 { + margin-bottom: -80px; + margin-top: -80px +} + +.sl--mx-20 { + margin-left: -80px; + margin-right: -80px +} + +.sl--my-24 { + margin-bottom: -96px; + margin-top: -96px +} + +.sl--mx-24 { + margin-left: -96px; + margin-right: -96px +} + +.sl--my-28 { + margin-bottom: -112px; + margin-top: -112px +} + +.sl--mx-28 { + margin-left: -112px; + margin-right: -112px +} + +.sl--my-32 { + margin-bottom: -128px; + margin-top: -128px +} + +.sl--mx-32 { + margin-left: -128px; + margin-right: -128px +} + +.sl--my-36 { + margin-bottom: -144px; + margin-top: -144px +} + +.sl--mx-36 { + margin-left: -144px; + margin-right: -144px +} + +.sl--my-40 { + margin-bottom: -160px; + margin-top: -160px +} + +.sl--mx-40 { + margin-left: -160px; + margin-right: -160px +} + +.sl--my-44 { + margin-bottom: -176px; + margin-top: -176px +} + +.sl--mx-44 { + margin-left: -176px; + margin-right: -176px +} + +.sl--my-48 { + margin-bottom: -192px; + margin-top: -192px +} + +.sl--mx-48 { + margin-left: -192px; + margin-right: -192px +} + +.sl--my-52 { + margin-bottom: -208px; + margin-top: -208px +} + +.sl--mx-52 { + margin-left: -208px; + margin-right: -208px +} + +.sl--my-56 { + margin-bottom: -224px; + margin-top: -224px +} + +.sl--mx-56 { + margin-left: -224px; + margin-right: -224px +} + +.sl--my-60 { + margin-bottom: -240px; + margin-top: -240px +} + +.sl--mx-60 { + margin-left: -240px; + margin-right: -240px +} + +.sl--my-64 { + margin-bottom: -256px; + margin-top: -256px +} + +.sl--mx-64 { + margin-left: -256px; + margin-right: -256px +} + +.sl--my-72 { + margin-bottom: -288px; + margin-top: -288px +} + +.sl--mx-72 { + margin-left: -288px; + margin-right: -288px +} + +.sl--my-80 { + margin-bottom: -320px; + margin-top: -320px +} + +.sl--mx-80 { + margin-left: -320px; + margin-right: -320px +} + +.sl--my-96 { + margin-bottom: -384px; + margin-top: -384px +} + +.sl--mx-96 { + margin-left: -384px; + margin-right: -384px +} + +.sl--my-px { + margin-bottom: -1px; + margin-top: -1px +} + +.sl--mx-px { + margin-left: -1px; + margin-right: -1px +} + +.sl--my-0\.5 { + margin-bottom: -2px; + margin-top: -2px +} + +.sl--mx-0\.5 { + margin-left: -2px; + margin-right: -2px +} + +.sl--my-1\.5 { + margin-bottom: -6px; + margin-top: -6px +} + +.sl--mx-1\.5 { + margin-left: -6px; + margin-right: -6px +} + +.sl--my-2\.5 { + margin-bottom: -10px; + margin-top: -10px +} + +.sl--mx-2\.5 { + margin-left: -10px; + margin-right: -10px +} + +.sl--my-3\.5 { + margin-bottom: -14px; + margin-top: -14px +} + +.sl--mx-3\.5 { + margin-left: -14px; + margin-right: -14px +} + +.sl--my-4\.5 { + margin-bottom: -18px; + margin-top: -18px +} + +.sl--mx-4\.5 { + margin-left: -18px; + margin-right: -18px +} + +.sl-mt-0 { + margin-top: 0 +} + +.sl-mr-0 { + margin-right: 0 +} + +.sl-mb-0 { + margin-bottom: 0 +} + +.sl-ml-0 { + margin-left: 0 +} + +.sl-mt-1 { + margin-top: 4px +} + +.sl-mr-1 { + margin-right: 4px +} + +.sl-mb-1 { + margin-bottom: 4px +} + +.sl-ml-1 { + margin-left: 4px +} + +.sl-mt-2 { + margin-top: 8px +} + +.sl-mr-2 { + margin-right: 8px +} + +.sl-mb-2 { + margin-bottom: 8px +} + +.sl-ml-2 { + margin-left: 8px +} + +.sl-mt-3 { + margin-top: 12px +} + +.sl-mr-3 { + margin-right: 12px +} + +.sl-mb-3 { + margin-bottom: 12px +} + +.sl-ml-3 { + margin-left: 12px +} + +.sl-mt-4 { + margin-top: 16px +} + +.sl-mr-4 { + margin-right: 16px +} + +.sl-mb-4 { + margin-bottom: 16px +} + +.sl-ml-4 { + margin-left: 16px +} + +.sl-mt-5 { + margin-top: 20px +} + +.sl-mr-5 { + margin-right: 20px +} + +.sl-mb-5 { + margin-bottom: 20px +} + +.sl-ml-5 { + margin-left: 20px +} + +.sl-mt-6 { + margin-top: 24px +} + +.sl-mr-6 { + margin-right: 24px +} + +.sl-mb-6 { + margin-bottom: 24px +} + +.sl-ml-6 { + margin-left: 24px +} + +.sl-mt-7 { + margin-top: 28px +} + +.sl-mr-7 { + margin-right: 28px +} + +.sl-mb-7 { + margin-bottom: 28px +} + +.sl-ml-7 { + margin-left: 28px +} + +.sl-mt-8 { + margin-top: 32px +} + +.sl-mr-8 { + margin-right: 32px +} + +.sl-mb-8 { + margin-bottom: 32px +} + +.sl-ml-8 { + margin-left: 32px +} + +.sl-mt-9 { + margin-top: 36px +} + +.sl-mr-9 { + margin-right: 36px +} + +.sl-mb-9 { + margin-bottom: 36px +} + +.sl-ml-9 { + margin-left: 36px +} + +.sl-mt-10 { + margin-top: 40px +} + +.sl-mr-10 { + margin-right: 40px +} + +.sl-mb-10 { + margin-bottom: 40px +} + +.sl-ml-10 { + margin-left: 40px +} + +.sl-mt-11 { + margin-top: 44px +} + +.sl-mr-11 { + margin-right: 44px +} + +.sl-mb-11 { + margin-bottom: 44px +} + +.sl-ml-11 { + margin-left: 44px +} + +.sl-mt-12 { + margin-top: 48px +} + +.sl-mr-12 { + margin-right: 48px +} + +.sl-mb-12 { + margin-bottom: 48px +} + +.sl-ml-12 { + margin-left: 48px +} + +.sl-mt-14 { + margin-top: 56px +} + +.sl-mr-14 { + margin-right: 56px +} + +.sl-mb-14 { + margin-bottom: 56px +} + +.sl-ml-14 { + margin-left: 56px +} + +.sl-mt-16 { + margin-top: 64px +} + +.sl-mr-16 { + margin-right: 64px +} + +.sl-mb-16 { + margin-bottom: 64px +} + +.sl-ml-16 { + margin-left: 64px +} + +.sl-mt-20 { + margin-top: 80px +} + +.sl-mr-20 { + margin-right: 80px +} + +.sl-mb-20 { + margin-bottom: 80px +} + +.sl-ml-20 { + margin-left: 80px +} + +.sl-mt-24 { + margin-top: 96px +} + +.sl-mr-24 { + margin-right: 96px +} + +.sl-mb-24 { + margin-bottom: 96px +} + +.sl-ml-24 { + margin-left: 96px +} + +.sl-mt-28 { + margin-top: 112px +} + +.sl-mr-28 { + margin-right: 112px +} + +.sl-mb-28 { + margin-bottom: 112px +} + +.sl-ml-28 { + margin-left: 112px +} + +.sl-mt-32 { + margin-top: 128px +} + +.sl-mr-32 { + margin-right: 128px +} + +.sl-mb-32 { + margin-bottom: 128px +} + +.sl-ml-32 { + margin-left: 128px +} + +.sl-mt-36 { + margin-top: 144px +} + +.sl-mr-36 { + margin-right: 144px +} + +.sl-mb-36 { + margin-bottom: 144px +} + +.sl-ml-36 { + margin-left: 144px +} + +.sl-mt-40 { + margin-top: 160px +} + +.sl-mr-40 { + margin-right: 160px +} + +.sl-mb-40 { + margin-bottom: 160px +} + +.sl-ml-40 { + margin-left: 160px +} + +.sl-mt-44 { + margin-top: 176px +} + +.sl-mr-44 { + margin-right: 176px +} + +.sl-mb-44 { + margin-bottom: 176px +} + +.sl-ml-44 { + margin-left: 176px +} + +.sl-mt-48 { + margin-top: 192px +} + +.sl-mr-48 { + margin-right: 192px +} + +.sl-mb-48 { + margin-bottom: 192px +} + +.sl-ml-48 { + margin-left: 192px +} + +.sl-mt-52 { + margin-top: 208px +} + +.sl-mr-52 { + margin-right: 208px +} + +.sl-mb-52 { + margin-bottom: 208px +} + +.sl-ml-52 { + margin-left: 208px +} + +.sl-mt-56 { + margin-top: 224px +} + +.sl-mr-56 { + margin-right: 224px +} + +.sl-mb-56 { + margin-bottom: 224px +} + +.sl-ml-56 { + margin-left: 224px +} + +.sl-mt-60 { + margin-top: 240px +} + +.sl-mr-60 { + margin-right: 240px +} + +.sl-mb-60 { + margin-bottom: 240px +} + +.sl-ml-60 { + margin-left: 240px +} + +.sl-mt-64 { + margin-top: 256px +} + +.sl-mr-64 { + margin-right: 256px +} + +.sl-mb-64 { + margin-bottom: 256px +} + +.sl-ml-64 { + margin-left: 256px +} + +.sl-mt-72 { + margin-top: 288px +} + +.sl-mr-72 { + margin-right: 288px +} + +.sl-mb-72 { + margin-bottom: 288px +} + +.sl-ml-72 { + margin-left: 288px +} + +.sl-mt-80 { + margin-top: 320px +} + +.sl-mr-80 { + margin-right: 320px +} + +.sl-mb-80 { + margin-bottom: 320px +} + +.sl-ml-80 { + margin-left: 320px +} + +.sl-mt-96 { + margin-top: 384px +} + +.sl-mr-96 { + margin-right: 384px +} + +.sl-mb-96 { + margin-bottom: 384px +} + +.sl-ml-96 { + margin-left: 384px +} + +.sl-mt-auto { + margin-top: auto +} + +.sl-mr-auto { + margin-right: auto +} + +.sl-mb-auto { + margin-bottom: auto +} + +.sl-ml-auto { + margin-left: auto +} + +.sl-mt-px { + margin-top: 1px +} + +.sl-mr-px { + margin-right: 1px +} + +.sl-mb-px { + margin-bottom: 1px +} + +.sl-ml-px { + margin-left: 1px +} + +.sl-mt-0\.5 { + margin-top: 2px +} + +.sl-mr-0\.5 { + margin-right: 2px +} + +.sl-mb-0\.5 { + margin-bottom: 2px +} + +.sl-ml-0\.5 { + margin-left: 2px +} + +.sl-mt-1\.5 { + margin-top: 6px +} + +.sl-mr-1\.5 { + margin-right: 6px +} + +.sl-mb-1\.5 { + margin-bottom: 6px +} + +.sl-ml-1\.5 { + margin-left: 6px +} + +.sl-mt-2\.5 { + margin-top: 10px +} + +.sl-mr-2\.5 { + margin-right: 10px +} + +.sl-mb-2\.5 { + margin-bottom: 10px +} + +.sl-ml-2\.5 { + margin-left: 10px +} + +.sl-mt-3\.5 { + margin-top: 14px +} + +.sl-mr-3\.5 { + margin-right: 14px +} + +.sl-mb-3\.5 { + margin-bottom: 14px +} + +.sl-ml-3\.5 { + margin-left: 14px +} + +.sl-mt-4\.5 { + margin-top: 18px +} + +.sl-mr-4\.5 { + margin-right: 18px +} + +.sl-mb-4\.5 { + margin-bottom: 18px +} + +.sl-ml-4\.5 { + margin-left: 18px +} + +.sl--mt-0 { + margin-top: 0 +} + +.sl--mr-0 { + margin-right: 0 +} + +.sl--mb-0 { + margin-bottom: 0 +} + +.sl--ml-0 { + margin-left: 0 +} + +.sl--mt-1 { + margin-top: -4px +} + +.sl--mr-1 { + margin-right: -4px +} + +.sl--mb-1 { + margin-bottom: -4px +} + +.sl--ml-1 { + margin-left: -4px +} + +.sl--mt-2 { + margin-top: -8px +} + +.sl--mr-2 { + margin-right: -8px +} + +.sl--mb-2 { + margin-bottom: -8px +} + +.sl--ml-2 { + margin-left: -8px +} + +.sl--mt-3 { + margin-top: -12px +} + +.sl--mr-3 { + margin-right: -12px +} + +.sl--mb-3 { + margin-bottom: -12px +} + +.sl--ml-3 { + margin-left: -12px +} + +.sl--mt-4 { + margin-top: -16px +} + +.sl--mr-4 { + margin-right: -16px +} + +.sl--mb-4 { + margin-bottom: -16px +} + +.sl--ml-4 { + margin-left: -16px +} + +.sl--mt-5 { + margin-top: -20px +} + +.sl--mr-5 { + margin-right: -20px +} + +.sl--mb-5 { + margin-bottom: -20px +} + +.sl--ml-5 { + margin-left: -20px +} + +.sl--mt-6 { + margin-top: -24px +} + +.sl--mr-6 { + margin-right: -24px +} + +.sl--mb-6 { + margin-bottom: -24px +} + +.sl--ml-6 { + margin-left: -24px +} + +.sl--mt-7 { + margin-top: -28px +} + +.sl--mr-7 { + margin-right: -28px +} + +.sl--mb-7 { + margin-bottom: -28px +} + +.sl--ml-7 { + margin-left: -28px +} + +.sl--mt-8 { + margin-top: -32px +} + +.sl--mr-8 { + margin-right: -32px +} + +.sl--mb-8 { + margin-bottom: -32px +} + +.sl--ml-8 { + margin-left: -32px +} + +.sl--mt-9 { + margin-top: -36px +} + +.sl--mr-9 { + margin-right: -36px +} + +.sl--mb-9 { + margin-bottom: -36px +} + +.sl--ml-9 { + margin-left: -36px +} + +.sl--mt-10 { + margin-top: -40px +} + +.sl--mr-10 { + margin-right: -40px +} + +.sl--mb-10 { + margin-bottom: -40px +} + +.sl--ml-10 { + margin-left: -40px +} + +.sl--mt-11 { + margin-top: -44px +} + +.sl--mr-11 { + margin-right: -44px +} + +.sl--mb-11 { + margin-bottom: -44px +} + +.sl--ml-11 { + margin-left: -44px +} + +.sl--mt-12 { + margin-top: -48px +} + +.sl--mr-12 { + margin-right: -48px +} + +.sl--mb-12 { + margin-bottom: -48px +} + +.sl--ml-12 { + margin-left: -48px +} + +.sl--mt-14 { + margin-top: -56px +} + +.sl--mr-14 { + margin-right: -56px +} + +.sl--mb-14 { + margin-bottom: -56px +} + +.sl--ml-14 { + margin-left: -56px +} + +.sl--mt-16 { + margin-top: -64px +} + +.sl--mr-16 { + margin-right: -64px +} + +.sl--mb-16 { + margin-bottom: -64px +} + +.sl--ml-16 { + margin-left: -64px +} + +.sl--mt-20 { + margin-top: -80px +} + +.sl--mr-20 { + margin-right: -80px +} + +.sl--mb-20 { + margin-bottom: -80px +} + +.sl--ml-20 { + margin-left: -80px +} + +.sl--mt-24 { + margin-top: -96px +} + +.sl--mr-24 { + margin-right: -96px +} + +.sl--mb-24 { + margin-bottom: -96px +} + +.sl--ml-24 { + margin-left: -96px +} + +.sl--mt-28 { + margin-top: -112px +} + +.sl--mr-28 { + margin-right: -112px +} + +.sl--mb-28 { + margin-bottom: -112px +} + +.sl--ml-28 { + margin-left: -112px +} + +.sl--mt-32 { + margin-top: -128px +} + +.sl--mr-32 { + margin-right: -128px +} + +.sl--mb-32 { + margin-bottom: -128px +} + +.sl--ml-32 { + margin-left: -128px +} + +.sl--mt-36 { + margin-top: -144px +} + +.sl--mr-36 { + margin-right: -144px +} + +.sl--mb-36 { + margin-bottom: -144px +} + +.sl--ml-36 { + margin-left: -144px +} + +.sl--mt-40 { + margin-top: -160px +} + +.sl--mr-40 { + margin-right: -160px +} + +.sl--mb-40 { + margin-bottom: -160px +} + +.sl--ml-40 { + margin-left: -160px +} + +.sl--mt-44 { + margin-top: -176px +} + +.sl--mr-44 { + margin-right: -176px +} + +.sl--mb-44 { + margin-bottom: -176px +} + +.sl--ml-44 { + margin-left: -176px +} + +.sl--mt-48 { + margin-top: -192px +} + +.sl--mr-48 { + margin-right: -192px +} + +.sl--mb-48 { + margin-bottom: -192px +} + +.sl--ml-48 { + margin-left: -192px +} + +.sl--mt-52 { + margin-top: -208px +} + +.sl--mr-52 { + margin-right: -208px +} + +.sl--mb-52 { + margin-bottom: -208px +} + +.sl--ml-52 { + margin-left: -208px +} + +.sl--mt-56 { + margin-top: -224px +} + +.sl--mr-56 { + margin-right: -224px +} + +.sl--mb-56 { + margin-bottom: -224px +} + +.sl--ml-56 { + margin-left: -224px +} + +.sl--mt-60 { + margin-top: -240px +} + +.sl--mr-60 { + margin-right: -240px +} + +.sl--mb-60 { + margin-bottom: -240px +} + +.sl--ml-60 { + margin-left: -240px +} + +.sl--mt-64 { + margin-top: -256px +} + +.sl--mr-64 { + margin-right: -256px +} + +.sl--mb-64 { + margin-bottom: -256px +} + +.sl--ml-64 { + margin-left: -256px +} + +.sl--mt-72 { + margin-top: -288px +} + +.sl--mr-72 { + margin-right: -288px +} + +.sl--mb-72 { + margin-bottom: -288px +} + +.sl--ml-72 { + margin-left: -288px +} + +.sl--mt-80 { + margin-top: -320px +} + +.sl--mr-80 { + margin-right: -320px +} + +.sl--mb-80 { + margin-bottom: -320px +} + +.sl--ml-80 { + margin-left: -320px +} + +.sl--mt-96 { + margin-top: -384px +} + +.sl--mr-96 { + margin-right: -384px +} + +.sl--mb-96 { + margin-bottom: -384px +} + +.sl--ml-96 { + margin-left: -384px +} + +.sl--mt-px { + margin-top: -1px +} + +.sl--mr-px { + margin-right: -1px +} + +.sl--mb-px { + margin-bottom: -1px +} + +.sl--ml-px { + margin-left: -1px +} + +.sl--mt-0\.5 { + margin-top: -2px +} + +.sl--mr-0\.5 { + margin-right: -2px +} + +.sl--mb-0\.5 { + margin-bottom: -2px +} + +.sl--ml-0\.5 { + margin-left: -2px +} + +.sl--mt-1\.5 { + margin-top: -6px +} + +.sl--mr-1\.5 { + margin-right: -6px +} + +.sl--mb-1\.5 { + margin-bottom: -6px +} + +.sl--ml-1\.5 { + margin-left: -6px +} + +.sl--mt-2\.5 { + margin-top: -10px +} + +.sl--mr-2\.5 { + margin-right: -10px +} + +.sl--mb-2\.5 { + margin-bottom: -10px +} + +.sl--ml-2\.5 { + margin-left: -10px +} + +.sl--mt-3\.5 { + margin-top: -14px +} + +.sl--mr-3\.5 { + margin-right: -14px +} + +.sl--mb-3\.5 { + margin-bottom: -14px +} + +.sl--ml-3\.5 { + margin-left: -14px +} + +.sl--mt-4\.5 { + margin-top: -18px +} + +.sl--mr-4\.5 { + margin-right: -18px +} + +.sl--mb-4\.5 { + margin-bottom: -18px +} + +.sl--ml-4\.5 { + margin-left: -18px +} + +.sl-max-h-full { + max-height: 100% +} + +.sl-max-h-screen { + max-height: 100vh +} + +.sl-max-w-none { + max-width: none +} + +.sl-max-w-full { + max-width: 100% +} + +.sl-max-w-min { + max-width: -moz-min-content; + max-width: min-content +} + +.sl-max-w-max { + max-width: -moz-max-content; + max-width: max-content +} + +.sl-max-w-prose { + max-width: 65ch +} + +.sl-min-h-full { + min-height: 100% +} + +.sl-min-h-screen { + min-height: 100vh +} + +.sl-min-w-full { + min-width: 100% +} + +.sl-min-w-min { + min-width: -moz-min-content; + min-width: min-content +} + +.sl-min-w-max { + min-width: -moz-max-content; + min-width: max-content +} + +.sl-object-contain { + object-fit: contain +} + +.sl-object-cover { + object-fit: cover +} + +.sl-object-fill { + object-fit: fill +} + +.sl-object-none { + object-fit: none +} + +.sl-object-scale-down { + object-fit: scale-down +} + +.sl-object-bottom { + object-position: bottom +} + +.sl-object-center { + object-position: center +} + +.sl-object-left { + object-position: left +} + +.sl-object-left-bottom { + object-position: left bottom +} + +.sl-object-left-top { + object-position: left top +} + +.sl-object-right { + object-position: right +} + +.sl-object-right-bottom { + object-position: right bottom +} + +.sl-object-right-top { + object-position: right top +} + +.sl-object-top { + object-position: top +} + +.sl-opacity-0 { + opacity: 0 +} + +.sl-opacity-5 { + opacity: .05 +} + +.sl-opacity-10 { + opacity: .1 +} + +.sl-opacity-20 { + opacity: .2 +} + +.sl-opacity-30 { + opacity: .3 +} + +.sl-opacity-40 { + opacity: .4 +} + +.sl-opacity-50 { + opacity: .5 +} + +.sl-opacity-60 { + opacity: .6 +} + +.sl-opacity-70 { + opacity: .7 +} + +.sl-opacity-90 { + opacity: .9 +} + +.sl-opacity-100 { + opacity: 1 +} + +.hover\:sl-opacity-0:hover { + opacity: 0 +} + +.hover\:sl-opacity-5:hover { + opacity: .05 +} + +.hover\:sl-opacity-10:hover { + opacity: .1 +} + +.hover\:sl-opacity-20:hover { + opacity: .2 +} + +.hover\:sl-opacity-30:hover { + opacity: .3 +} + +.hover\:sl-opacity-40:hover { + opacity: .4 +} + +.hover\:sl-opacity-50:hover { + opacity: .5 +} + +.hover\:sl-opacity-60:hover { + opacity: .6 +} + +.hover\:sl-opacity-70:hover { + opacity: .7 +} + +.hover\:sl-opacity-90:hover { + opacity: .9 +} + +.hover\:sl-opacity-100:hover { + opacity: 1 +} + +.focus\:sl-opacity-0:focus { + opacity: 0 +} + +.focus\:sl-opacity-5:focus { + opacity: .05 +} + +.focus\:sl-opacity-10:focus { + opacity: .1 +} + +.focus\:sl-opacity-20:focus { + opacity: .2 +} + +.focus\:sl-opacity-30:focus { + opacity: .3 +} + +.focus\:sl-opacity-40:focus { + opacity: .4 +} + +.focus\:sl-opacity-50:focus { + opacity: .5 +} + +.focus\:sl-opacity-60:focus { + opacity: .6 +} + +.focus\:sl-opacity-70:focus { + opacity: .7 +} + +.focus\:sl-opacity-90:focus { + opacity: .9 +} + +.focus\:sl-opacity-100:focus { + opacity: 1 +} + +.active\:sl-opacity-0:active { + opacity: 0 +} + +.active\:sl-opacity-5:active { + opacity: .05 +} + +.active\:sl-opacity-10:active { + opacity: .1 +} + +.active\:sl-opacity-20:active { + opacity: .2 +} + +.active\:sl-opacity-30:active { + opacity: .3 +} + +.active\:sl-opacity-40:active { + opacity: .4 +} + +.active\:sl-opacity-50:active { + opacity: .5 +} + +.active\:sl-opacity-60:active { + opacity: .6 +} + +.active\:sl-opacity-70:active { + opacity: .7 +} + +.active\:sl-opacity-90:active { + opacity: .9 +} + +.active\:sl-opacity-100:active { + opacity: 1 +} + +.disabled\:sl-opacity-0:disabled { + opacity: 0 +} + +.disabled\:sl-opacity-5:disabled { + opacity: .05 +} + +.disabled\:sl-opacity-10:disabled { + opacity: .1 +} + +.disabled\:sl-opacity-20:disabled { + opacity: .2 +} + +.disabled\:sl-opacity-30:disabled { + opacity: .3 +} + +.disabled\:sl-opacity-40:disabled { + opacity: .4 +} + +.disabled\:sl-opacity-50:disabled { + opacity: .5 +} + +.disabled\:sl-opacity-60:disabled { + opacity: .6 +} + +.disabled\:sl-opacity-70:disabled { + opacity: .7 +} + +.disabled\:sl-opacity-90:disabled { + opacity: .9 +} + +.disabled\:sl-opacity-100:disabled { + opacity: 1 +} + +.sl-outline-none { + outline: 2px solid transparent; + outline-offset: 2px +} + +.sl-overflow-auto { + overflow: auto +} + +.sl-overflow-hidden { + overflow: hidden +} + +.sl-overflow-visible { + overflow: visible +} + +.sl-overflow-scroll { + overflow: scroll +} + +.sl-overflow-x-auto { + overflow-x: auto +} + +.sl-overflow-y-auto { + overflow-y: auto +} + +.sl-overflow-x-hidden { + overflow-x: hidden +} + +.sl-overflow-y-hidden { + overflow-y: hidden +} + +.sl-overflow-x-visible { + overflow-x: visible +} + +.sl-overflow-y-visible { + overflow-y: visible +} + +.sl-overflow-x-scroll { + overflow-x: scroll +} + +.sl-overflow-y-scroll { + overflow-y: scroll +} + +.sl-overscroll-auto { + overscroll-behavior: auto +} + +.sl-overscroll-contain { + overscroll-behavior: contain +} + +.sl-overscroll-none { + overscroll-behavior: none +} + +.sl-overscroll-y-auto { + overscroll-behavior-y: auto +} + +.sl-overscroll-y-contain { + overscroll-behavior-y: contain +} + +.sl-overscroll-y-none { + overscroll-behavior-y: none +} + +.sl-overscroll-x-auto { + overscroll-behavior-x: auto +} + +.sl-overscroll-x-contain { + overscroll-behavior-x: contain +} + +.sl-overscroll-x-none { + overscroll-behavior-x: none +} + +.sl-p-0 { + padding: 0 +} + +.sl-p-1 { + padding: 4px +} + +.sl-p-2 { + padding: 8px +} + +.sl-p-3 { + padding: 12px +} + +.sl-p-4 { + padding: 16px +} + +.sl-p-5 { + padding: 20px +} + +.sl-p-6 { + padding: 24px +} + +.sl-p-7 { + padding: 28px +} + +.sl-p-8 { + padding: 32px +} + +.sl-p-9 { + padding: 36px +} + +.sl-p-10 { + padding: 40px +} + +.sl-p-11 { + padding: 44px +} + +.sl-p-12 { + padding: 48px +} + +.sl-p-14 { + padding: 56px +} + +.sl-p-16 { + padding: 64px +} + +.sl-p-20 { + padding: 80px +} + +.sl-p-24 { + padding: 96px +} + +.sl-p-28 { + padding: 112px +} + +.sl-p-32 { + padding: 128px +} + +.sl-p-36 { + padding: 144px +} + +.sl-p-40 { + padding: 160px +} + +.sl-p-44 { + padding: 176px +} + +.sl-p-48 { + padding: 192px +} + +.sl-p-52 { + padding: 208px +} + +.sl-p-56 { + padding: 224px +} + +.sl-p-60 { + padding: 240px +} + +.sl-p-64 { + padding: 256px +} + +.sl-p-72 { + padding: 288px +} + +.sl-p-80 { + padding: 320px +} + +.sl-p-96 { + padding: 384px +} + +.sl-p-px { + padding: 1px +} + +.sl-p-0\.5 { + padding: 2px +} + +.sl-p-1\.5 { + padding: 6px +} + +.sl-p-2\.5 { + padding: 10px +} + +.sl-p-3\.5 { + padding: 14px +} + +.sl-p-4\.5 { + padding: 18px +} + +.sl-py-0 { + padding-bottom: 0; + padding-top: 0 +} + +.sl-px-0 { + padding-left: 0; + padding-right: 0 +} + +.sl-py-1 { + padding-bottom: 4px; + padding-top: 4px +} + +.sl-px-1 { + padding-left: 4px; + padding-right: 4px +} + +.sl-py-2 { + padding-bottom: 8px; + padding-top: 8px +} + +.sl-px-2 { + padding-left: 8px; + padding-right: 8px +} + +.sl-py-3 { + padding-bottom: 12px; + padding-top: 12px +} + +.sl-px-3 { + padding-left: 12px; + padding-right: 12px +} + +.sl-py-4 { + padding-bottom: 16px; + padding-top: 16px +} + +.sl-px-4 { + padding-left: 16px; + padding-right: 16px +} + +.sl-py-5 { + padding-bottom: 20px; + padding-top: 20px +} + +.sl-px-5 { + padding-left: 20px; + padding-right: 20px +} + +.sl-py-6 { + padding-bottom: 24px; + padding-top: 24px +} + +.sl-px-6 { + padding-left: 24px; + padding-right: 24px +} + +.sl-py-7 { + padding-bottom: 28px; + padding-top: 28px +} + +.sl-px-7 { + padding-left: 28px; + padding-right: 28px +} + +.sl-py-8 { + padding-bottom: 32px; + padding-top: 32px +} + +.sl-px-8 { + padding-left: 32px; + padding-right: 32px +} + +.sl-py-9 { + padding-bottom: 36px; + padding-top: 36px +} + +.sl-px-9 { + padding-left: 36px; + padding-right: 36px +} + +.sl-py-10 { + padding-bottom: 40px; + padding-top: 40px +} + +.sl-px-10 { + padding-left: 40px; + padding-right: 40px +} + +.sl-py-11 { + padding-bottom: 44px; + padding-top: 44px +} + +.sl-px-11 { + padding-left: 44px; + padding-right: 44px +} + +.sl-py-12 { + padding-bottom: 48px; + padding-top: 48px +} + +.sl-px-12 { + padding-left: 48px; + padding-right: 48px +} + +.sl-py-14 { + padding-bottom: 56px; + padding-top: 56px +} + +.sl-px-14 { + padding-left: 56px; + padding-right: 56px +} + +.sl-py-16 { + padding-bottom: 64px; + padding-top: 64px +} + +.sl-px-16 { + padding-left: 64px; + padding-right: 64px +} + +.sl-py-20 { + padding-bottom: 80px; + padding-top: 80px +} + +.sl-px-20 { + padding-left: 80px; + padding-right: 80px +} + +.sl-py-24 { + padding-bottom: 96px; + padding-top: 96px +} + +.sl-px-24 { + padding-left: 96px; + padding-right: 96px +} + +.sl-py-28 { + padding-bottom: 112px; + padding-top: 112px +} + +.sl-px-28 { + padding-left: 112px; + padding-right: 112px +} + +.sl-py-32 { + padding-bottom: 128px; + padding-top: 128px +} + +.sl-px-32 { + padding-left: 128px; + padding-right: 128px +} + +.sl-py-36 { + padding-bottom: 144px; + padding-top: 144px +} + +.sl-px-36 { + padding-left: 144px; + padding-right: 144px +} + +.sl-py-40 { + padding-bottom: 160px; + padding-top: 160px +} + +.sl-px-40 { + padding-left: 160px; + padding-right: 160px +} + +.sl-py-44 { + padding-bottom: 176px; + padding-top: 176px +} + +.sl-px-44 { + padding-left: 176px; + padding-right: 176px +} + +.sl-py-48 { + padding-bottom: 192px; + padding-top: 192px +} + +.sl-px-48 { + padding-left: 192px; + padding-right: 192px +} + +.sl-py-52 { + padding-bottom: 208px; + padding-top: 208px +} + +.sl-px-52 { + padding-left: 208px; + padding-right: 208px +} + +.sl-py-56 { + padding-bottom: 224px; + padding-top: 224px +} + +.sl-px-56 { + padding-left: 224px; + padding-right: 224px +} + +.sl-py-60 { + padding-bottom: 240px; + padding-top: 240px +} + +.sl-px-60 { + padding-left: 240px; + padding-right: 240px +} + +.sl-py-64 { + padding-bottom: 256px; + padding-top: 256px +} + +.sl-px-64 { + padding-left: 256px; + padding-right: 256px +} + +.sl-py-72 { + padding-bottom: 288px; + padding-top: 288px +} + +.sl-px-72 { + padding-left: 288px; + padding-right: 288px +} + +.sl-py-80 { + padding-bottom: 320px; + padding-top: 320px +} + +.sl-px-80 { + padding-left: 320px; + padding-right: 320px +} + +.sl-py-96 { + padding-bottom: 384px; + padding-top: 384px +} + +.sl-px-96 { + padding-left: 384px; + padding-right: 384px +} + +.sl-py-px { + padding-bottom: 1px; + padding-top: 1px +} + +.sl-px-px { + padding-left: 1px; + padding-right: 1px +} + +.sl-py-0\.5 { + padding-bottom: 2px; + padding-top: 2px +} + +.sl-px-0\.5 { + padding-left: 2px; + padding-right: 2px +} + +.sl-py-1\.5 { + padding-bottom: 6px; + padding-top: 6px +} + +.sl-px-1\.5 { + padding-left: 6px; + padding-right: 6px +} + +.sl-py-2\.5 { + padding-bottom: 10px; + padding-top: 10px +} + +.sl-px-2\.5 { + padding-left: 10px; + padding-right: 10px +} + +.sl-py-3\.5 { + padding-bottom: 14px; + padding-top: 14px +} + +.sl-px-3\.5 { + padding-left: 14px; + padding-right: 14px +} + +.sl-py-4\.5 { + padding-bottom: 18px; + padding-top: 18px +} + +.sl-px-4\.5 { + padding-left: 18px; + padding-right: 18px +} + +.sl-pt-0 { + padding-top: 0 +} + +.sl-pr-0 { + padding-right: 0 +} + +.sl-pb-0 { + padding-bottom: 0 +} + +.sl-pl-0 { + padding-left: 0 +} + +.sl-pt-1 { + padding-top: 4px +} + +.sl-pr-1 { + padding-right: 4px +} + +.sl-pb-1 { + padding-bottom: 4px +} + +.sl-pl-1 { + padding-left: 4px +} + +.sl-pt-2 { + padding-top: 8px +} + +.sl-pr-2 { + padding-right: 8px +} + +.sl-pb-2 { + padding-bottom: 8px +} + +.sl-pl-2 { + padding-left: 8px +} + +.sl-pt-3 { + padding-top: 12px +} + +.sl-pr-3 { + padding-right: 12px +} + +.sl-pb-3 { + padding-bottom: 12px +} + +.sl-pl-3 { + padding-left: 12px +} + +.sl-pt-4 { + padding-top: 16px +} + +.sl-pr-4 { + padding-right: 16px +} + +.sl-pb-4 { + padding-bottom: 16px +} + +.sl-pl-4 { + padding-left: 16px +} + +.sl-pt-5 { + padding-top: 20px +} + +.sl-pr-5 { + padding-right: 20px +} + +.sl-pb-5 { + padding-bottom: 20px +} + +.sl-pl-5 { + padding-left: 20px +} + +.sl-pt-6 { + padding-top: 24px +} + +.sl-pr-6 { + padding-right: 24px +} + +.sl-pb-6 { + padding-bottom: 24px +} + +.sl-pl-6 { + padding-left: 24px +} + +.sl-pt-7 { + padding-top: 28px +} + +.sl-pr-7 { + padding-right: 28px +} + +.sl-pb-7 { + padding-bottom: 28px +} + +.sl-pl-7 { + padding-left: 28px +} + +.sl-pt-8 { + padding-top: 32px +} + +.sl-pr-8 { + padding-right: 32px +} + +.sl-pb-8 { + padding-bottom: 32px +} + +.sl-pl-8 { + padding-left: 32px +} + +.sl-pt-9 { + padding-top: 36px +} + +.sl-pr-9 { + padding-right: 36px +} + +.sl-pb-9 { + padding-bottom: 36px +} + +.sl-pl-9 { + padding-left: 36px +} + +.sl-pt-10 { + padding-top: 40px +} + +.sl-pr-10 { + padding-right: 40px +} + +.sl-pb-10 { + padding-bottom: 40px +} + +.sl-pl-10 { + padding-left: 40px +} + +.sl-pt-11 { + padding-top: 44px +} + +.sl-pr-11 { + padding-right: 44px +} + +.sl-pb-11 { + padding-bottom: 44px +} + +.sl-pl-11 { + padding-left: 44px +} + +.sl-pt-12 { + padding-top: 48px +} + +.sl-pr-12 { + padding-right: 48px +} + +.sl-pb-12 { + padding-bottom: 48px +} + +.sl-pl-12 { + padding-left: 48px +} + +.sl-pt-14 { + padding-top: 56px +} + +.sl-pr-14 { + padding-right: 56px +} + +.sl-pb-14 { + padding-bottom: 56px +} + +.sl-pl-14 { + padding-left: 56px +} + +.sl-pt-16 { + padding-top: 64px +} + +.sl-pr-16 { + padding-right: 64px +} + +.sl-pb-16 { + padding-bottom: 64px +} + +.sl-pl-16 { + padding-left: 64px +} + +.sl-pt-20 { + padding-top: 80px +} + +.sl-pr-20 { + padding-right: 80px +} + +.sl-pb-20 { + padding-bottom: 80px +} + +.sl-pl-20 { + padding-left: 80px +} + +.sl-pt-24 { + padding-top: 96px +} + +.sl-pr-24 { + padding-right: 96px +} + +.sl-pb-24 { + padding-bottom: 96px +} + +.sl-pl-24 { + padding-left: 96px +} + +.sl-pt-28 { + padding-top: 112px +} + +.sl-pr-28 { + padding-right: 112px +} + +.sl-pb-28 { + padding-bottom: 112px +} + +.sl-pl-28 { + padding-left: 112px +} + +.sl-pt-32 { + padding-top: 128px +} + +.sl-pr-32 { + padding-right: 128px +} + +.sl-pb-32 { + padding-bottom: 128px +} + +.sl-pl-32 { + padding-left: 128px +} + +.sl-pt-36 { + padding-top: 144px +} + +.sl-pr-36 { + padding-right: 144px +} + +.sl-pb-36 { + padding-bottom: 144px +} + +.sl-pl-36 { + padding-left: 144px +} + +.sl-pt-40 { + padding-top: 160px +} + +.sl-pr-40 { + padding-right: 160px +} + +.sl-pb-40 { + padding-bottom: 160px +} + +.sl-pl-40 { + padding-left: 160px +} + +.sl-pt-44 { + padding-top: 176px +} + +.sl-pr-44 { + padding-right: 176px +} + +.sl-pb-44 { + padding-bottom: 176px +} + +.sl-pl-44 { + padding-left: 176px +} + +.sl-pt-48 { + padding-top: 192px +} + +.sl-pr-48 { + padding-right: 192px +} + +.sl-pb-48 { + padding-bottom: 192px +} + +.sl-pl-48 { + padding-left: 192px +} + +.sl-pt-52 { + padding-top: 208px +} + +.sl-pr-52 { + padding-right: 208px +} + +.sl-pb-52 { + padding-bottom: 208px +} + +.sl-pl-52 { + padding-left: 208px +} + +.sl-pt-56 { + padding-top: 224px +} + +.sl-pr-56 { + padding-right: 224px +} + +.sl-pb-56 { + padding-bottom: 224px +} + +.sl-pl-56 { + padding-left: 224px +} + +.sl-pt-60 { + padding-top: 240px +} + +.sl-pr-60 { + padding-right: 240px +} + +.sl-pb-60 { + padding-bottom: 240px +} + +.sl-pl-60 { + padding-left: 240px +} + +.sl-pt-64 { + padding-top: 256px +} + +.sl-pr-64 { + padding-right: 256px +} + +.sl-pb-64 { + padding-bottom: 256px +} + +.sl-pl-64 { + padding-left: 256px +} + +.sl-pt-72 { + padding-top: 288px +} + +.sl-pr-72 { + padding-right: 288px +} + +.sl-pb-72 { + padding-bottom: 288px +} + +.sl-pl-72 { + padding-left: 288px +} + +.sl-pt-80 { + padding-top: 320px +} + +.sl-pr-80 { + padding-right: 320px +} + +.sl-pb-80 { + padding-bottom: 320px +} + +.sl-pl-80 { + padding-left: 320px +} + +.sl-pt-96 { + padding-top: 384px +} + +.sl-pr-96 { + padding-right: 384px +} + +.sl-pb-96 { + padding-bottom: 384px +} + +.sl-pl-96 { + padding-left: 384px +} + +.sl-pt-px { + padding-top: 1px +} + +.sl-pr-px { + padding-right: 1px +} + +.sl-pb-px { + padding-bottom: 1px +} + +.sl-pl-px { + padding-left: 1px +} + +.sl-pt-0\.5 { + padding-top: 2px +} + +.sl-pr-0\.5 { + padding-right: 2px +} + +.sl-pb-0\.5 { + padding-bottom: 2px +} + +.sl-pl-0\.5 { + padding-left: 2px +} + +.sl-pt-1\.5 { + padding-top: 6px +} + +.sl-pr-1\.5 { + padding-right: 6px +} + +.sl-pb-1\.5 { + padding-bottom: 6px +} + +.sl-pl-1\.5 { + padding-left: 6px +} + +.sl-pt-2\.5 { + padding-top: 10px +} + +.sl-pr-2\.5 { + padding-right: 10px +} + +.sl-pb-2\.5 { + padding-bottom: 10px +} + +.sl-pl-2\.5 { + padding-left: 10px +} + +.sl-pt-3\.5 { + padding-top: 14px +} + +.sl-pr-3\.5 { + padding-right: 14px +} + +.sl-pb-3\.5 { + padding-bottom: 14px +} + +.sl-pl-3\.5 { + padding-left: 14px +} + +.sl-pt-4\.5 { + padding-top: 18px +} + +.sl-pr-4\.5 { + padding-right: 18px +} + +.sl-pb-4\.5 { + padding-bottom: 18px +} + +.sl-pl-4\.5 { + padding-left: 18px +} + +.sl-placeholder::-ms-input-placeholder { + color: var(--color-text-light) +} + +.sl-placeholder::placeholder { + color: var(--color-text-light) +} + +.sl-placeholder-primary::-ms-input-placeholder { + color: #3898ff +} + +.sl-placeholder-primary::placeholder { + color: #3898ff +} + +.sl-placeholder-success::-ms-input-placeholder { + color: #0ea06f +} + +.sl-placeholder-success::placeholder { + color: #0ea06f +} + +.sl-placeholder-warning::-ms-input-placeholder { + color: #f3602b +} + +.sl-placeholder-warning::placeholder { + color: #f3602b +} + +.sl-placeholder-danger::-ms-input-placeholder { + color: #f05151 +} + +.sl-placeholder-danger::placeholder { + color: #f05151 +} + +.sl-pointer-events-none { + pointer-events: none +} + +.sl-pointer-events-auto { + pointer-events: auto +} + +.sl-static { + position: static +} + +.sl-fixed { + position: fixed +} + +.sl-absolute { + position: absolute +} + +.sl-relative { + position: relative +} + +.sl-sticky { + position: -webkit-sticky; + position: sticky +} + +.sl-resize-none { + resize: none +} + +.sl-resize-y { + resize: vertical +} + +.sl-resize-x { + resize: horizontal +} + +.sl-resize { + resize: both +} + +.sl-ring-primary { + --tw-ring-color: hsla(var(--primary-h), 80%, 61%, var(--tw-ring-opacity)) +} + +.sl-ring-success { + --tw-ring-color: hsla(var(--success-h), 84%, 34%, var(--tw-ring-opacity)) +} + +.sl-ring-warning { + --tw-ring-color: hsla(var(--warning-h), 89%, 56%, var(--tw-ring-opacity)) +} + +.sl-ring-danger { + --tw-ring-color: hsla(var(--danger-h), 84%, 63%, var(--tw-ring-opacity)) +} + +.focus\:sl-ring-primary:focus { + --tw-ring-color: hsla(var(--primary-h), 80%, 61%, var(--tw-ring-opacity)) +} + +.focus\:sl-ring-success:focus { + --tw-ring-color: hsla(var(--success-h), 84%, 34%, var(--tw-ring-opacity)) +} + +.focus\:sl-ring-warning:focus { + --tw-ring-color: hsla(var(--warning-h), 89%, 56%, var(--tw-ring-opacity)) +} + +.focus\:sl-ring-danger:focus { + --tw-ring-color: hsla(var(--danger-h), 84%, 63%, var(--tw-ring-opacity)) +} + +.sl-ring-opacity-0 { + --tw-ring-opacity: 0 +} + +.sl-ring-opacity-5 { + --tw-ring-opacity: 0.05 +} + +.sl-ring-opacity-10 { + --tw-ring-opacity: 0.1 +} + +.sl-ring-opacity-20 { + --tw-ring-opacity: 0.2 +} + +.sl-ring-opacity-30 { + --tw-ring-opacity: 0.3 +} + +.sl-ring-opacity-40 { + --tw-ring-opacity: 0.4 +} + +.sl-ring-opacity-50 { + --tw-ring-opacity: 0.5 +} + +.sl-ring-opacity-60 { + --tw-ring-opacity: 0.6 +} + +.sl-ring-opacity-70 { + --tw-ring-opacity: 0.7 +} + +.sl-ring-opacity-90 { + --tw-ring-opacity: 0.9 +} + +.sl-ring-opacity-100 { + --tw-ring-opacity: 1 +} + +.focus\:sl-ring-opacity-0:focus { + --tw-ring-opacity: 0 +} + +.focus\:sl-ring-opacity-5:focus { + --tw-ring-opacity: 0.05 +} + +.focus\:sl-ring-opacity-10:focus { + --tw-ring-opacity: 0.1 +} + +.focus\:sl-ring-opacity-20:focus { + --tw-ring-opacity: 0.2 +} + +.focus\:sl-ring-opacity-30:focus { + --tw-ring-opacity: 0.3 +} + +.focus\:sl-ring-opacity-40:focus { + --tw-ring-opacity: 0.4 +} + +.focus\:sl-ring-opacity-50:focus { + --tw-ring-opacity: 0.5 +} + +.focus\:sl-ring-opacity-60:focus { + --tw-ring-opacity: 0.6 +} + +.focus\:sl-ring-opacity-70:focus { + --tw-ring-opacity: 0.7 +} + +.focus\:sl-ring-opacity-90:focus { + --tw-ring-opacity: 0.9 +} + +.focus\:sl-ring-opacity-100:focus { + --tw-ring-opacity: 1 +} + +* { + --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgba(147, 197, 253, .5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000 +} + +.sl-ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) +} + +.sl-ring-inset { + --tw-ring-inset: inset +} + +.focus\:sl-ring:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) +} + +.focus\:sl-ring-inset:focus { + --tw-ring-inset: inset +} + +.sl-stroke-transparent { + stroke: transparent +} + +.sl-stroke-current { + stroke: currentColor +} + +.sl-stroke-lighten-100 { + stroke: var(--color-lighten-100) +} + +.sl-stroke-darken-100 { + stroke: var(--color-darken-100) +} + +.sl-stroke-primary { + stroke: var(--color-primary) +} + +.sl-stroke-primary-tint { + stroke: var(--color-primary-tint) +} + +.sl-stroke-primary-light { + stroke: var(--color-primary-light) +} + +.sl-stroke-primary-dark { + stroke: var(--color-primary-dark) +} + +.sl-stroke-primary-darker { + stroke: var(--color-primary-darker) +} + +.sl-stroke-success { + stroke: var(--color-success) +} + +.sl-stroke-success-tint { + stroke: var(--color-success-tint) +} + +.sl-stroke-success-light { + stroke: var(--color-success-light) +} + +.sl-stroke-success-dark { + stroke: var(--color-success-dark) +} + +.sl-stroke-success-darker { + stroke: var(--color-success-darker) +} + +.sl-stroke-warning { + stroke: var(--color-warning) +} + +.sl-stroke-warning-tint { + stroke: var(--color-warning-tint) +} + +.sl-stroke-warning-light { + stroke: var(--color-warning-light) +} + +.sl-stroke-warning-dark { + stroke: var(--color-warning-dark) +} + +.sl-stroke-warning-darker { + stroke: var(--color-warning-darker) +} + +.sl-stroke-danger { + stroke: var(--color-danger) +} + +.sl-stroke-danger-tint { + stroke: var(--color-danger-tint) +} + +.sl-stroke-danger-light { + stroke: var(--color-danger-light) +} + +.sl-stroke-danger-dark { + stroke: var(--color-danger-dark) +} + +.sl-stroke-danger-darker { + stroke: var(--color-danger-darker) +} + +.sl-stroke-code { + stroke: var(--color-code) +} + +.sl-stroke-on-code { + stroke: var(--color-on-code) +} + +.sl-stroke-on-primary { + stroke: var(--color-on-primary) +} + +.sl-stroke-on-success { + stroke: var(--color-on-success) +} + +.sl-stroke-on-warning { + stroke: var(--color-on-warning) +} + +.sl-stroke-on-danger { + stroke: var(--color-on-danger) +} + +.sl-stroke-text { + stroke: var(--color-text) +} + +.sl-table-auto { + table-layout: auto +} + +.sl-table-fixed { + table-layout: fixed +} + +.sl-text-left { + text-align: left +} + +.sl-text-center { + text-align: center +} + +.sl-text-right { + text-align: right +} + +.sl-text-justify { + text-align: justify +} + +.sl-text-transparent { + color: transparent +} + +.sl-text-current { + color: currentColor +} + +.sl-text-lighten-100 { + color: var(--color-lighten-100) +} + +.sl-text-darken-100 { + color: var(--color-darken-100) +} + +.sl-text-primary { + color: var(--color-primary) +} + +.sl-text-primary-tint { + color: var(--color-primary-tint) +} + +.sl-text-primary-light { + color: var(--color-primary-light) +} + +.sl-text-primary-dark { + color: var(--color-primary-dark) +} + +.sl-text-primary-darker { + color: var(--color-primary-darker) +} + +.sl-text-success { + color: var(--color-success) +} + +.sl-text-success-tint { + color: var(--color-success-tint) +} + +.sl-text-success-light { + color: var(--color-success-light) +} + +.sl-text-success-dark { + color: var(--color-success-dark) +} + +.sl-text-success-darker { + color: var(--color-success-darker) +} + +.sl-text-warning { + color: var(--color-warning) +} + +.sl-text-warning-tint { + color: var(--color-warning-tint) +} + +.sl-text-warning-light { + color: var(--color-warning-light) +} + +.sl-text-warning-dark { + color: var(--color-warning-dark) +} + +.sl-text-warning-darker { + color: var(--color-warning-darker) +} + +.sl-text-danger { + color: var(--color-danger) +} + +.sl-text-danger-tint { + color: var(--color-danger-tint) +} + +.sl-text-danger-light { + color: var(--color-danger-light) +} + +.sl-text-danger-dark { + color: var(--color-danger-dark) +} + +.sl-text-danger-darker { + color: var(--color-danger-darker) +} + +.sl-text-code { + color: var(--color-code) +} + +.sl-text-on-code { + color: var(--color-on-code) +} + +.sl-text-on-primary { + color: var(--color-on-primary) +} + +.sl-text-on-success { + color: var(--color-on-success) +} + +.sl-text-on-warning { + color: var(--color-on-warning) +} + +.sl-text-on-danger { + color: var(--color-on-danger) +} + +.sl-text-body { + color: var(--color-text) +} + +.sl-text-muted { + color: var(--color-text-muted) +} + +.sl-text-light { + color: var(--color-text-light) +} + +.sl-text-heading { + color: var(--color-text-heading) +} + +.sl-text-paragraph { + color: var(--color-text-paragraph) +} + +.sl-text-canvas-50 { + color: var(--color-canvas-50) +} + +.sl-text-canvas-100 { + color: var(--color-canvas-100) +} + +.sl-text-canvas-200 { + color: var(--color-canvas-200) +} + +.sl-text-canvas-300 { + color: var(--color-canvas-300) +} + +.sl-text-canvas-pure { + color: var(--color-canvas-pure) +} + +.sl-text-canvas { + color: var(--color-canvas) +} + +.sl-text-canvas-dialog { + color: var(--color-canvas-dialog) +} + +.sl-text-link { + color: var(--color-link) +} + +.sl-text-link-dark { + color: var(--color-link-dark) +} + +.hover\:sl-text-transparent:hover { + color: transparent +} + +.hover\:sl-text-current:hover { + color: currentColor +} + +.hover\:sl-text-lighten-100:hover { + color: var(--color-lighten-100) +} + +.hover\:sl-text-darken-100:hover { + color: var(--color-darken-100) +} + +.hover\:sl-text-primary:hover { + color: var(--color-primary) +} + +.hover\:sl-text-primary-tint:hover { + color: var(--color-primary-tint) +} + +.hover\:sl-text-primary-light:hover { + color: var(--color-primary-light) +} + +.hover\:sl-text-primary-dark:hover { + color: var(--color-primary-dark) +} + +.hover\:sl-text-primary-darker:hover { + color: var(--color-primary-darker) +} + +.hover\:sl-text-success:hover { + color: var(--color-success) +} + +.hover\:sl-text-success-tint:hover { + color: var(--color-success-tint) +} + +.hover\:sl-text-success-light:hover { + color: var(--color-success-light) +} + +.hover\:sl-text-success-dark:hover { + color: var(--color-success-dark) +} + +.hover\:sl-text-success-darker:hover { + color: var(--color-success-darker) +} + +.hover\:sl-text-warning:hover { + color: var(--color-warning) +} + +.hover\:sl-text-warning-tint:hover { + color: var(--color-warning-tint) +} + +.hover\:sl-text-warning-light:hover { + color: var(--color-warning-light) +} + +.hover\:sl-text-warning-dark:hover { + color: var(--color-warning-dark) +} + +.hover\:sl-text-warning-darker:hover { + color: var(--color-warning-darker) +} + +.hover\:sl-text-danger:hover { + color: var(--color-danger) +} + +.hover\:sl-text-danger-tint:hover { + color: var(--color-danger-tint) +} + +.hover\:sl-text-danger-light:hover { + color: var(--color-danger-light) +} + +.hover\:sl-text-danger-dark:hover { + color: var(--color-danger-dark) +} + +.hover\:sl-text-danger-darker:hover { + color: var(--color-danger-darker) +} + +.hover\:sl-text-code:hover { + color: var(--color-code) +} + +.hover\:sl-text-on-code:hover { + color: var(--color-on-code) +} + +.hover\:sl-text-on-primary:hover { + color: var(--color-on-primary) +} + +.hover\:sl-text-on-success:hover { + color: var(--color-on-success) +} + +.hover\:sl-text-on-warning:hover { + color: var(--color-on-warning) +} + +.hover\:sl-text-on-danger:hover { + color: var(--color-on-danger) +} + +.hover\:sl-text-body:hover { + color: var(--color-text) +} + +.hover\:sl-text-muted:hover { + color: var(--color-text-muted) +} + +.hover\:sl-text-light:hover { + color: var(--color-text-light) +} + +.hover\:sl-text-heading:hover { + color: var(--color-text-heading) +} + +.hover\:sl-text-paragraph:hover { + color: var(--color-text-paragraph) +} + +.hover\:sl-text-canvas-50:hover { + color: var(--color-canvas-50) +} + +.hover\:sl-text-canvas-100:hover { + color: var(--color-canvas-100) +} + +.hover\:sl-text-canvas-200:hover { + color: var(--color-canvas-200) +} + +.hover\:sl-text-canvas-300:hover { + color: var(--color-canvas-300) +} + +.hover\:sl-text-canvas-pure:hover { + color: var(--color-canvas-pure) +} + +.hover\:sl-text-canvas:hover { + color: var(--color-canvas) +} + +.hover\:sl-text-canvas-dialog:hover { + color: var(--color-canvas-dialog) +} + +.hover\:sl-text-link:hover { + color: var(--color-link) +} + +.hover\:sl-text-link-dark:hover { + color: var(--color-link-dark) +} + +.focus\:sl-text-transparent:focus { + color: transparent +} + +.focus\:sl-text-current:focus { + color: currentColor +} + +.focus\:sl-text-lighten-100:focus { + color: var(--color-lighten-100) +} + +.focus\:sl-text-darken-100:focus { + color: var(--color-darken-100) +} + +.focus\:sl-text-primary:focus { + color: var(--color-primary) +} + +.focus\:sl-text-primary-tint:focus { + color: var(--color-primary-tint) +} + +.focus\:sl-text-primary-light:focus { + color: var(--color-primary-light) +} + +.focus\:sl-text-primary-dark:focus { + color: var(--color-primary-dark) +} + +.focus\:sl-text-primary-darker:focus { + color: var(--color-primary-darker) +} + +.focus\:sl-text-success:focus { + color: var(--color-success) +} + +.focus\:sl-text-success-tint:focus { + color: var(--color-success-tint) +} + +.focus\:sl-text-success-light:focus { + color: var(--color-success-light) +} + +.focus\:sl-text-success-dark:focus { + color: var(--color-success-dark) +} + +.focus\:sl-text-success-darker:focus { + color: var(--color-success-darker) +} + +.focus\:sl-text-warning:focus { + color: var(--color-warning) +} + +.focus\:sl-text-warning-tint:focus { + color: var(--color-warning-tint) +} + +.focus\:sl-text-warning-light:focus { + color: var(--color-warning-light) +} + +.focus\:sl-text-warning-dark:focus { + color: var(--color-warning-dark) +} + +.focus\:sl-text-warning-darker:focus { + color: var(--color-warning-darker) +} + +.focus\:sl-text-danger:focus { + color: var(--color-danger) +} + +.focus\:sl-text-danger-tint:focus { + color: var(--color-danger-tint) +} + +.focus\:sl-text-danger-light:focus { + color: var(--color-danger-light) +} + +.focus\:sl-text-danger-dark:focus { + color: var(--color-danger-dark) +} + +.focus\:sl-text-danger-darker:focus { + color: var(--color-danger-darker) +} + +.focus\:sl-text-code:focus { + color: var(--color-code) +} + +.focus\:sl-text-on-code:focus { + color: var(--color-on-code) +} + +.focus\:sl-text-on-primary:focus { + color: var(--color-on-primary) +} + +.focus\:sl-text-on-success:focus { + color: var(--color-on-success) +} + +.focus\:sl-text-on-warning:focus { + color: var(--color-on-warning) +} + +.focus\:sl-text-on-danger:focus { + color: var(--color-on-danger) +} + +.focus\:sl-text-body:focus { + color: var(--color-text) +} + +.focus\:sl-text-muted:focus { + color: var(--color-text-muted) +} + +.focus\:sl-text-light:focus { + color: var(--color-text-light) +} + +.focus\:sl-text-heading:focus { + color: var(--color-text-heading) +} + +.focus\:sl-text-paragraph:focus { + color: var(--color-text-paragraph) +} + +.focus\:sl-text-canvas-50:focus { + color: var(--color-canvas-50) +} + +.focus\:sl-text-canvas-100:focus { + color: var(--color-canvas-100) +} + +.focus\:sl-text-canvas-200:focus { + color: var(--color-canvas-200) +} + +.focus\:sl-text-canvas-300:focus { + color: var(--color-canvas-300) +} + +.focus\:sl-text-canvas-pure:focus { + color: var(--color-canvas-pure) +} + +.focus\:sl-text-canvas:focus { + color: var(--color-canvas) +} + +.focus\:sl-text-canvas-dialog:focus { + color: var(--color-canvas-dialog) +} + +.focus\:sl-text-link:focus { + color: var(--color-link) +} + +.focus\:sl-text-link-dark:focus { + color: var(--color-link-dark) +} + +.disabled\:sl-text-transparent:disabled { + color: transparent +} + +.disabled\:sl-text-current:disabled { + color: currentColor +} + +.disabled\:sl-text-lighten-100:disabled { + color: var(--color-lighten-100) +} + +.disabled\:sl-text-darken-100:disabled { + color: var(--color-darken-100) +} + +.disabled\:sl-text-primary:disabled { + color: var(--color-primary) +} + +.disabled\:sl-text-primary-tint:disabled { + color: var(--color-primary-tint) +} + +.disabled\:sl-text-primary-light:disabled { + color: var(--color-primary-light) +} + +.disabled\:sl-text-primary-dark:disabled { + color: var(--color-primary-dark) +} + +.disabled\:sl-text-primary-darker:disabled { + color: var(--color-primary-darker) +} + +.disabled\:sl-text-success:disabled { + color: var(--color-success) +} + +.disabled\:sl-text-success-tint:disabled { + color: var(--color-success-tint) +} + +.disabled\:sl-text-success-light:disabled { + color: var(--color-success-light) +} + +.disabled\:sl-text-success-dark:disabled { + color: var(--color-success-dark) +} + +.disabled\:sl-text-success-darker:disabled { + color: var(--color-success-darker) +} + +.disabled\:sl-text-warning:disabled { + color: var(--color-warning) +} + +.disabled\:sl-text-warning-tint:disabled { + color: var(--color-warning-tint) +} + +.disabled\:sl-text-warning-light:disabled { + color: var(--color-warning-light) +} + +.disabled\:sl-text-warning-dark:disabled { + color: var(--color-warning-dark) +} + +.disabled\:sl-text-warning-darker:disabled { + color: var(--color-warning-darker) +} + +.disabled\:sl-text-danger:disabled { + color: var(--color-danger) +} + +.disabled\:sl-text-danger-tint:disabled { + color: var(--color-danger-tint) +} + +.disabled\:sl-text-danger-light:disabled { + color: var(--color-danger-light) +} + +.disabled\:sl-text-danger-dark:disabled { + color: var(--color-danger-dark) +} + +.disabled\:sl-text-danger-darker:disabled { + color: var(--color-danger-darker) +} + +.disabled\:sl-text-code:disabled { + color: var(--color-code) +} + +.disabled\:sl-text-on-code:disabled { + color: var(--color-on-code) +} + +.disabled\:sl-text-on-primary:disabled { + color: var(--color-on-primary) +} + +.disabled\:sl-text-on-success:disabled { + color: var(--color-on-success) +} + +.disabled\:sl-text-on-warning:disabled { + color: var(--color-on-warning) +} + +.disabled\:sl-text-on-danger:disabled { + color: var(--color-on-danger) +} + +.disabled\:sl-text-body:disabled { + color: var(--color-text) +} + +.disabled\:sl-text-muted:disabled { + color: var(--color-text-muted) +} + +.disabled\:sl-text-light:disabled { + color: var(--color-text-light) +} + +.disabled\:sl-text-heading:disabled { + color: var(--color-text-heading) +} + +.disabled\:sl-text-paragraph:disabled { + color: var(--color-text-paragraph) +} + +.disabled\:sl-text-canvas-50:disabled { + color: var(--color-canvas-50) +} + +.disabled\:sl-text-canvas-100:disabled { + color: var(--color-canvas-100) +} + +.disabled\:sl-text-canvas-200:disabled { + color: var(--color-canvas-200) +} + +.disabled\:sl-text-canvas-300:disabled { + color: var(--color-canvas-300) +} + +.disabled\:sl-text-canvas-pure:disabled { + color: var(--color-canvas-pure) +} + +.disabled\:sl-text-canvas:disabled { + color: var(--color-canvas) +} + +.disabled\:sl-text-canvas-dialog:disabled { + color: var(--color-canvas-dialog) +} + +.disabled\:sl-text-link:disabled { + color: var(--color-link) +} + +.disabled\:sl-text-link-dark:disabled { + color: var(--color-link-dark) +} + +.sl-underline { + text-decoration: underline +} + +.sl-line-through { + text-decoration: line-through +} + +.sl-no-underline { + text-decoration: none +} + +.sl-truncate { + overflow: hidden; + white-space: nowrap +} + +.sl-overflow-ellipsis, .sl-truncate { + text-overflow: ellipsis +} + +.sl-overflow-clip { + text-overflow: clip +} + +.sl-uppercase { + text-transform: uppercase +} + +.sl-lowercase { + text-transform: lowercase +} + +.sl-capitalize { + text-transform: capitalize +} + +.sl-normal-case { + text-transform: none +} + +.sl-transform { + transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.sl-transform, .sl-transform-gpu { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1 +} + +.sl-transform-gpu { + transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.sl-transform-none { + transform: none +} + +.sl-delay-75 { + transition-delay: 75ms +} + +.sl-delay-150 { + transition-delay: .15s +} + +.sl-delay-300 { + transition-delay: .3s +} + +.sl-delay-500 { + transition-delay: .5s +} + +.sl-delay-1000 { + transition-delay: 1s +} + +.sl-duration-75 { + transition-duration: 75ms +} + +.sl-duration-150 { + transition-duration: .15s +} + +.sl-duration-300 { + transition-duration: .3s +} + +.sl-duration-500 { + transition-duration: .5s +} + +.sl-duration-1000 { + transition-duration: 1s +} + +.sl-transition { + transition-duration: .15s; + transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform; + transition-timing-function: cubic-bezier(.4, 0, .2, 1) +} + +.sl-translate-x-0 { + --tw-translate-x: 0px +} + +.sl-translate-x-1 { + --tw-translate-x: 4px +} + +.sl-translate-x-2 { + --tw-translate-x: 8px +} + +.sl-translate-x-3 { + --tw-translate-x: 12px +} + +.sl-translate-x-4 { + --tw-translate-x: 16px +} + +.sl-translate-x-5 { + --tw-translate-x: 20px +} + +.sl-translate-x-6 { + --tw-translate-x: 24px +} + +.sl-translate-x-7 { + --tw-translate-x: 28px +} + +.sl-translate-x-8 { + --tw-translate-x: 32px +} + +.sl-translate-x-9 { + --tw-translate-x: 36px +} + +.sl-translate-x-10 { + --tw-translate-x: 40px +} + +.sl-translate-x-11 { + --tw-translate-x: 44px +} + +.sl-translate-x-12 { + --tw-translate-x: 48px +} + +.sl-translate-x-14 { + --tw-translate-x: 56px +} + +.sl-translate-x-16 { + --tw-translate-x: 64px +} + +.sl-translate-x-20 { + --tw-translate-x: 80px +} + +.sl-translate-x-24 { + --tw-translate-x: 96px +} + +.sl-translate-x-28 { + --tw-translate-x: 112px +} + +.sl-translate-x-32 { + --tw-translate-x: 128px +} + +.sl-translate-x-36 { + --tw-translate-x: 144px +} + +.sl-translate-x-40 { + --tw-translate-x: 160px +} + +.sl-translate-x-44 { + --tw-translate-x: 176px +} + +.sl-translate-x-48 { + --tw-translate-x: 192px +} + +.sl-translate-x-52 { + --tw-translate-x: 208px +} + +.sl-translate-x-56 { + --tw-translate-x: 224px +} + +.sl-translate-x-60 { + --tw-translate-x: 240px +} + +.sl-translate-x-64 { + --tw-translate-x: 256px +} + +.sl-translate-x-72 { + --tw-translate-x: 288px +} + +.sl-translate-x-80 { + --tw-translate-x: 320px +} + +.sl-translate-x-96 { + --tw-translate-x: 384px +} + +.sl-translate-x-px { + --tw-translate-x: 1px +} + +.sl-translate-x-0\.5 { + --tw-translate-x: 2px +} + +.sl-translate-x-1\.5 { + --tw-translate-x: 6px +} + +.sl-translate-x-2\.5 { + --tw-translate-x: 10px +} + +.sl-translate-x-3\.5 { + --tw-translate-x: 14px +} + +.sl-translate-x-4\.5 { + --tw-translate-x: 18px +} + +.sl--translate-x-0 { + --tw-translate-x: 0px +} + +.sl--translate-x-1 { + --tw-translate-x: -4px +} + +.sl--translate-x-2 { + --tw-translate-x: -8px +} + +.sl--translate-x-3 { + --tw-translate-x: -12px +} + +.sl--translate-x-4 { + --tw-translate-x: -16px +} + +.sl--translate-x-5 { + --tw-translate-x: -20px +} + +.sl--translate-x-6 { + --tw-translate-x: -24px +} + +.sl--translate-x-7 { + --tw-translate-x: -28px +} + +.sl--translate-x-8 { + --tw-translate-x: -32px +} + +.sl--translate-x-9 { + --tw-translate-x: -36px +} + +.sl--translate-x-10 { + --tw-translate-x: -40px +} + +.sl--translate-x-11 { + --tw-translate-x: -44px +} + +.sl--translate-x-12 { + --tw-translate-x: -48px +} + +.sl--translate-x-14 { + --tw-translate-x: -56px +} + +.sl--translate-x-16 { + --tw-translate-x: -64px +} + +.sl--translate-x-20 { + --tw-translate-x: -80px +} + +.sl--translate-x-24 { + --tw-translate-x: -96px +} + +.sl--translate-x-28 { + --tw-translate-x: -112px +} + +.sl--translate-x-32 { + --tw-translate-x: -128px +} + +.sl--translate-x-36 { + --tw-translate-x: -144px +} + +.sl--translate-x-40 { + --tw-translate-x: -160px +} + +.sl--translate-x-44 { + --tw-translate-x: -176px +} + +.sl--translate-x-48 { + --tw-translate-x: -192px +} + +.sl--translate-x-52 { + --tw-translate-x: -208px +} + +.sl--translate-x-56 { + --tw-translate-x: -224px +} + +.sl--translate-x-60 { + --tw-translate-x: -240px +} + +.sl--translate-x-64 { + --tw-translate-x: -256px +} + +.sl--translate-x-72 { + --tw-translate-x: -288px +} + +.sl--translate-x-80 { + --tw-translate-x: -320px +} + +.sl--translate-x-96 { + --tw-translate-x: -384px +} + +.sl--translate-x-px { + --tw-translate-x: -1px +} + +.sl--translate-x-0\.5 { + --tw-translate-x: -2px +} + +.sl--translate-x-1\.5 { + --tw-translate-x: -6px +} + +.sl--translate-x-2\.5 { + --tw-translate-x: -10px +} + +.sl--translate-x-3\.5 { + --tw-translate-x: -14px +} + +.sl--translate-x-4\.5 { + --tw-translate-x: -18px +} + +.sl-translate-y-0 { + --tw-translate-y: 0px +} + +.sl-translate-y-1 { + --tw-translate-y: 4px +} + +.sl-translate-y-2 { + --tw-translate-y: 8px +} + +.sl-translate-y-3 { + --tw-translate-y: 12px +} + +.sl-translate-y-4 { + --tw-translate-y: 16px +} + +.sl-translate-y-5 { + --tw-translate-y: 20px +} + +.sl-translate-y-6 { + --tw-translate-y: 24px +} + +.sl-translate-y-7 { + --tw-translate-y: 28px +} + +.sl-translate-y-8 { + --tw-translate-y: 32px +} + +.sl-translate-y-9 { + --tw-translate-y: 36px +} + +.sl-translate-y-10 { + --tw-translate-y: 40px +} + +.sl-translate-y-11 { + --tw-translate-y: 44px +} + +.sl-translate-y-12 { + --tw-translate-y: 48px +} + +.sl-translate-y-14 { + --tw-translate-y: 56px +} + +.sl-translate-y-16 { + --tw-translate-y: 64px +} + +.sl-translate-y-20 { + --tw-translate-y: 80px +} + +.sl-translate-y-24 { + --tw-translate-y: 96px +} + +.sl-translate-y-28 { + --tw-translate-y: 112px +} + +.sl-translate-y-32 { + --tw-translate-y: 128px +} + +.sl-translate-y-36 { + --tw-translate-y: 144px +} + +.sl-translate-y-40 { + --tw-translate-y: 160px +} + +.sl-translate-y-44 { + --tw-translate-y: 176px +} + +.sl-translate-y-48 { + --tw-translate-y: 192px +} + +.sl-translate-y-52 { + --tw-translate-y: 208px +} + +.sl-translate-y-56 { + --tw-translate-y: 224px +} + +.sl-translate-y-60 { + --tw-translate-y: 240px +} + +.sl-translate-y-64 { + --tw-translate-y: 256px +} + +.sl-translate-y-72 { + --tw-translate-y: 288px +} + +.sl-translate-y-80 { + --tw-translate-y: 320px +} + +.sl-translate-y-96 { + --tw-translate-y: 384px +} + +.sl-translate-y-px { + --tw-translate-y: 1px +} + +.sl-translate-y-0\.5 { + --tw-translate-y: 2px +} + +.sl-translate-y-1\.5 { + --tw-translate-y: 6px +} + +.sl-translate-y-2\.5 { + --tw-translate-y: 10px +} + +.sl-translate-y-3\.5 { + --tw-translate-y: 14px +} + +.sl-translate-y-4\.5 { + --tw-translate-y: 18px +} + +.sl--translate-y-0 { + --tw-translate-y: 0px +} + +.sl--translate-y-1 { + --tw-translate-y: -4px +} + +.sl--translate-y-2 { + --tw-translate-y: -8px +} + +.sl--translate-y-3 { + --tw-translate-y: -12px +} + +.sl--translate-y-4 { + --tw-translate-y: -16px +} + +.sl--translate-y-5 { + --tw-translate-y: -20px +} + +.sl--translate-y-6 { + --tw-translate-y: -24px +} + +.sl--translate-y-7 { + --tw-translate-y: -28px +} + +.sl--translate-y-8 { + --tw-translate-y: -32px +} + +.sl--translate-y-9 { + --tw-translate-y: -36px +} + +.sl--translate-y-10 { + --tw-translate-y: -40px +} + +.sl--translate-y-11 { + --tw-translate-y: -44px +} + +.sl--translate-y-12 { + --tw-translate-y: -48px +} + +.sl--translate-y-14 { + --tw-translate-y: -56px +} + +.sl--translate-y-16 { + --tw-translate-y: -64px +} + +.sl--translate-y-20 { + --tw-translate-y: -80px +} + +.sl--translate-y-24 { + --tw-translate-y: -96px +} + +.sl--translate-y-28 { + --tw-translate-y: -112px +} + +.sl--translate-y-32 { + --tw-translate-y: -128px +} + +.sl--translate-y-36 { + --tw-translate-y: -144px +} + +.sl--translate-y-40 { + --tw-translate-y: -160px +} + +.sl--translate-y-44 { + --tw-translate-y: -176px +} + +.sl--translate-y-48 { + --tw-translate-y: -192px +} + +.sl--translate-y-52 { + --tw-translate-y: -208px +} + +.sl--translate-y-56 { + --tw-translate-y: -224px +} + +.sl--translate-y-60 { + --tw-translate-y: -240px +} + +.sl--translate-y-64 { + --tw-translate-y: -256px +} + +.sl--translate-y-72 { + --tw-translate-y: -288px +} + +.sl--translate-y-80 { + --tw-translate-y: -320px +} + +.sl--translate-y-96 { + --tw-translate-y: -384px +} + +.sl--translate-y-px { + --tw-translate-y: -1px +} + +.sl--translate-y-0\.5 { + --tw-translate-y: -2px +} + +.sl--translate-y-1\.5 { + --tw-translate-y: -6px +} + +.sl--translate-y-2\.5 { + --tw-translate-y: -10px +} + +.sl--translate-y-3\.5 { + --tw-translate-y: -14px +} + +.sl--translate-y-4\.5 { + --tw-translate-y: -18px +} + +.hover\:sl-translate-x-0:hover { + --tw-translate-x: 0px +} + +.hover\:sl-translate-x-1:hover { + --tw-translate-x: 4px +} + +.hover\:sl-translate-x-2:hover { + --tw-translate-x: 8px +} + +.hover\:sl-translate-x-3:hover { + --tw-translate-x: 12px +} + +.hover\:sl-translate-x-4:hover { + --tw-translate-x: 16px +} + +.hover\:sl-translate-x-5:hover { + --tw-translate-x: 20px +} + +.hover\:sl-translate-x-6:hover { + --tw-translate-x: 24px +} + +.hover\:sl-translate-x-7:hover { + --tw-translate-x: 28px +} + +.hover\:sl-translate-x-8:hover { + --tw-translate-x: 32px +} + +.hover\:sl-translate-x-9:hover { + --tw-translate-x: 36px +} + +.hover\:sl-translate-x-10:hover { + --tw-translate-x: 40px +} + +.hover\:sl-translate-x-11:hover { + --tw-translate-x: 44px +} + +.hover\:sl-translate-x-12:hover { + --tw-translate-x: 48px +} + +.hover\:sl-translate-x-14:hover { + --tw-translate-x: 56px +} + +.hover\:sl-translate-x-16:hover { + --tw-translate-x: 64px +} + +.hover\:sl-translate-x-20:hover { + --tw-translate-x: 80px +} + +.hover\:sl-translate-x-24:hover { + --tw-translate-x: 96px +} + +.hover\:sl-translate-x-28:hover { + --tw-translate-x: 112px +} + +.hover\:sl-translate-x-32:hover { + --tw-translate-x: 128px +} + +.hover\:sl-translate-x-36:hover { + --tw-translate-x: 144px +} + +.hover\:sl-translate-x-40:hover { + --tw-translate-x: 160px +} + +.hover\:sl-translate-x-44:hover { + --tw-translate-x: 176px +} + +.hover\:sl-translate-x-48:hover { + --tw-translate-x: 192px +} + +.hover\:sl-translate-x-52:hover { + --tw-translate-x: 208px +} + +.hover\:sl-translate-x-56:hover { + --tw-translate-x: 224px +} + +.hover\:sl-translate-x-60:hover { + --tw-translate-x: 240px +} + +.hover\:sl-translate-x-64:hover { + --tw-translate-x: 256px +} + +.hover\:sl-translate-x-72:hover { + --tw-translate-x: 288px +} + +.hover\:sl-translate-x-80:hover { + --tw-translate-x: 320px +} + +.hover\:sl-translate-x-96:hover { + --tw-translate-x: 384px +} + +.hover\:sl-translate-x-px:hover { + --tw-translate-x: 1px +} + +.hover\:sl-translate-x-0\.5:hover { + --tw-translate-x: 2px +} + +.hover\:sl-translate-x-1\.5:hover { + --tw-translate-x: 6px +} + +.hover\:sl-translate-x-2\.5:hover { + --tw-translate-x: 10px +} + +.hover\:sl-translate-x-3\.5:hover { + --tw-translate-x: 14px +} + +.hover\:sl-translate-x-4\.5:hover { + --tw-translate-x: 18px +} + +.hover\:sl--translate-x-0:hover { + --tw-translate-x: 0px +} + +.hover\:sl--translate-x-1:hover { + --tw-translate-x: -4px +} + +.hover\:sl--translate-x-2:hover { + --tw-translate-x: -8px +} + +.hover\:sl--translate-x-3:hover { + --tw-translate-x: -12px +} + +.hover\:sl--translate-x-4:hover { + --tw-translate-x: -16px +} + +.hover\:sl--translate-x-5:hover { + --tw-translate-x: -20px +} + +.hover\:sl--translate-x-6:hover { + --tw-translate-x: -24px +} + +.hover\:sl--translate-x-7:hover { + --tw-translate-x: -28px +} + +.hover\:sl--translate-x-8:hover { + --tw-translate-x: -32px +} + +.hover\:sl--translate-x-9:hover { + --tw-translate-x: -36px +} + +.hover\:sl--translate-x-10:hover { + --tw-translate-x: -40px +} + +.hover\:sl--translate-x-11:hover { + --tw-translate-x: -44px +} + +.hover\:sl--translate-x-12:hover { + --tw-translate-x: -48px +} + +.hover\:sl--translate-x-14:hover { + --tw-translate-x: -56px +} + +.hover\:sl--translate-x-16:hover { + --tw-translate-x: -64px +} + +.hover\:sl--translate-x-20:hover { + --tw-translate-x: -80px +} + +.hover\:sl--translate-x-24:hover { + --tw-translate-x: -96px +} + +.hover\:sl--translate-x-28:hover { + --tw-translate-x: -112px +} + +.hover\:sl--translate-x-32:hover { + --tw-translate-x: -128px +} + +.hover\:sl--translate-x-36:hover { + --tw-translate-x: -144px +} + +.hover\:sl--translate-x-40:hover { + --tw-translate-x: -160px +} + +.hover\:sl--translate-x-44:hover { + --tw-translate-x: -176px +} + +.hover\:sl--translate-x-48:hover { + --tw-translate-x: -192px +} + +.hover\:sl--translate-x-52:hover { + --tw-translate-x: -208px +} + +.hover\:sl--translate-x-56:hover { + --tw-translate-x: -224px +} + +.hover\:sl--translate-x-60:hover { + --tw-translate-x: -240px +} + +.hover\:sl--translate-x-64:hover { + --tw-translate-x: -256px +} + +.hover\:sl--translate-x-72:hover { + --tw-translate-x: -288px +} + +.hover\:sl--translate-x-80:hover { + --tw-translate-x: -320px +} + +.hover\:sl--translate-x-96:hover { + --tw-translate-x: -384px +} + +.hover\:sl--translate-x-px:hover { + --tw-translate-x: -1px +} + +.hover\:sl--translate-x-0\.5:hover { + --tw-translate-x: -2px +} + +.hover\:sl--translate-x-1\.5:hover { + --tw-translate-x: -6px +} + +.hover\:sl--translate-x-2\.5:hover { + --tw-translate-x: -10px +} + +.hover\:sl--translate-x-3\.5:hover { + --tw-translate-x: -14px +} + +.hover\:sl--translate-x-4\.5:hover { + --tw-translate-x: -18px +} + +.hover\:sl-translate-y-0:hover { + --tw-translate-y: 0px +} + +.hover\:sl-translate-y-1:hover { + --tw-translate-y: 4px +} + +.hover\:sl-translate-y-2:hover { + --tw-translate-y: 8px +} + +.hover\:sl-translate-y-3:hover { + --tw-translate-y: 12px +} + +.hover\:sl-translate-y-4:hover { + --tw-translate-y: 16px +} + +.hover\:sl-translate-y-5:hover { + --tw-translate-y: 20px +} + +.hover\:sl-translate-y-6:hover { + --tw-translate-y: 24px +} + +.hover\:sl-translate-y-7:hover { + --tw-translate-y: 28px +} + +.hover\:sl-translate-y-8:hover { + --tw-translate-y: 32px +} + +.hover\:sl-translate-y-9:hover { + --tw-translate-y: 36px +} + +.hover\:sl-translate-y-10:hover { + --tw-translate-y: 40px +} + +.hover\:sl-translate-y-11:hover { + --tw-translate-y: 44px +} + +.hover\:sl-translate-y-12:hover { + --tw-translate-y: 48px +} + +.hover\:sl-translate-y-14:hover { + --tw-translate-y: 56px +} + +.hover\:sl-translate-y-16:hover { + --tw-translate-y: 64px +} + +.hover\:sl-translate-y-20:hover { + --tw-translate-y: 80px +} + +.hover\:sl-translate-y-24:hover { + --tw-translate-y: 96px +} + +.hover\:sl-translate-y-28:hover { + --tw-translate-y: 112px +} + +.hover\:sl-translate-y-32:hover { + --tw-translate-y: 128px +} + +.hover\:sl-translate-y-36:hover { + --tw-translate-y: 144px +} + +.hover\:sl-translate-y-40:hover { + --tw-translate-y: 160px +} + +.hover\:sl-translate-y-44:hover { + --tw-translate-y: 176px +} + +.hover\:sl-translate-y-48:hover { + --tw-translate-y: 192px +} + +.hover\:sl-translate-y-52:hover { + --tw-translate-y: 208px +} + +.hover\:sl-translate-y-56:hover { + --tw-translate-y: 224px +} + +.hover\:sl-translate-y-60:hover { + --tw-translate-y: 240px +} + +.hover\:sl-translate-y-64:hover { + --tw-translate-y: 256px +} + +.hover\:sl-translate-y-72:hover { + --tw-translate-y: 288px +} + +.hover\:sl-translate-y-80:hover { + --tw-translate-y: 320px +} + +.hover\:sl-translate-y-96:hover { + --tw-translate-y: 384px +} + +.hover\:sl-translate-y-px:hover { + --tw-translate-y: 1px +} + +.hover\:sl-translate-y-0\.5:hover { + --tw-translate-y: 2px +} + +.hover\:sl-translate-y-1\.5:hover { + --tw-translate-y: 6px +} + +.hover\:sl-translate-y-2\.5:hover { + --tw-translate-y: 10px +} + +.hover\:sl-translate-y-3\.5:hover { + --tw-translate-y: 14px +} + +.hover\:sl-translate-y-4\.5:hover { + --tw-translate-y: 18px +} + +.hover\:sl--translate-y-0:hover { + --tw-translate-y: 0px +} + +.hover\:sl--translate-y-1:hover { + --tw-translate-y: -4px +} + +.hover\:sl--translate-y-2:hover { + --tw-translate-y: -8px +} + +.hover\:sl--translate-y-3:hover { + --tw-translate-y: -12px +} + +.hover\:sl--translate-y-4:hover { + --tw-translate-y: -16px +} + +.hover\:sl--translate-y-5:hover { + --tw-translate-y: -20px +} + +.hover\:sl--translate-y-6:hover { + --tw-translate-y: -24px +} + +.hover\:sl--translate-y-7:hover { + --tw-translate-y: -28px +} + +.hover\:sl--translate-y-8:hover { + --tw-translate-y: -32px +} + +.hover\:sl--translate-y-9:hover { + --tw-translate-y: -36px +} + +.hover\:sl--translate-y-10:hover { + --tw-translate-y: -40px +} + +.hover\:sl--translate-y-11:hover { + --tw-translate-y: -44px +} + +.hover\:sl--translate-y-12:hover { + --tw-translate-y: -48px +} + +.hover\:sl--translate-y-14:hover { + --tw-translate-y: -56px +} + +.hover\:sl--translate-y-16:hover { + --tw-translate-y: -64px +} + +.hover\:sl--translate-y-20:hover { + --tw-translate-y: -80px +} + +.hover\:sl--translate-y-24:hover { + --tw-translate-y: -96px +} + +.hover\:sl--translate-y-28:hover { + --tw-translate-y: -112px +} + +.hover\:sl--translate-y-32:hover { + --tw-translate-y: -128px +} + +.hover\:sl--translate-y-36:hover { + --tw-translate-y: -144px +} + +.hover\:sl--translate-y-40:hover { + --tw-translate-y: -160px +} + +.hover\:sl--translate-y-44:hover { + --tw-translate-y: -176px +} + +.hover\:sl--translate-y-48:hover { + --tw-translate-y: -192px +} + +.hover\:sl--translate-y-52:hover { + --tw-translate-y: -208px +} + +.hover\:sl--translate-y-56:hover { + --tw-translate-y: -224px +} + +.hover\:sl--translate-y-60:hover { + --tw-translate-y: -240px +} + +.hover\:sl--translate-y-64:hover { + --tw-translate-y: -256px +} + +.hover\:sl--translate-y-72:hover { + --tw-translate-y: -288px +} + +.hover\:sl--translate-y-80:hover { + --tw-translate-y: -320px +} + +.hover\:sl--translate-y-96:hover { + --tw-translate-y: -384px +} + +.hover\:sl--translate-y-px:hover { + --tw-translate-y: -1px +} + +.hover\:sl--translate-y-0\.5:hover { + --tw-translate-y: -2px +} + +.hover\:sl--translate-y-1\.5:hover { + --tw-translate-y: -6px +} + +.hover\:sl--translate-y-2\.5:hover { + --tw-translate-y: -10px +} + +.hover\:sl--translate-y-3\.5:hover { + --tw-translate-y: -14px +} + +.hover\:sl--translate-y-4\.5:hover { + --tw-translate-y: -18px +} + +.sl-select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +.sl-select-text { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text +} + +.sl-select-all { + -webkit-user-select: all; + -moz-user-select: all; + user-select: all +} + +.sl-select-auto { + -webkit-user-select: auto; + -moz-user-select: auto; + -ms-user-select: auto; + user-select: auto +} + +.sl-align-baseline { + vertical-align: baseline +} + +.sl-align-top { + vertical-align: top +} + +.sl-align-middle { + vertical-align: middle +} + +.sl-align-bottom { + vertical-align: bottom +} + +.sl-align-text-top { + vertical-align: text-top +} + +.sl-align-text-bottom { + vertical-align: text-bottom +} + +.sl-visible { + visibility: visible +} + +.sl-invisible { + visibility: hidden +} + +.sl-group:hover .group-hover\:sl-visible { + visibility: visible +} + +.sl-group:hover .group-hover\:sl-invisible { + visibility: hidden +} + +.sl-group:focus .group-focus\:sl-visible { + visibility: visible +} + +.sl-group:focus .group-focus\:sl-invisible { + visibility: hidden +} + +.sl-whitespace-normal { + white-space: normal +} + +.sl-whitespace-nowrap { + white-space: nowrap +} + +.sl-whitespace-pre { + white-space: pre +} + +.sl-whitespace-pre-line { + white-space: pre-line +} + +.sl-whitespace-pre-wrap { + white-space: pre-wrap +} + +.sl-w-0 { + width: 0 +} + +.sl-w-1 { + width: 4px +} + +.sl-w-2 { + width: 8px +} + +.sl-w-3 { + width: 12px +} + +.sl-w-4 { + width: 16px +} + +.sl-w-5 { + width: 20px +} + +.sl-w-6 { + width: 24px +} + +.sl-w-7 { + width: 28px +} + +.sl-w-8 { + width: 32px +} + +.sl-w-9 { + width: 36px +} + +.sl-w-10 { + width: 40px +} + +.sl-w-11 { + width: 44px +} + +.sl-w-12 { + width: 48px +} + +.sl-w-14 { + width: 56px +} + +.sl-w-16 { + width: 64px +} + +.sl-w-20 { + width: 80px +} + +.sl-w-24 { + width: 96px +} + +.sl-w-28 { + width: 112px +} + +.sl-w-32 { + width: 128px +} + +.sl-w-36 { + width: 144px +} + +.sl-w-40 { + width: 160px +} + +.sl-w-44 { + width: 176px +} + +.sl-w-48 { + width: 192px +} + +.sl-w-52 { + width: 208px +} + +.sl-w-56 { + width: 224px +} + +.sl-w-60 { + width: 240px +} + +.sl-w-64 { + width: 256px +} + +.sl-w-72 { + width: 288px +} + +.sl-w-80 { + width: 320px +} + +.sl-w-96 { + width: 384px +} + +.sl-w-auto { + width: auto +} + +.sl-w-px { + width: 1px +} + +.sl-w-0\.5 { + width: 2px +} + +.sl-w-1\.5 { + width: 6px +} + +.sl-w-2\.5 { + width: 10px +} + +.sl-w-3\.5 { + width: 14px +} + +.sl-w-4\.5 { + width: 18px +} + +.sl-w-xs { + width: 20px +} + +.sl-w-sm { + width: 24px +} + +.sl-w-md { + width: 32px +} + +.sl-w-lg { + width: 36px +} + +.sl-w-xl { + width: 44px +} + +.sl-w-2xl { + width: 52px +} + +.sl-w-3xl { + width: 60px +} + +.sl-w-1\/2 { + width: 50% +} + +.sl-w-1\/3 { + width: 33.333333% +} + +.sl-w-2\/3 { + width: 66.666667% +} + +.sl-w-1\/4 { + width: 25% +} + +.sl-w-2\/4 { + width: 50% +} + +.sl-w-3\/4 { + width: 75% +} + +.sl-w-1\/5 { + width: 20% +} + +.sl-w-2\/5 { + width: 40% +} + +.sl-w-3\/5 { + width: 60% +} + +.sl-w-4\/5 { + width: 80% +} + +.sl-w-1\/6 { + width: 16.666667% +} + +.sl-w-2\/6 { + width: 33.333333% +} + +.sl-w-3\/6 { + width: 50% +} + +.sl-w-4\/6 { + width: 66.666667% +} + +.sl-w-5\/6 { + width: 83.333333% +} + +.sl-w-full { + width: 100% +} + +.sl-w-screen { + width: 100vw +} + +.sl-w-min { + width: -moz-min-content; + width: min-content +} + +.sl-w-max { + width: -moz-max-content; + width: max-content +} + +.sl-break-normal { + overflow-wrap: normal; + word-break: normal +} + +.sl-break-words { + overflow-wrap: break-word +} + +.sl-break-all { + word-break: break-all +} + +.sl-z-0 { + z-index: 0 +} + +.sl-z-10 { + z-index: 10 +} + +.sl-z-20 { + z-index: 20 +} + +.sl-z-30 { + z-index: 30 +} + +.sl-z-40 { + z-index: 40 +} + +.sl-z-50 { + z-index: 50 +} + +.sl-z-auto { + z-index: auto +} + +.focus\:sl-z-0:focus { + z-index: 0 +} + +.focus\:sl-z-10:focus { + z-index: 10 +} + +.focus\:sl-z-20:focus { + z-index: 20 +} + +.focus\:sl-z-30:focus { + z-index: 30 +} + +.focus\:sl-z-40:focus { + z-index: 40 +} + +.focus\:sl-z-50:focus { + z-index: 50 +} + +.focus\:sl-z-auto:focus { + z-index: auto +} + +:root { + --font-prose: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-ui: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: "SF Mono", ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --font-code: var(--font-mono); + --fs-paragraph-leading: 22px; + --fs-paragraph: 16px; + --fs-code: 14px; + --fs-paragraph-small: 14px; + --fs-paragraph-tiny: 12px; + --lh-paragraph-leading: 1.875; + --lh-paragraph: 1.625; + --lh-code: 1.5; + --lh-paragraph-small: 1.625; + --lh-paragraph-tiny: 1.625; + --color-code: var(--color-canvas-tint); + --color-on-code: var(--color-text-heading) +} + +.sl-avatar--with-bg:before { + background-color: var(--avatar-bg-color); + bottom: 0; + content: " "; + left: 0; + opacity: var(--avatar-bg-opacity); + position: absolute; + right: 0; + top: 0 +} + +.sl-aspect-ratio:before { + content: ""; + display: block; + height: 0; + padding-bottom: calc(1 / var(--ratio) * 100%) +} + +.sl-aspect-ratio > :not(style) { + align-items: center; + bottom: 0; + display: flex; + height: 100%; + justify-content: center; + left: 0; + overflow: hidden; + position: absolute; + right: 0; + top: 0; + width: 100% +} + +.sl-aspect-ratio > img, .sl-aspect-ratio > video { + object-fit: cover +} + +.sl-badge { + align-items: center; + border-width: 1px; + display: inline-flex; + outline: 2px solid transparent; + outline-offset: 2px +} + +.sl-badge a { + color: var(--color-text-muted) +} + +.sl-badge a:hover { + color: var(--color-text); + cursor: pointer +} + +.sl-button { + align-items: center; + display: inline-flex; + line-height: 0; + outline: 2px solid transparent; + outline-offset: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.sl-button-group > .sl-button:not(:first-child):not(:last-child) { + border-radius: 0; + border-right: 0 +} + +.sl-button-group > .sl-button:first-child:not(:last-child) { + border-bottom-right-radius: 0; + border-right: 0; + border-top-right-radius: 0 +} + +.sl-button-group > .sl-button:last-child:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0 +} + +.sl-image--inverted { + filter: invert(1) hue-rotate(180deg); + mix-blend-mode: screen +} + +.Link, .Link > code { + color: var(--color-link) +} + +.Link:hover, .Link:hover > code { + color: var(--color-link-dark) +} + +.sl-link-heading:hover .sl-link-heading__icon { + opacity: 1 +} + +.sl-link-heading__icon { + opacity: 0 +} + +.sl-menu { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none +} + +.sl-menu--pointer-interactions .sl-menu-item:not(.sl-menu-item--disabled):hover { + background-color: var(--color-primary); + color: var(--color-on-primary) +} + +.sl-menu--pointer-interactions .sl-menu-item:not(.sl-menu-item--disabled):hover .sl-menu-item__description { + color: var(--color-on-primary) +} + +.sl-menu--pointer-interactions .sl-menu-item:not(.sl-menu-item--disabled):hover .sl-menu-item__icon { + color: var(--color-on-primary) !important +} + +.sl-menu-item__link-icon, .sl-menu-item__meta-text { + opacity: .6 +} + +.sl-menu-item--disabled .sl-menu-item__title-wrapper { + cursor: not-allowed; + opacity: .5 +} + +.sl-menu-item--disabled .sl-menu-item__meta-text { + cursor: not-allowed; + opacity: .4 +} + +.sl-menu-item--focused { + background-color: var(--color-primary); + color: var(--color-on-primary) +} + +.sl-menu-item--focused .sl-menu-item__link-icon, .sl-menu-item--focused .sl-menu-item__meta-text { + opacity: 1 +} + +.sl-menu-item--focused .sl-menu-item__description { + color: var(--color-on-primary) +} + +.sl-menu-item--focused .sl-menu-item__icon { + color: var(--color-on-primary) !important +} + +.sl-menu-item--submenu-active { + background-color: var(--color-primary-tint) +} + +.sl-menu-item__title-wrapper { + max-width: 250px +} + +.sl-menu-item__description { + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden +} + +.sl-popover { + --tw-blur: var(--tw-empty, /*!*/ /*!*/); + --tw-brightness: var(--tw-empty, /*!*/ /*!*/); + --tw-contrast: var(--tw-empty, /*!*/ /*!*/); + --tw-grayscale: var(--tw-empty, /*!*/ /*!*/); + --tw-hue-rotate: var(--tw-empty, /*!*/ /*!*/); + --tw-invert: var(--tw-empty, /*!*/ /*!*/); + --tw-saturate: var(--tw-empty, /*!*/ /*!*/); + --tw-sepia: var(--tw-empty, /*!*/ /*!*/); + --tw-drop-shadow: var(--tw-empty, /*!*/ /*!*/); + --tw-drop-shadow: drop-shadow(var(--drop-shadow-default1)) drop-shadow(var(--drop-shadow-default2)); + border-radius: 2px; + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) +} + +.sl-popover > :not(.sl-popover__tip) { + border-radius: 2px; + position: relative; + z-index: 10 +} + +.sl-prose { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + --fs-paragraph: 1em; + --fs-paragraph-small: 0.875em; + --fs-code: 0.875em; + font-family: var(--font-prose); + font-size: 16px; + line-height: var(--lh-paragraph) +} + +.sl-prose > :first-child { + margin-top: 0 +} + +.sl-prose > :last-child { + margin-bottom: 0 +} + +.sl-prose h1 { + font-size: 2.25em +} + +.sl-prose > h1 { + margin-bottom: 1.11em; + margin-top: 0 +} + +.sl-prose h2 { + font-size: 1.75em; + line-height: 1.3333333 +} + +.sl-prose > h2 { + margin-bottom: 1em; + margin-top: 1.428em +} + +.sl-prose h3 { + font-size: 1.25em +} + +.sl-prose > h3 { + margin-bottom: .8em; + margin-top: 2em +} + +.sl-prose h4 { + font-size: 1em +} + +.sl-prose > h4 { + margin-bottom: .5em; + margin-top: 2em +} + +.sl-prose h2 + *, .sl-prose h3 + *, .sl-prose h4 + * { + margin-top: 0 +} + +.sl-prose strong { + font-weight: 600 +} + +.sl-prose .sl-text-lg { + font-size: .875em +} + +.sl-prose p { + color: var(--color-text-paragraph); + font-size: var(--fs-paragraph); + margin-bottom: 1em; + margin-top: 1em +} + +.sl-prose p:first-child { + margin-top: 0 +} + +.sl-prose p:last-child { + margin-bottom: 0 +} + +.sl-prose p > a > img { + display: inline +} + +.sl-prose caption a, .sl-prose figcaption a, .sl-prose li a, .sl-prose p a, .sl-prose table a { + color: var(--color-link) +} + +.sl-prose caption a:hover, .sl-prose figcaption a:hover, .sl-prose li a:hover, .sl-prose p a:hover, .sl-prose table a:hover { + color: var(--color-link-dark) +} + +.sl-prose caption a, .sl-prose figcaption a, .sl-prose li a, .sl-prose p a, .sl-prose table a { + --color-link: var(--color-text-primary); + --color-link-dark: var(--color-primary-dark) +} + +.sl-prose hr { + margin-bottom: 1em; + margin-top: 1em +} + +.sl-prose .sl-live-code { + margin: 1.25em -4px; + table-layout: auto; + width: 100% +} + +.sl-prose .sl-live-code__inner > pre { + margin-bottom: 0; + margin-top: 0 +} + +.sl-prose .sl-callout, .sl-prose ol, .sl-prose ul { + margin-bottom: 1.5em; + margin-top: 1.5em +} + +.sl-prose ol, .sl-prose ul { + line-height: var(--lh-paragraph) +} + +.sl-prose ol li, .sl-prose ul li { + padding-left: 2em +} + +.sl-prose ol li { + position: relative +} + +.sl-prose ol li:before { + content: counter(list-item) ". "; + font-variant-numeric: tabular-nums +} + +.sl-prose ul:not(.contains-task-list) li { + position: relative +} + +.sl-prose ul:not(.contains-task-list) li:before { + background-color: var(--color-text); + border-radius: 50%; + content: ""; + height: .375em; + left: .75em; + opacity: .7; + position: absolute; + top: .625em; + width: .375em +} + +.sl-prose li { + margin-bottom: 4px; + margin-top: 4px; + padding-left: 1.75em +} + +.sl-prose li p { + display: inline; + margin-bottom: .75em; + margin-top: .75em +} + +.sl-prose li > :first-child { + margin-top: 0 +} + +.sl-prose > ol p + :last-child, .sl-prose > ul p + :last-child { + margin-bottom: .75em +} + +.sl-prose ol ol, .sl-prose ol ul, .sl-prose ul ol, .sl-prose ul ul { + margin-bottom: 2px; + margin-top: 2px +} + +.sl-prose ul.contains-task-list input { + margin-left: -1.875em; + margin-right: .625em; + position: relative; + top: 1px +} + +.sl-prose ul.contains-task-list p { + margin-top: 0 +} + +.sl-prose figure { + margin-bottom: 1.5em; + margin-top: 1.5em +} + +.sl-prose figure figure, .sl-prose figure img, .sl-prose figure video { + margin-bottom: 0; + margin-top: 0 +} + +.sl-prose figure > figcaption, .sl-prose figure > figcaption p { + color: var(--color-text-muted); + font-size: var(--fs-paragraph-small); + line-height: var(--lh-paragraph-small); + margin-top: 8px; + padding-left: 16px; + padding-right: 16px; + text-align: center +} + +.sl-prose blockquote p { + margin-bottom: .5em; + margin-top: .5em +} + +.sl-prose table { + font-size: var(--fs-paragraph-small); + margin-bottom: 1.5em; + margin-left: -4px; + margin-right: -4px; + overflow-x: auto; + table-layout: auto; + width: 100% +} + +.sl-prose thead td, .sl-prose thead th { + color: var(--color-text-muted); + font-size: .857em; + font-weight: 500; + padding: 8px 12px; + text-transform: uppercase +} + +.sl-prose thead td:first-child, .sl-prose thead th:first-child { + padding-left: 4px +} + +.sl-prose tbody { + border-radius: 5px; + box-shadow: 0 0 0 1px var(--color-border, currentColor) +} + +.sl-prose tbody tr { + border-top-width: 1px +} + +.sl-prose tbody tr:first-child { + border-top: 0 +} + +.sl-prose tbody tr:nth-child(2n) { + background-color: var(--color-canvas-tint) +} + +.sl-prose td { + margin: .625em .75em; + padding: 10px 12px; + vertical-align: top +} + +.sl-prose td:not([align=center],[align=right]), .sl-prose th:not([align=center],[align=right]) { + text-align: left +} + +.sl-prose .mermaid { + margin-bottom: 1.5em; + margin-top: 1.5em +} + +.sl-prose .mermaid > svg { + border-radius: 5px; + border-width: 1px; + height: auto !important; + padding: 1.25em +} + +.sl-prose .sl-code-group .mermaid, .sl-prose .sl-code-group pre { + margin-top: 0 +} + +.sl-svg-focus { + filter: drop-shadow(0 0 1px hsla(var(--primary-h), 80%, 51%, .9)) +} + +.sl-radio-group__radio:hover { + cursor: pointer +} + +.sl-radio-group__radio--disabled { + opacity: .6 +} + +.sl-radio-group__radio--disabled:hover { + cursor: not-allowed +} + +.sl-stack--horizontal.sl-stack--1 > :not(style) ~ :not(style) { + margin-left: 4px +} + +.sl-stack--horizontal.sl-stack--2 > :not(style) ~ :not(style) { + margin-left: 8px +} + +.sl-stack--horizontal.sl-stack--3 > :not(style) ~ :not(style) { + margin-left: 12px +} + +.sl-stack--horizontal.sl-stack--4 > :not(style) ~ :not(style) { + margin-left: 16px +} + +.sl-stack--horizontal.sl-stack--5 > :not(style) ~ :not(style) { + margin-left: 20px +} + +.sl-stack--horizontal.sl-stack--6 > :not(style) ~ :not(style) { + margin-left: 24px +} + +.sl-stack--horizontal.sl-stack--8 > :not(style) ~ :not(style) { + margin-left: 32px +} + +.sl-stack--horizontal.sl-stack--10 > :not(style) ~ :not(style) { + margin-left: 40px +} + +.sl-stack--horizontal.sl-stack--12 > :not(style) ~ :not(style) { + margin-left: 48px +} + +.sl-stack--horizontal.sl-stack--14 > :not(style) ~ :not(style) { + margin-left: 56px +} + +.sl-stack--horizontal.sl-stack--16 > :not(style) ~ :not(style) { + margin-left: 64px +} + +.sl-stack--horizontal.sl-stack--20 > :not(style) ~ :not(style) { + margin-left: 80px +} + +.sl-stack--horizontal.sl-stack--24 > :not(style) ~ :not(style) { + margin-left: 96px +} + +.sl-stack--horizontal.sl-stack--32 > :not(style) ~ :not(style) { + margin-left: 128px +} + +.sl-stack--vertical.sl-stack--1 > :not(style) ~ :not(style) { + margin-top: 4px +} + +.sl-stack--vertical.sl-stack--2 > :not(style) ~ :not(style) { + margin-top: 8px +} + +.sl-stack--vertical.sl-stack--3 > :not(style) ~ :not(style) { + margin-top: 12px +} + +.sl-stack--vertical.sl-stack--4 > :not(style) ~ :not(style) { + margin-top: 16px +} + +.sl-stack--vertical.sl-stack--5 > :not(style) ~ :not(style) { + margin-top: 20px +} + +.sl-stack--vertical.sl-stack--6 > :not(style) ~ :not(style) { + margin-top: 24px +} + +.sl-stack--vertical.sl-stack--8 > :not(style) ~ :not(style) { + margin-top: 32px +} + +.sl-stack--vertical.sl-stack--10 > :not(style) ~ :not(style) { + margin-top: 40px +} + +.sl-stack--vertical.sl-stack--12 > :not(style) ~ :not(style) { + margin-top: 48px +} + +.sl-stack--vertical.sl-stack--14 > :not(style) ~ :not(style) { + margin-top: 56px +} + +.sl-stack--vertical.sl-stack--16 > :not(style) ~ :not(style) { + margin-top: 64px +} + +.sl-stack--vertical.sl-stack--20 > :not(style) ~ :not(style) { + margin-top: 80px +} + +.sl-stack--vertical.sl-stack--24 > :not(style) ~ :not(style) { + margin-top: 96px +} + +.sl-stack--vertical.sl-stack--32 > :not(style) ~ :not(style) { + margin-top: 128px +} + +.sl-switch .sl-switch__indicator { + transition: background-color .1s cubic-bezier(.4, 1, .75, .9) +} + +.sl-switch .sl-switch__indicator .sl-switch__icon { + visibility: hidden +} + +.sl-switch .sl-switch__indicator:before { + background-color: var(--color-canvas); + border-radius: 50%; + content: ""; + height: calc(100% - 4px); + left: 0; + margin: 2px; + position: absolute; + transition: left .1s cubic-bezier(.4, 1, .75, .9); + width: calc(50% - 4px) +} + +.sl-switch input:checked:disabled ~ .sl-switch__indicator { + background-color: var(--color-primary-light) +} + +.sl-switch input:checked ~ .sl-switch__indicator { + background-color: var(--color-primary) +} + +.sl-switch input:checked ~ .sl-switch__indicator .sl-switch__icon { + visibility: visible +} + +.sl-switch input:checked ~ .sl-switch__indicator:before { + left: 50% +} + +.sl-tooltip { + --tw-blur: var(--tw-empty, /*!*/ /*!*/); + --tw-brightness: var(--tw-empty, /*!*/ /*!*/); + --tw-contrast: var(--tw-empty, /*!*/ /*!*/); + --tw-grayscale: var(--tw-empty, /*!*/ /*!*/); + --tw-hue-rotate: var(--tw-empty, /*!*/ /*!*/); + --tw-invert: var(--tw-empty, /*!*/ /*!*/); + --tw-saturate: var(--tw-empty, /*!*/ /*!*/); + --tw-sepia: var(--tw-empty, /*!*/ /*!*/); + --tw-drop-shadow: var(--tw-empty, /*!*/ /*!*/); + --tw-drop-shadow: drop-shadow(var(--drop-shadow-default1)) drop-shadow(var(--drop-shadow-default2)); + border-radius: 2px; + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); + font-size: 11px; + max-width: 300px; + padding: 4px 6px +} + +.sl-tooltip > :not(.sl-tooltip_tip) { + position: relative; + z-index: 10 +} + +input, textarea { + background-color: transparent +} + +.sl-focus-ring { + --tw-ring-color: hsla(var(--primary-h), 80%, 61%, var(--tw-ring-opacity)); + --tw-ring-opacity: 0.5; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color); + border-radius: 2px; + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) +} + +:root, [data-theme=light], [data-theme=light] .sl-inverted .sl-inverted, [data-theme=light] .sl-inverted .sl-inverted .sl-inverted .sl-inverted { + --text-h: 0; + --text-s: 0%; + --text-l: 15%; + --shadow-sm: 0px 0px 1px rgba(67, 90, 111, .3); + --shadow-md: 0px 2px 4px -2px rgba(0, 0, 0, .25), 0px 0px 1px rgba(67, 90, 111, .3); + --shadow-lg: 0 4px 17px rgba(67, 90, 111, .2), 0 2px 3px rgba(0, 0, 0, .1), inset 0 0 0 .5px var(--color-canvas-pure), 0 0 0 .5px rgba(0, 0, 0, .2); + --shadow-xl: 0px 0px 1px rgba(67, 90, 111, .3), 0px 8px 10px -4px rgba(67, 90, 111, .45); + --shadow-2xl: 0px 0px 1px rgba(67, 90, 111, .3), 0px 16px 24px -8px rgba(67, 90, 111, .45); + --drop-shadow-default1: 0 0 0.5px rgba(0, 0, 0, .6); + --drop-shadow-default2: 0 2px 5px rgba(67, 90, 111, .3); + --color-text-heading: hsla(var(--text-h), var(--text-s), max(3, calc(var(--text-l) - 15)), 1); + --color-text: hsla(var(--text-h), var(--text-s), var(--text-l), 1); + --color-text-paragraph: hsla(var(--text-h), var(--text-s), var(--text-l), 0.9); + --color-text-muted: hsla(var(--text-h), var(--text-s), var(--text-l), 0.7); + --color-text-light: hsla(var(--text-h), var(--text-s), var(--text-l), 0.55); + --color-text-disabled: hsla(var(--text-h), var(--text-s), var(--text-l), 0.3); + --canvas-h: 218; + --canvas-s: 40%; + --canvas-l: 100%; + --color-canvas: hsla(var(--canvas-h), var(--canvas-s), var(--canvas-l), 1); + --color-canvas-dark: #000; + --color-canvas-pure: #fff; + --color-canvas-tint: rgba(245, 247, 250, .5); + --color-canvas-50: #f5f7fa; + --color-canvas-100: #ebeef5; + --color-canvas-200: #e0e6f0; + --color-canvas-300: #d5ddeb; + --color-canvas-400: #cbd5e7; + --color-canvas-500: #c0cde2; + --color-canvas-dialog: #fff; + --color-border-dark: hsla(var(--canvas-h), 30%, 72%, 0.5); + --color-border: hsla(var(--canvas-h), 32%, 78%, 0.5); + --color-border-light: hsla(var(--canvas-h), 24%, 84%, 0.5); + --color-border-input: hsla(var(--canvas-h), 24%, 72%, 0.8); + --color-border-button: hsla(var(--canvas-h), 24%, 20%, 0.65); + --primary-h: 202; + --primary-s: 100%; + --primary-l: 55%; + --color-text-primary: #0081cc; + --color-primary-dark: #1891d8; + --color-primary-darker: #126fa5; + --color-primary: #19abff; + --color-primary-light: #52bfff; + --color-primary-tint: rgba(77, 190, 255, .25); + --color-on-primary: #fff; + --success-h: 156; + --success-s: 95%; + --success-l: 37%; + --color-text-success: #05c779; + --color-success-dark: #138b5b; + --color-success-darker: #0f6c47; + --color-success: #05b870; + --color-success-light: #06db86; + --color-success-tint: rgba(81, 251, 183, .25); + --color-on-success: #fff; + --warning-h: 20; + --warning-s: 90%; + --warning-l: 56%; + --color-text-warning: #c2470a; + --color-warning-dark: #d35d22; + --color-warning-darker: #9e461a; + --color-warning: #f46d2a; + --color-warning-light: #f7925f; + --color-warning-tint: rgba(246, 139, 85, .25); + --color-on-warning: #fff; + --danger-h: 0; + --danger-s: 84%; + --danger-l: 63%; + --color-text-danger: #bc1010; + --color-danger-dark: #d83b3b; + --color-danger-darker: #af2323; + --color-danger: #f05151; + --color-danger-light: #f58e8e; + --color-danger-tint: rgba(241, 91, 91, .25); + --color-on-danger: #fff; + color: var(--color-text) +} + +:root .sl-inverted, [data-theme=light] .sl-inverted, [data-theme=light] .sl-inverted .sl-inverted .sl-inverted { + --text-h: 0; + --text-s: 0%; + --text-l: 86%; + --shadow-sm: 0px 0px 1px rgba(11, 13, 19, .5); + --shadow-md: 0px 2px 4px -2px rgba(0, 0, 0, .35), 0px 0px 1px rgba(11, 13, 19, .4); + --shadow-lg: 0 2px 14px rgba(0, 0, 0, .55), 0 0 0 0.5px hsla(0, 0%, 100%, .2); + --shadow-xl: 0px 0px 1px rgba(11, 13, 19, .4), 0px 8px 10px -4px rgba(11, 13, 19, .55); + --shadow-2xl: 0px 0px 1px rgba(11, 13, 19, .4), 0px 16px 24px -8px rgba(11, 13, 19, .55); + --drop-shadow-default1: 0 0 0.5px hsla(0, 0%, 100%, .5); + --drop-shadow-default2: 0 3px 8px rgba(0, 0, 0, .6); + --color-text-heading: hsla(var(--text-h), var(--text-s), max(3, calc(var(--text-l) - 15)), 1); + --color-text: hsla(var(--text-h), var(--text-s), var(--text-l), 1); + --color-text-paragraph: hsla(var(--text-h), var(--text-s), var(--text-l), 0.9); + --color-text-muted: hsla(var(--text-h), var(--text-s), var(--text-l), 0.7); + --color-text-light: hsla(var(--text-h), var(--text-s), var(--text-l), 0.55); + --color-text-disabled: hsla(var(--text-h), var(--text-s), var(--text-l), 0.3); + --canvas-h: 218; + --canvas-s: 32%; + --canvas-l: 10%; + --color-canvas: hsla(var(--canvas-h), var(--canvas-s), var(--canvas-l), 1); + --color-canvas-dark: #000; + --color-canvas-pure: #0c1018; + --color-canvas-tint: rgba(60, 76, 103, .2); + --color-canvas-50: #3c4c67; + --color-canvas-100: #2d394e; + --color-canvas-200: #212a3b; + --color-canvas-300: #19212e; + --color-canvas-400: #171e2b; + --color-canvas-500: #151c28; + --color-canvas-dialog: #2d394e; + --color-border-dark: hsla(var(--canvas-h), 24%, 23%, 0.5); + --color-border: hsla(var(--canvas-h), 26%, 28%, 0.5); + --color-border-light: hsla(var(--canvas-h), 19%, 34%, 0.5); + --color-border-input: hsla(var(--canvas-h), 19%, 30%, 0.8); + --color-border-button: hsla(var(--canvas-h), 19%, 80%, 0.65); + --primary-h: 202; + --primary-s: 90%; + --primary-l: 51%; + --color-text-primary: #66c7ff; + --color-primary-dark: #1f83bd; + --color-primary-darker: #186491; + --color-primary: #12a0f3; + --color-primary-light: #42b3f5; + --color-primary-tint: rgba(85, 187, 246, .25); + --color-on-primary: #fff; + --success-h: 156; + --success-s: 95%; + --success-l: 67%; + --color-text-success: #41f1ab; + --color-success-dark: #47dca0; + --color-success-darker: #24bc7f; + --color-success: #62f3b9; + --color-success-light: #a0f8d5; + --color-success-tint: rgba(89, 243, 181, .25); + --color-on-success: #fff; + --warning-h: 20; + --warning-s: 90%; + --warning-l: 50%; + --color-text-warning: #ec7d46; + --color-warning-dark: #b55626; + --color-warning-darker: #8b421d; + --color-warning: #e75d18; + --color-warning-light: #ec7d46; + --color-warning-tint: rgba(238, 142, 93, .25); + --color-on-warning: #fff; + --danger-h: 0; + --danger-s: 84%; + --danger-l: 43%; + --color-text-danger: #e74b4b; + --color-danger-dark: #972626; + --color-danger-darker: #721d1d; + --color-danger: #c11a1a; + --color-danger-light: #e22828; + --color-danger-tint: rgba(234, 98, 98, .25); + --color-on-danger: #fff; + color: var(--color-text) +} + +[data-theme=dark], [data-theme=dark] .sl-inverted .sl-inverted, [data-theme=dark] .sl-inverted .sl-inverted .sl-inverted .sl-inverted { + --text-h: 0; + --text-s: 0%; + --text-l: 85%; + --shadow-sm: 0px 0px 1px rgba(11, 13, 19, .5); + --shadow-md: 0px 2px 4px -2px rgba(0, 0, 0, .35), 0px 0px 1px rgba(11, 13, 19, .4); + --shadow-lg: 0 2px 14px rgba(0, 0, 0, .55), 0 0 0 0.5px hsla(0, 0%, 100%, .2); + --shadow-xl: 0px 0px 1px rgba(11, 13, 19, .4), 0px 8px 10px -4px rgba(11, 13, 19, .55); + --shadow-2xl: 0px 0px 1px rgba(11, 13, 19, .4), 0px 16px 24px -8px rgba(11, 13, 19, .55); + --drop-shadow-default1: 0 0 0.5px hsla(0, 0%, 100%, .5); + --drop-shadow-default2: 0 3px 8px rgba(0, 0, 0, .6); + --color-text-heading: hsla(var(--text-h), var(--text-s), max(3, calc(var(--text-l) - 15)), 1); + --color-text: hsla(var(--text-h), var(--text-s), var(--text-l), 1); + --color-text-paragraph: hsla(var(--text-h), var(--text-s), var(--text-l), 0.9); + --color-text-muted: hsla(var(--text-h), var(--text-s), var(--text-l), 0.7); + --color-text-light: hsla(var(--text-h), var(--text-s), var(--text-l), 0.55); + --color-text-disabled: hsla(var(--text-h), var(--text-s), var(--text-l), 0.3); + --canvas-h: 218; + --canvas-s: 32%; + --canvas-l: 8%; + --color-canvas: hsla(var(--canvas-h), var(--canvas-s), var(--canvas-l), 1); + --color-canvas-dark: #000; + --color-canvas-pure: #090c11; + --color-canvas-tint: rgba(57, 71, 96, .2); + --color-canvas-50: #262f40; + --color-canvas-100: #1a212d; + --color-canvas-200: #121821; + --color-canvas-300: #0e131a; + --color-canvas-400: #0c1017; + --color-canvas-500: #0c1017; + --color-canvas-dialog: #1a212d; + --color-border-dark: hsla(var(--canvas-h), 24%, 21%, 0.5); + --color-border: hsla(var(--canvas-h), 26%, 26%, 0.5); + --color-border-light: hsla(var(--canvas-h), 19%, 32%, 0.5); + --color-border-input: hsla(var(--canvas-h), 19%, 28%, 0.8); + --color-border-button: hsla(var(--canvas-h), 19%, 80%, 0.65); + --primary-h: 202; + --primary-s: 80%; + --primary-l: 36%; + --color-text-primary: #66c7ff; + --color-primary-dark: #1c5a7d; + --color-primary-darker: #154560; + --color-primary: #126fa5; + --color-primary-light: #1685c5; + --color-primary-tint: rgba(21, 130, 193, .25); + --color-on-primary: #fff; + --success-h: 156; + --success-s: 95%; + --success-l: 37%; + --color-text-success: #4be7a9; + --color-success-dark: #145239; + --color-success-darker: #10422e; + --color-success: #0f6c47; + --color-success-light: #128255; + --color-success-tint: rgba(26, 188, 123, .25); + --color-on-success: #fff; + --warning-h: 20; + --warning-s: 90%; + --warning-l: 56%; + --color-text-warning: #e28150; + --color-warning-dark: #7d4021; + --color-warning-darker: #61311a; + --color-warning: #9e461a; + --color-warning-light: #c1551f; + --color-warning-tint: rgba(184, 81, 30, .25); + --color-on-warning: #fff; + --danger-h: 0; + --danger-s: 84%; + --danger-l: 63%; + --color-text-danger: #d55; + --color-danger-dark: #892929; + --color-danger-darker: #6a2020; + --color-danger: #af2323; + --color-danger-light: #d12929; + --color-danger-tint: rgba(179, 35, 35, .25); + --color-on-danger: #fff; + color: var(--color-text) +} + +[data-theme=dark] .sl-inverted, [data-theme=dark] .sl-inverted .sl-inverted .sl-inverted { + --text-h: 0; + --text-s: 0%; + --text-l: 89%; + --shadow-sm: 0px 0px 1px rgba(11, 13, 19, .5); + --shadow-md: 0px 2px 4px -2px rgba(0, 0, 0, .35), 0px 0px 1px rgba(11, 13, 19, .4); + --shadow-lg: 0 2px 14px rgba(0, 0, 0, .55), 0 0 0 0.5px hsla(0, 0%, 100%, .2); + --shadow-xl: 0px 0px 1px rgba(11, 13, 19, .4), 0px 8px 10px -4px rgba(11, 13, 19, .55); + --shadow-2xl: 0px 0px 1px rgba(11, 13, 19, .4), 0px 16px 24px -8px rgba(11, 13, 19, .55); + --drop-shadow-default1: 0 0 0.5px hsla(0, 0%, 100%, .5); + --drop-shadow-default2: 0 3px 8px rgba(0, 0, 0, .6); + --color-text-heading: hsla(var(--text-h), var(--text-s), max(3, calc(var(--text-l) - 15)), 1); + --color-text: hsla(var(--text-h), var(--text-s), var(--text-l), 1); + --color-text-paragraph: hsla(var(--text-h), var(--text-s), var(--text-l), 0.9); + --color-text-muted: hsla(var(--text-h), var(--text-s), var(--text-l), 0.7); + --color-text-light: hsla(var(--text-h), var(--text-s), var(--text-l), 0.55); + --color-text-disabled: hsla(var(--text-h), var(--text-s), var(--text-l), 0.3); + --canvas-h: 218; + --canvas-s: 32%; + --canvas-l: 13%; + --color-canvas: hsla(var(--canvas-h), var(--canvas-s), var(--canvas-l), 1); + --color-canvas-dark: #000; + --color-canvas-pure: #111722; + --color-canvas-tint: rgba(66, 83, 112, .2); + --color-canvas-50: #2b374a; + --color-canvas-100: #222b3a; + --color-canvas-200: #1a212e; + --color-canvas-300: #141a24; + --color-canvas-400: #121721; + --color-canvas-500: #121721; + --color-canvas-dialog: #222b3a; + --color-border-dark: hsla(var(--canvas-h), 24%, 26%, 0.5); + --color-border: hsla(var(--canvas-h), 26%, 31%, 0.5); + --color-border-light: hsla(var(--canvas-h), 19%, 37%, 0.5); + --color-border-input: hsla(var(--canvas-h), 19%, 33%, 0.8); + --color-border-button: hsla(var(--canvas-h), 19%, 80%, 0.65); + --primary-h: 202; + --primary-s: 80%; + --primary-l: 33%; + --color-text-primary: #66c7ff; + --color-primary-dark: #1a5475; + --color-primary-darker: #14425c; + --color-primary: #116697; + --color-primary-light: #147cb8; + --color-primary-tint: rgba(21, 130, 193, .25); + --color-on-primary: #fff; + --success-h: 156; + --success-s: 95%; + --success-l: 67%; + --color-text-success: #4be7a9; + --color-success-dark: #25986a; + --color-success-darker: #1c7350; + --color-success: #1bc581; + --color-success-light: #28e297; + --color-success-tint: rgba(26, 188, 123, .25); + --color-on-success: #fff; + --warning-h: 20; + --warning-s: 90%; + --warning-l: 50%; + --color-text-warning: #e28150; + --color-warning-dark: #713a1e; + --color-warning-darker: #552b16; + --color-warning: #914018; + --color-warning-light: #ab4c1c; + --color-warning-tint: rgba(184, 81, 30, .25); + --color-on-warning: #fff; + --danger-h: 0; + --danger-s: 84%; + --danger-l: 43%; + --color-text-danger: #d55; + --color-danger-dark: #5e1c1c; + --color-danger-darker: #471515; + --color-danger: #771818; + --color-danger-light: #911d1d; + --color-danger-tint: rgba(179, 35, 35, .25); + --color-on-danger: #fff; + color: var(--color-text) +} + +.sl-elements { + font-size: 13px +} + +.sl-elements .svg-inline--fa { + display: inline-block +} + +.sl-elements .DocsSkeleton { + animation: skeleton-glow .5s linear infinite alternate; + background: rgba(206, 217, 224, .2); + background-clip: padding-box !important; + border-color: rgba(206, 217, 224, .2) !important; + border-radius: 2px; + box-shadow: none !important; + color: transparent !important; + cursor: default; + pointer-events: none; + user-select: none +} + +.sl-elements .Model { + --fs-code: 12px +} + +.sl-elements .ElementsTableOfContentsItem:hover { + color: inherit; + text-decoration: none +} + +.sl-elements .ParameterGrid { + align-items: center; + display: grid; + grid-template-columns:fit-content(120px) 20px auto; + margin-bottom: 16px; + padding-bottom: 0; + row-gap: 3px +} + +.sl-elements .TryItPanel > :nth-child(2) { + overflow: auto +} + +.sl-elements .OperationParametersContent { + max-height: 162px +} + +.sl-elements .Checkbox { + max-width: 15px; + padding-right: 3px +} + +.sl-elements .TextForCheckBox { + padding-left: 9px; + padding-top: 6px +} + +.sl-elements .TextRequestBody { + margin-bottom: 16px; + max-height: 200px; + overflow-y: auto; + padding-bottom: 0 +} + +.sl-elements .HttpOperation .JsonSchemaViewer .sl-markdown-viewer p, .sl-elements .HttpOperation__Parameters .sl-markdown-viewer p, .sl-elements .Model .JsonSchemaViewer .sl-markdown-viewer p { + font-size: 12px; + line-height: 1.5em +} + +:root, :host { + --fa-font-solid: normal 900 1em/1 "Font Awesome 6 Solid"; + --fa-font-regular: normal 400 1em/1 "Font Awesome 6 Regular"; + --fa-font-light: normal 300 1em/1 "Font Awesome 6 Light"; + --fa-font-thin: normal 100 1em/1 "Font Awesome 6 Thin"; + --fa-font-duotone: normal 900 1em/1 "Font Awesome 6 Duotone"; + --fa-font-brands: normal 400 1em/1 "Font Awesome 6 Brands"; +} + +svg:not(:root).svg-inline--fa, +svg:not(:host).svg-inline--fa { + overflow: visible; + box-sizing: content-box; +} + +.svg-inline--fa { + display: var(--fa-display, inline-block); + height: 1em; + overflow: visible; + vertical-align: -0.125em; +} + +.svg-inline--fa.fa-2xs { + vertical-align: 0.1em; +} + +.svg-inline--fa.fa-xs { + vertical-align: 0em; +} + +.svg-inline--fa.fa-sm { + vertical-align: -0.0714285705em; +} + +.svg-inline--fa.fa-lg { + vertical-align: -0.2em; +} + +.svg-inline--fa.fa-xl { + vertical-align: -0.25em; +} + +.svg-inline--fa.fa-2xl { + vertical-align: -0.3125em; +} + +.svg-inline--fa.fa-pull-left { + margin-right: var(--fa-pull-margin, 0.3em); + width: auto; +} + +.svg-inline--fa.fa-pull-right { + margin-left: var(--fa-pull-margin, 0.3em); + width: auto; +} + +.svg-inline--fa.fa-li { + width: var(--fa-li-width, 2em); + top: 0.25em; +} + +.svg-inline--fa.fa-fw { + width: var(--fa-fw-width, 1.25em); +} + +.fa-layers svg.svg-inline--fa { + bottom: 0; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; +} + +.fa-layers-counter, +.fa-layers-text { + display: inline-block; + position: absolute; + text-align: center; +} + +.fa-layers { + display: inline-block; + height: 1em; + position: relative; + text-align: center; + vertical-align: -0.125em; + width: 1em; +} + +.fa-layers svg.svg-inline--fa { + -webkit-transform-origin: center center; + transform-origin: center center; +} + +.fa-layers-text { + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + -webkit-transform-origin: center center; + transform-origin: center center; +} + +.fa-layers-counter { + background-color: var(--fa-counter-background-color, #ff253a); + border-radius: var(--fa-counter-border-radius, 1em); + box-sizing: border-box; + color: var(--fa-inverse, #fff); + line-height: var(--fa-counter-line-height, 1); + max-width: var(--fa-counter-max-width, 5em); + min-width: var(--fa-counter-min-width, 1.5em); + overflow: hidden; + padding: var(--fa-counter-padding, 0.25em 0.5em); + right: var(--fa-right, 0); + text-overflow: ellipsis; + top: var(--fa-top, 0); + -webkit-transform: scale(var(--fa-counter-scale, 0.25)); + transform: scale(var(--fa-counter-scale, 0.25)); + -webkit-transform-origin: top right; + transform-origin: top right; +} + +.fa-layers-bottom-right { + bottom: var(--fa-bottom, 0); + right: var(--fa-right, 0); + top: auto; + -webkit-transform: scale(var(--fa-layers-scale, 0.25)); + transform: scale(var(--fa-layers-scale, 0.25)); + -webkit-transform-origin: bottom right; + transform-origin: bottom right; +} + +.fa-layers-bottom-left { + bottom: var(--fa-bottom, 0); + left: var(--fa-left, 0); + right: auto; + top: auto; + -webkit-transform: scale(var(--fa-layers-scale, 0.25)); + transform: scale(var(--fa-layers-scale, 0.25)); + -webkit-transform-origin: bottom left; + transform-origin: bottom left; +} + +.fa-layers-top-right { + top: var(--fa-top, 0); + right: var(--fa-right, 0); + -webkit-transform: scale(var(--fa-layers-scale, 0.25)); + transform: scale(var(--fa-layers-scale, 0.25)); + -webkit-transform-origin: top right; + transform-origin: top right; +} + +.fa-layers-top-left { + left: var(--fa-left, 0); + right: auto; + top: var(--fa-top, 0); + -webkit-transform: scale(var(--fa-layers-scale, 0.25)); + transform: scale(var(--fa-layers-scale, 0.25)); + -webkit-transform-origin: top left; + transform-origin: top left; +} + +.fa-1x { + font-size: 1em; +} + +.fa-2x { + font-size: 2em; +} + +.fa-3x { + font-size: 3em; +} + +.fa-4x { + font-size: 4em; +} + +.fa-5x { + font-size: 5em; +} + +.fa-6x { + font-size: 6em; +} + +.fa-7x { + font-size: 7em; +} + +.fa-8x { + font-size: 8em; +} + +.fa-9x { + font-size: 9em; +} + +.fa-10x { + font-size: 10em; +} + +.fa-2xs { + font-size: 0.625em; + line-height: 0.1em; + vertical-align: 0.225em; +} + +.fa-xs { + font-size: 0.75em; + line-height: 0.0833333337em; + vertical-align: 0.125em; +} + +.fa-sm { + font-size: 0.875em; + line-height: 0.0714285718em; + vertical-align: 0.0535714295em; +} + +.fa-lg { + font-size: 1.25em; + line-height: 0.05em; + vertical-align: -0.075em; +} + +.fa-xl { + font-size: 1.5em; + line-height: 0.0416666682em; + vertical-align: -0.125em; +} + +.fa-2xl { + font-size: 2em; + line-height: 0.03125em; + vertical-align: -0.1875em; +} + +.fa-fw { + text-align: center; + width: 1.25em; +} + +.fa-ul { + list-style-type: none; + margin-left: var(--fa-li-margin, 2.5em); + padding-left: 0; +} + +.fa-ul > li { + position: relative; +} + +.fa-li { + left: calc(var(--fa-li-width, 2em) * -1); + position: absolute; + text-align: center; + width: var(--fa-li-width, 2em); + line-height: inherit; +} + +.fa-border { + border-color: var(--fa-border-color, #eee); + border-radius: var(--fa-border-radius, 0.1em); + border-style: var(--fa-border-style, solid); + border-width: var(--fa-border-width, 0.08em); + padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); +} + +.fa-pull-left { + float: left; + margin-right: var(--fa-pull-margin, 0.3em); +} + +.fa-pull-right { + float: right; + margin-left: var(--fa-pull-margin, 0.3em); +} + +.fa-beat { + -webkit-animation-name: fa-beat; + animation-name: fa-beat; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); +} + +.fa-bounce { + -webkit-animation-name: fa-bounce; + animation-name: fa-bounce; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); +} + +.fa-fade { + -webkit-animation-name: fa-fade; + animation-name: fa-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); +} + +.fa-beat-fade { + -webkit-animation-name: fa-beat-fade; + animation-name: fa-beat-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); +} + +.fa-flip { + -webkit-animation-name: fa-flip; + animation-name: fa-flip; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); +} + +.fa-shake { + -webkit-animation-name: fa-shake; + animation-name: fa-shake; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); +} + +.fa-spin { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-delay: var(--fa-animation-delay, 0); + animation-delay: var(--fa-animation-delay, 0); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 2s); + animation-duration: var(--fa-animation-duration, 2s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); +} + +.fa-spin-reverse { + --fa-animation-direction: reverse; +} + +.fa-pulse, +.fa-spin-pulse { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, steps(8)); + animation-timing-function: var(--fa-animation-timing, steps(8)); +} + +@media (prefers-reduced-motion: reduce) { + .fa-beat, + .fa-bounce, + .fa-fade, + .fa-beat-fade, + .fa-flip, + .fa-pulse, + .fa-shake, + .fa-spin, + .fa-spin-pulse { + -webkit-animation-delay: -1ms; + animation-delay: -1ms; + -webkit-animation-duration: 1ms; + animation-duration: 1ms; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + transition-delay: 0s; + transition-duration: 0s; + } +} + +@-webkit-keyframes fa-beat { + 0%, + 90% { + -webkit-transform: scale(1); + transform: scale(1); + } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); + } +} + +@keyframes fa-beat { + 0%, + 90% { + -webkit-transform: scale(1); + transform: scale(1); + } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); + } +} + +@-webkit-keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); + } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); + } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); + } +} + +@keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); + } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); + } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); + } +} + +@-webkit-keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); + } +} + +@keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); + } +} + +@-webkit-keyframes fa-beat-fade { + 0%, + 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); + } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); + } +} + +@keyframes fa-beat-fade { + 0%, + 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); + } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); + } +} + +@-webkit-keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + } +} + +@keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + } +} + +@-webkit-keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); + } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); + } + 8%, + 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); + } + 12%, + 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); + } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); + } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); + } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); + } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); + } + 40%, + 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } +} + +@keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); + } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); + } + 8%, + 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); + } + 12%, + 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); + } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); + } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); + } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); + } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); + } + 40%, + 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } +} + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.fa-rotate-90 { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.fa-rotate-180 { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); +} + +.fa-rotate-270 { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); +} + +.fa-flip-horizontal { + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); +} + +.fa-flip-vertical { + -webkit-transform: scale(1, -1); + transform: scale(1, -1); +} + +.fa-flip-both, +.fa-flip-horizontal.fa-flip-vertical { + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); +} + +.fa-rotate-by { + -webkit-transform: rotate(var(--fa-rotate-angle, none)); + transform: rotate(var(--fa-rotate-angle, none)); +} + +.fa-stack { + display: inline-block; + vertical-align: middle; + height: 2em; + position: relative; + width: 2.5em; +} + +.fa-stack-1x, +.fa-stack-2x { + bottom: 0; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + z-index: var(--fa-stack-z-index, auto); +} + +.svg-inline--fa.fa-stack-1x { + height: 1em; + width: 1.25em; +} + +.svg-inline--fa.fa-stack-2x { + height: 2em; + width: 2.5em; +} + +.fa-inverse { + color: var(--fa-inverse, #fff); +} + +.sr-only, +.fa-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.sr-only-focusable:not(:focus), +.fa-sr-only-focusable:not(:focus) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.svg-inline--fa .fa-primary { + fill: var(--fa-primary-color, currentColor); + opacity: var(--fa-primary-opacity, 1); +} + +.svg-inline--fa .fa-secondary { + fill: var(--fa-secondary-color, currentColor); + opacity: var(--fa-secondary-opacity, 0.4); +} + +.svg-inline--fa.fa-swap-opacity .fa-primary { + opacity: var(--fa-secondary-opacity, 0.4); +} + +.svg-inline--fa.fa-swap-opacity .fa-secondary { + opacity: var(--fa-primary-opacity, 1); +} + +.svg-inline--fa mask .fa-primary, +.svg-inline--fa mask .fa-secondary { + fill: black; +} + +.fad.fa-inverse, +.fa-duotone.fa-inverse { + color: var(--fa-inverse, #fff); +} + + +/* SCRIBE CUSTOM STYLES */ + + +:root, [data-theme=light], [data-theme=light] .sl-inverted .sl-inverted, [data-theme=light] .sl-inverted .sl-inverted .sl-inverted .sl-inverted { + --color-canvas: #f2f2f2; +} + +:root .sl-inverted, [data-theme=light] .sl-inverted, [data-theme=light] .sl-inverted .sl-inverted .sl-inverted { + --color-canvas: #f2f2f2; +} + +aside { + display: block; + padding: 1em; + margin-top: 1.5em; + margin-bottom: 1.5em; + color: whitesmoke; + background: #2D394E; +} + +aside.warning { + background-color: #cc6065; +} + +aside.success { + background-color: #4c9454; +} + +aside.info { + background: #8fbcd4; +} + +input[type="file"]::file-selector-button { + color: var(--color-text-muted); + background-color: var(--color-canvas-100); + padding-left: 0; + border-radius: 2px; /* sl-rounded */ + border-color: transparent; /* sl-border-transparent */ + font-size: 10px; /* sl-font-base */ + height: 32px; /* sl-h-md */ +} + +input[type="file"]::file-selector-button:hover { + border-color: var(--color-border-input); /* .hover\:sl-border-input:hover */ + cursor: pointer; +} + +* { + /* Foreground, Background */ + scrollbar-color: #3c4c67 transparent); +} +*::-webkit-scrollbar { /* Background */ + width: 5px; + height: 5px; + background: transparent; +} + +*::-webkit-scrollbar-thumb { /* Foreground */ + background: #3c4c67; +} + + +.sl-inverted * { + /* Foreground, Background */ + scrollbar-color: #FFFFFF99 transparent; +} + +.sl-inverted *::-webkit-scrollbar-thumb { /* Foreground */ + background: #FFFFFF99; +} diff --git a/vendor/knuckleswtf/scribe/resources/example_custom_endpoint.yaml b/vendor/knuckleswtf/scribe/resources/example_custom_endpoint.yaml new file mode 100644 index 00000000..4b023521 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/example_custom_endpoint.yaml @@ -0,0 +1,53 @@ +# To include an endpoint that isn't a part of your Laravel app (or belongs to a vendor package), +# you can define it in a custom.*.yaml file, like this one. +# Each custom file should contain an array of endpoints. Here's an example: +# See https://scribe.knuckles.wtf/laravel/documenting/custom-endpoints#extra-sorting-groups-in-custom-endpoint-files for more options + +#- httpMethods: +# - POST +# uri: api/doSomething/{param} +# metadata: +# groupName: The group the endpoint belongs to. Can be a new group or an existing group. +# groupDescription: A description for the group. You don't need to set this for every endpoint; once is enough. +# subgroup: You can add a subgroup, too. +# title: Do something +# description: 'This endpoint allows you to do something.' +# authenticated: false +# headers: +# Content-Type: application/json +# Accept: application/json +# urlParameters: +# param: +# name: param +# description: A URL param for no reason. +# required: true +# example: 2 +# type: integer +# queryParameters: +# speed: +# name: speed +# description: How fast the thing should be done. Can be `slow` or `fast`. +# required: false +# example: fast +# type: string +# bodyParameters: +# something: +# name: something +# description: The things we should do. +# required: true +# example: +# - string 1 +# - string 2 +# type: 'string[]' +# responses: +# - status: 200 +# description: 'When the thing was done smoothly.' +# content: # Your response content can be an object, an array, a string or empty. +# { +# "hey": "ho ho ho" +# } +# responseFields: +# hey: +# name: hey +# description: Who knows? +# type: string # This is optional diff --git a/vendor/knuckleswtf/scribe/resources/images/navbar.png b/vendor/knuckleswtf/scribe/resources/images/navbar.png new file mode 100644 index 0000000000000000000000000000000000000000..df38e90d87e1a215371b4977e18cde90f8832537 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*PVQ%R6tFatx` 0) + }, + // map these accent keys to plain values + diacriticsMap: { + a: 'ÀÁÂÃÄÅàáâãäåĀāąĄ', + c: 'ÇçćĆčČ', + d: 'đĐďĎ', + e: 'ÈÉÊËèéêëěĚĒēęĘ', + i: 'ÌÍÎÏìíîïĪī', + l: 'łŁ', + n: 'ÑñňŇńŃ', + o: 'ÒÓÔÕÕÖØòóôõöøŌō', + r: 'řŘ', + s: 'ŠšśŚ', + t: 'ťŤ', + u: 'ÙÚÛÜùúûüůŮŪū', + y: 'ŸÿýÝ', + z: 'ŽžżŻźŹ' + } + }); + + function hashChange() { + const currentItems = document.querySelectorAll('.tocify-subheader.visible, .tocify-item.tocify-focus'); + Array.from(currentItems).forEach((elem) => { + elem.classList.remove('visible', 'tocify-focus'); + }); + + const currentTag = document.querySelector(`a[href="${window.location.hash}"]`); + if (currentTag) { + const parent = currentTag.closest('.tocify-subheader'); + if (parent) { + parent.classList.add('visible'); + } + + const siblings = currentTag.closest('.tocify-header'); + if (siblings) { + Array.from(siblings.querySelectorAll('.tocify-subheader')).forEach((elem) => { + elem.classList.add('visible'); + }); + } + + currentTag.parentElement.classList.add('tocify-focus'); + + // wait for dom changes to be done + setTimeout(() => { + currentTag.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); + // only close the sidebar on level-2 events + if (currentTag.parentElement.classList.contains('level-2')) { + closeSidebar(); + } + }, 1500); + } + } + + let languages = JSON.parse(document.body.getAttribute('data-languages')); + // Support a key => value object where the key is the name, or an array of strings where the value is the name + if (!Array.isArray(languages)) { + languages = Object.values(languages); + } + // if there is no language use the first one + const currentLanguage = window.localStorage.getItem('language') || languages[0]; + const languageStyle = document.getElementById('language-style'); + const langSelector = document.querySelectorAll('.lang-selector button.lang-button'); + + function setActiveLanguage(newLanguage) { + window.localStorage.setItem('language', newLanguage); + if (!languageStyle) { + return; + } + + const newStyle = languages.map((language) => { + return language === newLanguage + // the current one should be visible + ? `body .content .${language}-example pre { display: block; }` + // the inactive one should be hidden + : `body .content .${language}-example pre { display: none; }`; + }).join(`\n`); + + Array.from(langSelector).forEach((elem) => { + elem.classList.toggle('active', elem.getAttribute('data-language-name') === newLanguage); + }); + + const activeHash = window.location.hash.slice(1); + + languageStyle.innerHTML = newStyle; + + setTimeout(() => { + updateHash(activeHash); + }, 200); + } + + setActiveLanguage(currentLanguage); + + Array.from(langSelector).forEach((elem) => { + elem.addEventListener('click', () => { + const newLanguage = elem.getAttribute('data-language-name'); + setActiveLanguage(newLanguage); + }); + }); + + window.addEventListener('hashchange', hashChange, false); + + const divs = document.querySelectorAll('.content h1[id], .content h2[id]'); + + document.addEventListener('scroll', () => { + divs.forEach(item => { + const rect = item.getBoundingClientRect(); + if (rect.top > 0 && rect.top < 150) { + const location = window.location.toString().split('#')[0]; + history.replaceState(null, null, location + '#' + item.id); + hashChange(); + } + }); + }); + + hashChange(); +}); diff --git a/vendor/knuckleswtf/scribe/resources/js/tryitout.js b/vendor/knuckleswtf/scribe/resources/js/tryitout.js new file mode 100644 index 00000000..2a1d2b8a --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/js/tryitout.js @@ -0,0 +1,289 @@ +window.abortControllers = {}; + +function cacheAuthValue() { + // Whenever the auth header is set for one endpoint, cache it for the others + window.lastAuthValue = ''; + let authInputs = document.querySelectorAll(`.auth-value`) + authInputs.forEach(el => { + el.addEventListener('input', (event) => { + window.lastAuthValue = event.target.value; + authInputs.forEach(otherInput => { + if (otherInput === el) return; + // Don't block the main thread + setTimeout(() => { + otherInput.value = window.lastAuthValue; + }, 0); + }); + }); + }); +} + +window.addEventListener('DOMContentLoaded', cacheAuthValue); + +function getCookie(name) { + if (!document.cookie) { + return null; + } + + const cookies = document.cookie.split(';') + .map(c => c.trim()) + .filter(c => c.startsWith(name + '=')); + + if (cookies.length === 0) { + return null; + } + + return decodeURIComponent(cookies[0].split('=')[1]); +} + +function tryItOut(endpointId) { + document.querySelector(`#btn-tryout-${endpointId}`).hidden = true; + document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = false; + const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false; + executeBtn.disabled = false; + + // Show all input fields + document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`) + .forEach(el => el.style.display = 'block'); + + if (document.querySelector(`#form-${endpointId}`).dataset.authed === "1") { + const authElement = document.querySelector(`#auth-${endpointId}`); + authElement && (authElement.hidden = false); + } + // Expand all nested fields + document.querySelectorAll(`#form-${endpointId} details`) + .forEach(el => el.open = true); +} + +function cancelTryOut(endpointId) { + if (window.abortControllers[endpointId]) { + window.abortControllers[endpointId].abort(); + delete window.abortControllers[endpointId]; + } + + document.querySelector(`#btn-tryout-${endpointId}`).hidden = false; + const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`); + executeBtn.hidden = true; + executeBtn.textContent = executeBtn.dataset.initialText; + document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = true; + // Hide inputs + document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`) + .forEach(el => el.style.display = 'none'); + document.querySelectorAll(`#form-${endpointId} details`) + .forEach(el => el.open = false); + const authElement = document.querySelector(`#auth-${endpointId}`); + authElement && (authElement.hidden = true); + + document.querySelector('#execution-results-' + endpointId).hidden = true; + document.querySelector('#execution-error-' + endpointId).hidden = true; + + // Revert to sample code blocks + document.querySelector('#example-requests-' + endpointId).hidden = false; + document.querySelector('#example-responses-' + endpointId).hidden = false; +} + +function makeAPICall(method, path, body = {}, query = {}, headers = {}, endpointId = null) { + console.log({endpointId, path, body, query, headers}); + + if (!(body instanceof FormData) && typeof body !== "string") { + body = JSON.stringify(body) + } + + const url = new URL(window.tryItOutBaseUrl + '/' + path.replace(/^\//, '')); + + // We need this function because if you try to set an array or object directly to a URLSearchParams object, + // you'll get [object Object] or the array.toString() + function addItemToSearchParamsObject(key, value, searchParams) { + if (Array.isArray(value)) { + value.forEach((v, i) => { + // Append {filters: [first, second]} as filters[0]=first&filters[1]second + addItemToSearchParamsObject(key + '[' + i + ']', v, searchParams); + }) + } else if (typeof value === 'object' && value !== null) { + Object.keys(value).forEach((i) => { + // Append {filters: {name: first}} as filters[name]=first + addItemToSearchParamsObject(key + '[' + i + ']', value[i], searchParams); + }); + } else { + searchParams.append(key, value); + } + } + + Object.keys(query) + .forEach(key => addItemToSearchParamsObject(key, query[key], url.searchParams)); + + window.abortControllers[endpointId] = new AbortController(); + + return fetch(url, { + method, + headers, + body: method === 'GET' ? undefined : body, + signal: window.abortControllers[endpointId].signal, + referrer: window.tryItOutBaseUrl, + mode: 'cors', + credentials: 'same-origin', + }) + .then(response => Promise.all([response.status, response.statusText, response.text(), response.headers])); +} + +function hideCodeSamples(endpointId) { + document.querySelector('#example-requests-' + endpointId).hidden = true; + document.querySelector('#example-responses-' + endpointId).hidden = true; +} + +function handleResponse(endpointId, response, status, headers) { + hideCodeSamples(endpointId); + + // Hide error views + document.querySelector('#execution-error-' + endpointId).hidden = true; + + const responseContentEl = document.querySelector('#execution-response-content-' + endpointId); + + // Check if the response contains Laravel's dd() default dump output + const isLaravelDump = response.includes('Sfdump'); + + // If it's a Laravel dd() dump, use innerHTML to render it safely + if (isLaravelDump) { + responseContentEl.innerHTML = response === '' ? responseContentEl.dataset.emptyResponseText : response; + } else { + // Otherwise, stick to textContent for regular responses + responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response; + } + + // Prettify it if it's JSON + let isJson = false; + try { + const jsonParsed = JSON.parse(response); + if (jsonParsed !== null) { + isJson = true; + response = JSON.stringify(jsonParsed, null, 4); + responseContentEl.textContent = response; + } + } catch (e) { + + } + + isJson && window.hljs.highlightElement(responseContentEl); + const statusEl = document.querySelector('#execution-response-status-' + endpointId); + statusEl.textContent = ` (${status})`; + document.querySelector('#execution-results-' + endpointId).hidden = false; + statusEl.scrollIntoView({behavior: "smooth", block: "center"}); +} + +function handleError(endpointId, err) { + hideCodeSamples(endpointId); + // Hide response views + document.querySelector('#execution-results-' + endpointId).hidden = true; + + // Show error views + let errorMessage = err.message || err; + const $errorMessageEl = document.querySelector('#execution-error-message-' + endpointId); + $errorMessageEl.textContent = errorMessage + $errorMessageEl.textContent; + const errorEl = document.querySelector('#execution-error-' + endpointId); + errorEl.hidden = false; + errorEl.scrollIntoView({behavior: "smooth", block: "center"}); + +} + +async function executeTryOut(endpointId, form) { + const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`); + executeBtn.textContent = executeBtn.dataset.loadingText; + executeBtn.disabled = true; + executeBtn.scrollIntoView({behavior: "smooth", block: "center"}); + + let body; + let setter; + if (form.dataset.hasfiles === "1") { + body = new FormData(); + setter = (name, value) => body.append(name, value); + } else if (form.dataset.isarraybody === "1") { + body = []; + setter = (name, value) => _.set(body, name, value); + } else { + body = {}; + setter = (name, value) => _.set(body, name, value); + } + const bodyParameters = form.querySelectorAll('input[data-component=body]'); + bodyParameters.forEach(el => { + let value = el.value; + + if (el.type === 'number' && typeof value === 'string') { + value = parseFloat(value); + } + + if (el.type === 'file' && el.files[0]) { + setter(el.name, el.files[0]); + return; + } + + if (el.type !== 'radio') { + if (value === "" && el.required === false) { + // Don't include empty optional values in the request + return; + } + setter(el.name, value); + return; + } + + if (el.checked) { + value = (value === 'false') ? false : true; + setter(el.name, value); + } + }); + + const query = {}; + const queryParameters = form.querySelectorAll('input[data-component=query]'); + queryParameters.forEach(el => { + if (el.type !== 'radio' || (el.type === 'radio' && el.checked)) { + if (el.value === '') { + // Don't include empty values in the request + return; + } + + _.set(query, el.name, el.value); + } + }); + + let path = form.dataset.path; + const urlParameters = form.querySelectorAll('input[data-component=url]'); + urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value))); + + const headers = Object.fromEntries(Array.from(form.querySelectorAll('input[data-component=header]')) + .map(el => [el.name, el.value])); + + // When using FormData, the browser sets the correct content-type + boundary + let method = form.dataset.method; + if (body instanceof FormData) { + delete headers['Content-Type']; + + // When using FormData with PUT or PATCH, use method spoofing so PHP can access the post body + if (['PUT', 'PATCH'].includes(form.dataset.method)) { + method = 'POST'; + setter('_method', form.dataset.method); + } + } + + let preflightPromise = Promise.resolve(); + if (window.useCsrf && window.csrfUrl) { + preflightPromise = makeAPICall('GET', window.csrfUrl).then(() => { + headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN'); + }); + } + + return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId)) + .then(([responseStatus, statusText, responseContent, responseHeaders]) => { + handleResponse(endpointId, responseContent, responseStatus, responseHeaders) + }) + .catch(err => { + if (err.name === "AbortError") { + console.log("Request cancelled"); + return; + } + console.log("Error while making request: ", err); + handleError(endpointId, err); + }) + .finally(() => { + executeBtn.disabled = false; + executeBtn.textContent = executeBtn.dataset.initialText; + }); +} diff --git a/vendor/knuckleswtf/scribe/resources/views/components/badges/auth.blade.php b/vendor/knuckleswtf/scribe/resources/views/components/badges/auth.blade.php new file mode 100644 index 00000000..609ac323 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/components/badges/auth.blade.php @@ -0,0 +1,3 @@ +@if($authenticated)@component('scribe::components.badges.base', ['colour' => "darkred", 'text' => 'requires authentication']) +@endcomponent +@endif diff --git a/vendor/knuckleswtf/scribe/resources/views/components/badges/base.blade.php b/vendor/knuckleswtf/scribe/resources/views/components/badges/base.blade.php new file mode 100644 index 00000000..0d2b8ff0 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/components/badges/base.blade.php @@ -0,0 +1 @@ +{{ $text }} diff --git a/vendor/knuckleswtf/scribe/resources/views/components/badges/deprecated.blade.php b/vendor/knuckleswtf/scribe/resources/views/components/badges/deprecated.blade.php new file mode 100644 index 00000000..97f76e44 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/components/badges/deprecated.blade.php @@ -0,0 +1,5 @@ +@if($deprecated !== false) +@php($text = $deprecated === true ? 'deprecated' : "deprecated:$deprecated") +@component('scribe::components.badges.base', ['colour' => 'darkgoldenrod', 'text' => $text]) +@endcomponent +@endif diff --git a/vendor/knuckleswtf/scribe/resources/views/components/badges/http-method.blade.php b/vendor/knuckleswtf/scribe/resources/views/components/badges/http-method.blade.php new file mode 100644 index 00000000..0ae7c683 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/components/badges/http-method.blade.php @@ -0,0 +1,5 @@ +@component('scribe::components.badges.base', [ + 'colour' => \Knuckles\Scribe\Tools\WritingUtils::$httpMethodToCssColour[$method], + 'text' => $method, + ]) +@endcomponent diff --git a/vendor/knuckleswtf/scribe/resources/views/components/field-details.blade.php b/vendor/knuckleswtf/scribe/resources/views/components/field-details.blade.php new file mode 100644 index 00000000..06b0b8a3 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/components/field-details.blade.php @@ -0,0 +1,76 @@ +@php + $html ??= []; $class = $html['class'] ?? null; +@endphp +{{ $name }}   +@if($type){{ $type }}@endif  +@if($isInput && !$required)optional@endif   +@if($isInput && $deprecated)deprecated@endif   +@if($isInput && empty($hasChildren)) + @php + $isList = Str::endsWith($type, '[]'); + $fullName = str_replace('[]', '.0', $fullName ?? $name); + $baseType = $isList ? substr($type, 0, -2) : $type; + // Ignore the first '[]': the frontend will take care of it + while (\Str::endsWith($baseType, '[]')) { + $fullName .= '.0'; + $baseType = substr($baseType, 0, -2); + } + // When the body is an array, the item names will be ".0.thing" + $fullName = ltrim($fullName, '.'); + $inputType = match($baseType) { + 'number', 'integer' => 'number', + 'file' => 'file', + default => 'text', + }; + @endphp + @if($type === 'boolean') + + + @elseif($isList) + + + @else + + @endif +@endif +
+@php + if($example !== null && $example !== '' && !is_array($example)) { + $exampleAsString = $example; + if (is_bool($example)) { + $exampleAsString = $example ? "true" : "false"; + } + $description .= " Example: `$exampleAsString`"; + } +@endphp +{!! Parsedown::instance()->text(trim($description)) !!} +@if(!empty($enumValues)) +Must be one of: +
    {!! implode(" ", array_map(fn($val) => "
  • $val
  • ", $enumValues)) !!}
+@endif diff --git a/vendor/knuckleswtf/scribe/resources/views/components/nested-fields.blade.php b/vendor/knuckleswtf/scribe/resources/views/components/nested-fields.blade.php new file mode 100644 index 00000000..acf8a6ba --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/components/nested-fields.blade.php @@ -0,0 +1,105 @@ +@php + $isInput ??= true; + $level ??= 0; +@endphp +@foreach($fields as $name => $field) + @if($name === '[]') + @php + $description = "The request body is an array ({$field['type']}`)"; + $description .= !empty($field['description']) ? ", representing ".lcfirst($field['description'])."." : '.'; + if(count($field['__fields'])) $description .= " Each item has the following properties:"; + @endphp + {!! Parsedown::instance()->text($description) !!} + + @foreach($field['__fields'] as $subfieldName => $subfield) + @if(!empty($subfield['__fields'])) + + @else +
+ @component('scribe::components.field-details', [ + 'name' => $subfieldName, + 'fullName' => $subfield['name'], + 'type' => $subfield['type'] ?? 'string', + 'required' => $subfield['required'] ?? false, + 'deprecated' => $subfield['deprecated'] ?? false, + 'description' => $subfield['description'] ?? '', + 'example' => $subfield['example'] ?? '', + 'enumValues' => $subfield['enumValues'] ?? null, + 'endpointId' => $endpointId, + 'hasChildren' => false, + 'component' => 'body', + 'isInput' => $isInput, + ]) + @endcomponent +
+ @endif + @endforeach + @elseif(!empty($field['__fields'])) +
+
+ + @component('scribe::components.field-details', [ + 'name' => $name, + 'fullName' => $field['name'], + 'type' => $field['type'] ?? 'string', + 'required' => $field['required'] ?? false, + 'deprecated' => $field['deprecated'] ?? false, + 'description' => $field['description'] ?? '', + 'example' => $field['example'] ?? '', + 'enumValues' => $field['enumValues'] ?? null, + 'endpointId' => $endpointId, + 'hasChildren' => true, + 'component' => 'body', + 'isInput' => $isInput, + ]) + @endcomponent + + @foreach($field['__fields'] as $subfieldName => $subfield) + @if(!empty($subfield['__fields'])) + + @else +
+ @component('scribe::components.field-details', [ + 'name' => $subfieldName, + 'fullName' => $subfield['name'], + 'type' => $subfield['type'] ?? 'string', + 'required' => $subfield['required'] ?? false, + 'deprecated' => $subfield['deprecated'] ?? false, + 'description' => $subfield['description'] ?? '', + 'example' => $subfield['example'] ?? '', + 'enumValues' => $subfield['enumValues'] ?? null, + 'endpointId' => $endpointId, + 'hasChildren' => false, + 'component' => 'body', + 'isInput' => $isInput, + ]) + @endcomponent +
+ @endif + @endforeach +
+
+ @else +
+ @component('scribe::components.field-details', [ + 'name' => $name, + 'fullName' => $field['name'], + 'type' => $field['type'] ?? 'string', + 'required' => $field['required'] ?? false, + 'deprecated' => $field['deprecated'] ?? false, + 'description' => $field['description'] ?? '', + 'example' => $field['example'] ?? '', + 'enumValues' => $field['enumValues'] ?? null, + 'endpointId' => $endpointId, + 'hasChildren' => false, + 'component' => 'body', + 'isInput' => $isInput, + ]) + @endcomponent +
+ @endif +@endforeach diff --git a/vendor/knuckleswtf/scribe/resources/views/external/elements.blade.php b/vendor/knuckleswtf/scribe/resources/views/external/elements.blade.php new file mode 100644 index 00000000..bff12b3a --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/external/elements.blade.php @@ -0,0 +1,34 @@ + + + + + + + {!! $metadata['title'] !!} + + + + + + + + $value) + {{-- Attributes specified first override later ones --}} + {!! $attribute !!}="{!! $value !!}" +@endforeach + apiDescriptionUrl="{!! $metadata['openapi_spec_url'] !!}" + router="hash" + layout="sidebar" + hideTryIt="{!! ($tryItOut['enabled'] ?? true) ? '' : 'true'!!}" +@if(!empty($metadata['logo'])) + logo="{!! $metadata['logo'] !!}" +@endif +/> + + + diff --git a/vendor/knuckleswtf/scribe/resources/views/external/rapidoc.blade.php b/vendor/knuckleswtf/scribe/resources/views/external/rapidoc.blade.php new file mode 100644 index 00000000..c4729fad --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/external/rapidoc.blade.php @@ -0,0 +1,23 @@ + + + + + + + + + $value) + {{-- Attributes specified first override later ones --}} + {!! $attribute !!}="{!! $value !!}" +@endforeach + spec-url="{!! $metadata['openapi_spec_url'] !!}" + render-style="read" + allow-try="{!! ($tryItOut['enabled'] ?? true) ? 'true' : 'false'!!}" +> + @if($metadata['logo']) + + @endif + + + diff --git a/vendor/knuckleswtf/scribe/resources/views/external/scalar.blade.php b/vendor/knuckleswtf/scribe/resources/views/external/scalar.blade.php new file mode 100644 index 00000000..062d3cfd --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/external/scalar.blade.php @@ -0,0 +1,27 @@ + + + + {!! $metadata['title'] !!} + + + + + + + + + + diff --git a/vendor/knuckleswtf/scribe/resources/views/markdown/auth.blade.php b/vendor/knuckleswtf/scribe/resources/views/markdown/auth.blade.php new file mode 100644 index 00000000..0abe1ab4 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/markdown/auth.blade.php @@ -0,0 +1,12 @@ +@php + use Knuckles\Scribe\Tools\Utils as u; +@endphp +# {{ u::trans("scribe::headings.auth") }} + +@if(!$isAuthed) +{!! u::trans("scribe::auth.none") !!} +@else +{!! $authDescription !!} + +{!! $extraAuthInfo !!} +@endif diff --git a/vendor/knuckleswtf/scribe/resources/views/markdown/intro.blade.php b/vendor/knuckleswtf/scribe/resources/views/markdown/intro.blade.php new file mode 100644 index 00000000..66c5d19d --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/markdown/intro.blade.php @@ -0,0 +1,13 @@ +@php + use Knuckles\Scribe\Tools\Utils as u; +@endphp +# {{ u::trans("scribe::headings.introduction") }} + +{!! $description !!} + + + +{!! $introText !!} + diff --git a/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/bash.md.blade.php b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/bash.md.blade.php new file mode 100644 index 00000000..87c67ce1 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/bash.md.blade.php @@ -0,0 +1,34 @@ +@php + use Knuckles\Scribe\Tools\WritingUtils as u; + /** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp +```bash +curl --request {{$endpoint->httpMethods[0]}} \ + {{$endpoint->httpMethods[0] == 'GET' ? '--get ' : ''}}"{!! rtrim($baseUrl, '/') !!}/{{ ltrim($endpoint->boundUri, '/') }}@if(count($endpoint->cleanQueryParameters))?{!! u::printQueryParamsAsString($endpoint->cleanQueryParameters) !!}@endif"@if(count($endpoint->headers)) \ +@foreach($endpoint->headers as $header => $value) + --header "{{$header}}: {{ addslashes($value) }}"@if(! ($loop->last) || ($loop->last && count($endpoint->bodyParameters))) \ +@endif +@endforeach +@endif +@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters))) +@foreach($endpoint->cleanBodyParameters as $parameter => $value) +@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue) + --form "{!! "$key=".$actualValue !!}"@if(!($loop->parent->last) || count($endpoint->fileParameters))\ +@endif +@endforeach +@endforeach +@foreach($endpoint->fileParameters as $parameter => $value) +@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file) + --form "{!! "$key=@".$file->path() !!}" @if(!($loop->parent->last))\ +@endif +@endforeach +@endforeach +@elseif(count($endpoint->cleanBodyParameters)) +@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded') + --data "{!! http_build_query($endpoint->cleanBodyParameters, '', '&') !!}" +@else + --data "{!! addslashes(json_encode($endpoint->cleanBodyParameters, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) !!}" +@endif +@endif + +``` diff --git a/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/javascript.md.blade.php b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/javascript.md.blade.php new file mode 100644 index 00000000..3dc9335b --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/javascript.md.blade.php @@ -0,0 +1,62 @@ +@php + use Knuckles\Scribe\Tools\WritingUtils as u; + /** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp +```javascript +const url = new URL( + "{!! rtrim($baseUrl, '/') !!}/{{ ltrim($endpoint->boundUri, '/') }}" +); +@if(count($endpoint->cleanQueryParameters)) + +const params = {!! u::printQueryParamsAsKeyValue($endpoint->cleanQueryParameters, "\"", ":", 4, "{}") !!}; +Object.keys(params) + .forEach(key => url.searchParams.append(key, params[key])); +@endif + +@if(!empty($endpoint->headers)) +const headers = { +@foreach($endpoint->headers as $header => $value) + "{{$header}}": "{{$value}}", +@endforeach +@empty($endpoint->headers['Accept']) + "Accept": "application/json", +@endempty +}; +@endif + +@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters))) +const body = new FormData(); +@foreach($endpoint->cleanBodyParameters as $parameter => $value) +@foreach( u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue) +body.append('{!! $key !!}', '{!! $actualValue !!}'); +@endforeach +@endforeach +@foreach($endpoint->fileParameters as $parameter => $value) +@foreach( u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file) +body.append('{!! $key !!}', document.querySelector('input[name="{!! $key !!}"]').files[0]); +@endforeach +@endforeach +@elseif(count($endpoint->cleanBodyParameters)) +@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded') +let body = "{!! http_build_query($endpoint->cleanBodyParameters, '', '&') !!}"; +@else +let body = {!! json_encode($endpoint->cleanBodyParameters, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!}; +@endif +@endif + +fetch(url, { + method: "{{$endpoint->httpMethods[0]}}", +@if(count($endpoint->headers)) + headers, +@endif +@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters))) + body, +@elseif(count($endpoint->cleanBodyParameters)) +@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded') + body, +@else + body: JSON.stringify(body), +@endif +@endif +}).then(response => response.json()); +``` diff --git a/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/php.md.blade.php b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/php.md.blade.php new file mode 100644 index 00000000..36db15ff --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/php.md.blade.php @@ -0,0 +1,51 @@ +@php + use Knuckles\Scribe\Tools\WritingUtils as u; + /** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp +```php +$client = new \GuzzleHttp\Client(); +$url = '{!! rtrim($baseUrl, '/') . '/' . ltrim($endpoint->boundUri, '/') !!}'; +@if($endpoint->hasHeadersOrQueryOrBodyParams()) +$response = $client->{{ strtolower($endpoint->httpMethods[0]) }}( + $url, + [ +@if(!empty($endpoint->headers)) + 'headers' => {!! u::printPhpValue($endpoint->headers, 8) !!}, +@endif +@if(!empty($endpoint->cleanQueryParameters)) + 'query' => {!! u::printQueryParamsAsKeyValue($endpoint->cleanQueryParameters, "'", " =>", 12, "[]", 8) !!}, +@endif +@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && !empty($endpoint->cleanBodyParameters))) + 'multipart' => [ +@foreach($endpoint->cleanBodyParameters as $parameter => $value) +@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue) + [ + 'name' => '{!! $key !!}', + 'contents' => '{!! $actualValue !!}' + ], +@endforeach +@endforeach +@foreach($endpoint->fileParameters as $parameter => $value) +@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file) + [ + 'name' => '{!! $key !!}', + 'contents' => fopen('{!! $file->path() !!}', 'r') + ], +@endforeach +@endforeach + ], +@elseif(count($endpoint->cleanBodyParameters)) +@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded') + 'form_params' => {!! u::printPhpValue($endpoint->cleanBodyParameters, 8) !!}, +@else + 'json' => {!! u::printPhpValue($endpoint->cleanBodyParameters, 8) !!}, +@endif +@endif + ] +); +@else +$response = $client->{{ strtolower($endpoint->httpMethods[0]) }}($url); +@endif +$body = $response->getBody(); +print_r(json_decode((string) $body)); +``` diff --git a/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/python.md.blade.php b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/python.md.blade.php new file mode 100644 index 00000000..6ba7bad1 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/partials/example-requests/python.md.blade.php @@ -0,0 +1,52 @@ +@php + use Knuckles\Scribe\Tools\WritingUtils as u; + /** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp +```python +import requests +import json + +url = '{!! rtrim($baseUrl, '/') !!}/{{ $endpoint->boundUri }}' +@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters))) +files = { +@foreach($endpoint->cleanBodyParameters as $parameter => $value) +@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue) + '{!! $key !!}': (None, '{!! $actualValue !!}')@if(!($loop->parent->last) || count($endpoint->fileParameters)), +@endif +@endforeach +@endforeach +@foreach($endpoint->fileParameters as $parameter => $value) +@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file) + '{!! $key !!}': open('{!! $file->path() !!}', 'rb')@if(!($loop->parent->last)), +@endif +@endforeach +@endforeach +} +@endif +@if(count($endpoint->cleanBodyParameters)) +payload = {!! json_encode($endpoint->cleanBodyParameters, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!} +@endif +@if(count($endpoint->cleanQueryParameters)) +params = {!! u::printQueryParamsAsKeyValue($endpoint->cleanQueryParameters, "'", ":", 2, "{}") !!} +@endif +@if(!empty($endpoint->headers)) +headers = { +@foreach($endpoint->headers as $header => $value) + '{{$header}}': '{{$value}}'@if(!($loop->last)), +@endif +@endforeach + +} + +@endif +@php +$optionalArguments = []; +if (count($endpoint->headers)) $optionalArguments[] = "headers=headers"; +if (count($endpoint->fileParameters)) $optionalArguments[] = "files=files"; +if (count($endpoint->cleanBodyParameters) && $endpoint->headers['Content-Type'] != 'multipart/form-data') $optionalArguments[] = (count($endpoint->fileParameters) || $endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded' ? "data=payload" : "json=payload"); +if (count($endpoint->cleanQueryParameters)) $optionalArguments[] = "params=params"; +$optionalArguments = implode(', ',$optionalArguments); +@endphp +response = requests.request('{{$endpoint->httpMethods[0]}}', url, {{ $optionalArguments }}) +response.json() +``` diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/default/endpoint.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/default/endpoint.blade.php new file mode 100644 index 00000000..47b31471 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/default/endpoint.blade.php @@ -0,0 +1,190 @@ +@php + use Knuckles\Scribe\Tools\Utils as u; + /** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp + +

{{ $endpoint->name() }}

+ +

+@component('scribe::components.badges.auth', ['authenticated' => $endpoint->isAuthed()]) +@endcomponent +@component('scribe::components.badges.deprecated', ['deprecated' => $endpoint->metadata->deprecated]) +@endcomponent +

+ +{!! Parsedown::instance()->text($endpoint->metadata->description ?: '') !!} + + +
{{ u::trans("scribe::endpoint.example_request") }}:
+ +@foreach($metadata['example_languages'] as $language) + +
+ @include("scribe::partials.example-requests.$language") +
+ +@endforeach +
+ + +@if($endpoint->isGet() || $endpoint->hasResponses()) + @foreach($endpoint->responses as $response) +
+

{{ u::trans("scribe::endpoint.example_response") }} ({{ $response->fullDescription() }}):

+
+ @if(count($response->headers)) +
+ + Show headers + +
@foreach($response->headers as $header => $value)
+{{ $header }}: {{ is_array($value) ? implode('; ', $value) : $value }}
+@endforeach 
@endif +
+@if($response->isBinary())
+{!! u::trans("scribe::endpoint.responses.binary") !!} - {{ htmlentities(str_replace("<>", "", $response->content)) }}
+@elseif($response->status == 204)
+{!! u::trans("scribe::endpoint.responses.empty") !!}
+@else
+@php($parsed = json_decode($response->content))
+{{-- If response is a JSON string, prettify it. Otherwise, just print it --}}
+{!! htmlentities($parsed != null ? json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $response->content) !!}
+@endif 
+ @endforeach +@endif +
+ + +
+

+ {{ u::trans("scribe::endpoint.request") }}    + @if($metadata['try_it_out']['enabled'] ?? false) + +    + + @endif +

+ @foreach($endpoint->httpMethods as $method) +

+ @component('scribe::components.badges.http-method', ['method' => $method])@endcomponent + {{$endpoint->uri}} +

+ @endforeach + @if(count($endpoint->headers)) +

{{ u::trans("scribe::endpoint.headers") }}

+ @foreach($endpoint->headers as $name => $example) + isAuthed() && 'header' == $metadata['auth']['location'] && $metadata['auth']['name'] == $name) { + $htmlOptions = ['class' => 'auth-value']; + } + ?> +
+ @component('scribe::components.field-details', [ + 'name' => $name, + 'type' => null, + 'required' => true, + 'deprecated' => false, + 'description' => null, + 'example' => $example, + 'endpointId' => $endpoint->endpointId(), + 'component' => 'header', + 'isInput' => true, + 'html' => $htmlOptions, + ]) + @endcomponent +
+ @endforeach + @endif + @if(count($endpoint->urlParameters)) +

{{ u::trans("scribe::endpoint.url_parameters") }}

+ @foreach($endpoint->urlParameters as $attribute => $parameter) +
+ @component('scribe::components.field-details', [ + 'name' => $parameter->name, + 'type' => $parameter->type ?? 'string', + 'required' => $parameter->required, + 'deprecated' => $parameter->deprecated, + 'description' => $parameter->description, + 'example' => $parameter->example ?? '', + 'enumValues' => $parameter->enumValues, + 'endpointId' => $endpoint->endpointId(), + 'component' => 'url', + 'isInput' => true, + ]) + @endcomponent +
+ @endforeach + @endif + @if(count($endpoint->queryParameters)) +

{{ u::trans("scribe::endpoint.query_parameters") }}

+ @foreach($endpoint->queryParameters as $attribute => $parameter) + isAuthed() && 'query' == $metadata['auth']['location'] && $metadata['auth']['name'] == $attribute) { + $htmlOptions = ['class' => 'auth-value']; + } + ?> +
+ @component('scribe::components.field-details', [ + 'name' => $parameter->name, + 'type' => $parameter->type, + 'required' => $parameter->required, + 'deprecated' => $parameter->deprecated, + 'description' => $parameter->description, + 'example' => $parameter->example ?? '', + 'enumValues' => $parameter->enumValues, + 'endpointId' => $endpoint->endpointId(), + 'component' => 'query', + 'isInput' => true, + 'html' => $htmlOptions, + ]) + @endcomponent +
+ @endforeach + @endif + @if(count($endpoint->nestedBodyParameters)) +

{{ u::trans("scribe::endpoint.body_parameters") }}

+ + @endif + + +@if(count($endpoint->responseFields)) +

{{ u::trans("scribe::endpoint.response") }}

+

{{ u::trans("scribe::endpoint.response_fields") }}

+ +@endif diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/default/groups.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/default/groups.blade.php new file mode 100644 index 00000000..18df4014 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/default/groups.blade.php @@ -0,0 +1,21 @@ +@foreach($groupedEndpoints as $group) +

{!! $group['name'] !!}

+ + {!! Parsedown::instance()->text($group['description']) !!} + + @foreach($group['subgroups'] as $subgroupName => $subgroup) + @if($subgroupName !== "") +

{{ $subgroupName }}

+ @php($subgroupDescription = collect($subgroup)->first(fn ($e) => $e->metadata->subgroupDescription)?->metadata?->subgroupDescription) + @if($subgroupDescription) +

+ {!! Parsedown::instance()->text($subgroupDescription) !!} +

+ @endif + @endif + @foreach($subgroup as $endpoint) + @include("scribe::themes.default.endpoint") + @endforeach + @endforeach +@endforeach + diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/default/index.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/default/index.blade.php new file mode 100644 index 00000000..c528de6f --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/default/index.blade.php @@ -0,0 +1,74 @@ +@php + use Knuckles\Scribe\Tools\WritingUtils as u; +@endphp + + + + + + + {!! $metadata['title'] !!} + + + + + + + + + + + + + +@if(isset($metadata['example_languages'])) + +@endif + +@if($tryItOut['enabled'] ?? true) + + +@endif + + + + + + + +@include("scribe::themes.default.sidebar") + +
+
+
+ {!! $intro !!} + + {!! $auth !!} + + @include("scribe::themes.default.groups") + + {!! $append !!} +
+
+ @if(isset($metadata['example_languages'])) +
+ @foreach($metadata['example_languages'] as $name => $lang) + @php if (is_numeric($name)) $name = $lang; @endphp + + @endforeach +
+ @endif +
+
+ + diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/default/sidebar.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/default/sidebar.blade.php new file mode 100644 index 00000000..53bbc802 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/default/sidebar.blade.php @@ -0,0 +1,69 @@ +@php + use Knuckles\Scribe\Tools\Utils as u; +@endphp + + + MENU + navbar-image + + +
+ @if($metadata['logo'] != false) + + @endif + + @isset($metadata['example_languages']) +
+ @foreach($metadata['example_languages'] as $name => $lang) + @php if (is_numeric($name)) $name = $lang; @endphp + + @endforeach +
+ @endisset + + + +
+ @foreach($headings as $h1) +
    +
  • + {!! $h1['name'] !!} +
  • + @if(count($h1['subheadings']) > 0) +
      + @foreach($h1['subheadings'] as $h2) +
    • + {!! $h2['name'] !!} +
    • + @if(count($h2['subheadings']) > 0) + + @endif + @endforeach +
    + @endif +
+ @endforeach +
+ + + + +
diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/components/field-details.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/components/field-details.blade.php new file mode 100644 index 00000000..f960dedd --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/components/field-details.blade.php @@ -0,0 +1,70 @@ +@php + $hasChildren ??= false; + $isArrayBody = $name == "[]"; + $expandable = $hasChildren && !$isArrayBody; +@endphp + +
+
+
+
+ @if($expandable) +
+ +
+ @endif + @unless($isArrayBody) +
+
{{ $name }}
+ @if($type) + {{ $type }} + @endif +
+ @if($required || $deprecated) +
+
+ @if($required) + required + @endif + @if($deprecated) + deprecated + @endif +
+ @endif + @endunless +
+ @if($description) +
+ {!! Parsedown::instance()->text($description) !!} +
+ @endif + @if(!empty($enumValues)) + Must be one of: +
    {!! implode(" ", array_map(fn($val) => "
  • $val
  • ", $enumValues)) !!}
+ @endif + @if($isArrayBody) +
+
array of:
+ @if($required) +
+ required + @endif +
+ @endif + @if(!$hasChildren && !is_null($example) && $example !== '') +
+ Example: +
+
+ {{ is_array($example) || is_bool($example) ? json_encode($example) : $example }} +
+
+
+ @endif +
+
diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/components/nested-fields.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/components/nested-fields.blade.php new file mode 100644 index 00000000..66e93d1e --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/components/nested-fields.blade.php @@ -0,0 +1,38 @@ +@php + $level ??= 0; + $levelNestingClass = match($level) { + 0 => "sl-ml-px", + default => "sl-ml-7" + }; + $expandable ??= !isset($fields["[]"]); +@endphp + +@foreach($fields as $name => $field) +
+ @component('scribe::themes.elements.components.field-details', [ + 'name' => $name, + 'type' => $field['type'] ?? 'string', + 'required' => $field['required'] ?? false, + 'deprecated' => $field['deprecated'] ?? false, + 'description' => $field['description'] ?? '', + 'example' => $field['example'] ?? '', + 'enumValues' => $field['enumValues'] ?? null, + 'endpointId' => $endpointId, + 'hasChildren' => !empty($field['__fields']), + 'component' => 'body', + ]) + @endcomponent + + @if(!empty($field['__fields'])) +
+ @component('scribe::themes.elements.components.nested-fields', [ + 'fields' => $field['__fields'], + 'endpointId' => $endpointId, + 'level' => $level + 1, + 'expandable'=> $expandable, + ]) + @endcomponent +
+ @endif +
+@endforeach diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/endpoint.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/endpoint.blade.php new file mode 100644 index 00000000..0233a2d9 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/endpoint.blade.php @@ -0,0 +1,281 @@ +@php + use Knuckles\Scribe\Tools\Utils as u; + /** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp + +
+
+
+
+

+ {{ $endpoint->name() }} +

+
+
+ +
+
+ @foreach($endpoint->httpMethods as $method) +
+ {{ $method }} +
+ @endforeach +
+
{!! rtrim($baseUrl, '/') !!}
+
/{{ ltrim($endpoint->uri, '/') }}
+
+ + @if($endpoint->metadata->authenticated) +
requires authentication +
+ @endif + @if($endpoint->metadata->deprecated === true) +
deprecated +
+ @endif + @if(is_string($endpoint->metadata->deprecated)) +
deprecated:{{$endpoint->metadata->deprecated}} +
+ @endif +
+
+ + {!! Parsedown::instance()->text($endpoint->metadata->description ?: '') !!} +
+
+
+
+
+ @if(count($endpoint->headers)) +
+

+ {{ u::trans("scribe::endpoint.headers") }} +

+
+ @foreach($endpoint->headers as $header => $value) + @component('scribe::themes.elements.components.field-details', [ + 'name' => $header, + 'type' => null, + 'required' => false, + 'deprecated' => false, + 'description' => null, + 'example' => $value, + 'endpointId' => $endpoint->endpointId(), + 'component' => 'header', + 'isInput' => true, + ]) + @endcomponent + @endforeach +
+
+ @endif + + @if(count($endpoint->urlParameters)) +
+

{{ u::trans("scribe::endpoint.url_parameters") }}

+ +
+ @foreach($endpoint->urlParameters as $attribute => $parameter) + @component('scribe::themes.elements.components.field-details', [ + 'name' => $parameter->name, + 'type' => $parameter->type ?? 'string', + 'required' => $parameter->required, + 'deprecated' => $parameter->deprecated, + 'description' => $parameter->description, + 'example' => $parameter->example ?? '', + 'enumValues' => $parameter->enumValues, + 'endpointId' => $endpoint->endpointId(), + 'component' => 'url', + 'isInput' => true, + ]) + @endcomponent + @endforeach +
+
+ @endif + + + @if(count($endpoint->queryParameters)) +
+

{{ u::trans("scribe::endpoint.query_parameters") }}

+ +
+ @foreach($endpoint->queryParameters as $attribute => $parameter) + @component('scribe::themes.elements.components.field-details', [ + 'name' => $parameter->name, + 'type' => $parameter->type, + 'required' => $parameter->required, + 'deprecated' => $parameter->deprecated, + 'description' => $parameter->description, + 'example' => $parameter->example ?? '', + 'enumValues' => $parameter->enumValues, + 'endpointId' => $endpoint->endpointId(), + 'component' => 'query', + 'isInput' => true, + ]) + @endcomponent + @endforeach +
+
+ @endif + + @if(count($endpoint->nestedBodyParameters)) +
+

{{ u::trans("scribe::endpoint.body_parameters") }}

+ +
+ @component('scribe::themes.elements.components.nested-fields', [ + 'fields' => $endpoint->nestedBodyParameters, + 'endpointId' => $endpoint->endpointId(), + ]) + @endcomponent +
+
+ @endif + + @if(count($endpoint->responseFields)) +
+

{{ u::trans("scribe::endpoint.response_fields") }}

+ +
+ @component('scribe::themes.elements.components.nested-fields', [ + 'fields' => $endpoint->nestedResponseFields, + 'endpointId' => $endpoint->endpointId(), + 'isInput' => false, + ]) + @endcomponent +
+
+ @endif +
+
+
+ +
+
+ + @if($metadata['try_it_out']['enabled'] ?? false) + @include("scribe::themes.elements.try_it_out") + @endif + + @if($metadata['example_languages']) +
+
+
+
+ {{ u::trans("scribe::endpoint.example_request") }}: + +
+
+
+ @foreach($metadata['example_languages'] as $index => $language) +
+
+
+ @include("scribe::partials.example-requests.$language") +
+
+
+ @endforeach +
+ @endif + + @if($endpoint->isGet() || $endpoint->hasResponses()) +
+
+
+
+
+
{{ u::trans("scribe::endpoint.example_response") }}:
+
+
+
+
+
+ +
+ @foreach($endpoint->responses as $index => $response) +
+
@if(count($response->headers)) +
+ + + + + + + Headers + + + @php + $headerLines = []; + foreach($response->headers as $header => $value) { + $headerLines[] = $header . ': ' . (is_array($value) ? implode('; ', $value) : $value); + } + @endphp +
{{ implode("\n", $headerLines) }}
+
+ @endif + @if($response->isBinary()) +
[{{ u::trans("scribe::endpoint.responses.binary") }}] - {{ htmlentities(str_replace("<>", "", $response->content)) }}
+ @elseif($response->status == 204) +
[{{ u::trans("scribe::endpoint.responses.empty") }}]
+ @else + @php($parsed = json_decode($response->content)) + {{-- If response is a JSON string, prettify it. Otherwise, just print it --}} +
{!! htmlentities($parsed != null ? json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $response->content) !!}
+ @endif +
+
+ @endforeach +
+ @endif +
+
+
+ diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/groups.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/groups.blade.php new file mode 100644 index 00000000..ddfd8abf --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/groups.blade.php @@ -0,0 +1,28 @@ +@foreach($groupedEndpoints as $group) +

+ {!! $group['name'] !!} +

+ + {!! Parsedown::instance()->text($group['description']) !!} + + @foreach($group['subgroups'] as $subgroupName => $subgroup) + @if($subgroupName !== "") +

+ {{ $subgroupName }} +

+ @php($subgroupDescription = collect($subgroup)->first(fn ($e) => $e->metadata->subgroupDescription)?->metadata?->subgroupDescription) + @if($subgroupDescription) + {!! Parsedown::instance()->text($subgroupDescription) !!} + @endif +
+ @endif + @foreach($subgroup as $endpoint) + @include("scribe::themes.elements.endpoint") + @endforeach + @endforeach +@endforeach + diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/index.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/index.blade.php new file mode 100644 index 00000000..626cfedd --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/index.blade.php @@ -0,0 +1,360 @@ +@php + use Knuckles\Scribe\Tools\WritingUtils as u; +@endphp + + + + + + + + {!! $metadata['title'] !!} + + + + + + + + + + + + + @if($tryItOut['enabled'] ?? true) + + + + + + @endif + + + + + +@if($metadata['example_languages']) + +@endif + + + +
+ + @include("scribe::themes.elements.sidebar") + +
+ +
+
+

+ {!! $metadata['title'] !!} +

+ @if($metadata['postman_collection_url']) + + Postman collection → + + @endif + @if($metadata['openapi_spec_url']) + + OpenAPI spec → + + @endif +
+ +
+ {!! $intro !!} + + {!! $auth !!} +
+
+ + @include("scribe::themes.elements.groups") + +
+ {!! $append !!} +
+
+ +
+ + + + + + + + + + diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/sidebar.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/sidebar.blade.php new file mode 100644 index 00000000..9191e859 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/sidebar.blade.php @@ -0,0 +1,92 @@ + diff --git a/vendor/knuckleswtf/scribe/resources/views/themes/elements/try_it_out.blade.php b/vendor/knuckleswtf/scribe/resources/views/themes/elements/try_it_out.blade.php new file mode 100644 index 00000000..b0b42bf3 --- /dev/null +++ b/vendor/knuckleswtf/scribe/resources/views/themes/elements/try_it_out.blade.php @@ -0,0 +1,322 @@ +@php + use Knuckles\Scribe\Tools\Utils as u; + /** @var \Knuckles\Camel\Output\OutputEndpointData $endpoint */ +@endphp + +
+
+
+ @if($endpoint->isAuthed() && $metadata['auth']['location'] !== 'body') + + @endif + + @if(count($endpoint->headers)) + + @endif + + @if(count($endpoint->urlParameters)) + + @endif + + @if(count($endpoint->queryParameters)) + + @endif + + @if(count($endpoint->bodyParameters)) + + @endif + +
+
+ +
+
+ + + + +
+
+
diff --git a/vendor/knuckleswtf/scribe/routes/laravel.php b/vendor/knuckleswtf/scribe/routes/laravel.php new file mode 100644 index 00000000..f4b8ebcd --- /dev/null +++ b/vendor/knuckleswtf/scribe/routes/laravel.php @@ -0,0 +1,21 @@ +group(function () use ($prefix) { + Route::view($prefix, 'scribe.index')->name('scribe'); + + Route::get("{$prefix}.postman", function () { + return new JsonResponse(Storage::disk('local')->get('scribe/collection.json'), json: true); + })->name('scribe.postman'); + + Route::get("{$prefix}.openapi", function () { + return response()->file(Storage::disk('local')->path('scribe/openapi.yaml')); + })->name('scribe.openapi'); + }); diff --git a/vendor/knuckleswtf/scribe/src/Attributes/Authenticated.php b/vendor/knuckleswtf/scribe/src/Attributes/Authenticated.php new file mode 100644 index 00000000..4b431b76 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/Authenticated.php @@ -0,0 +1,16 @@ + $this->authenticated]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/BodyParam.php b/vendor/knuckleswtf/scribe/src/Attributes/BodyParam.php new file mode 100644 index 00000000..67a05f83 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/BodyParam.php @@ -0,0 +1,6 @@ + $this->deprecated]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/Endpoint.php b/vendor/knuckleswtf/scribe/src/Attributes/Endpoint.php new file mode 100644 index 00000000..86c93afe --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/Endpoint.php @@ -0,0 +1,29 @@ + $this->title, + 'description' => $this->description, + ]; + if (! is_null($this->authenticated)) { + $data['authenticated'] = $this->authenticated; + } + + return $data; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/GenericParam.php b/vendor/knuckleswtf/scribe/src/Attributes/GenericParam.php new file mode 100644 index 00000000..97db4550 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/GenericParam.php @@ -0,0 +1,57 @@ + $this->name, + 'description' => $this->description, + 'type' => $this->type, + 'required' => $this->required, + 'example' => $this->example, + 'enumValues' => $this->getEnumValues(), + 'nullable' => $this->nullable, + 'deprecated' => $this->deprecated, + ]; + } + + protected function getEnumValues(): array + { + if (! $this->enum) { + return []; + } + + if (is_array($this->enum)) { + return $this->enum; + } + + if (enum_exists($this->enum) && method_exists($this->enum, 'tryFrom')) { + return array_map( + // $case->value only exists on BackedEnums, not UnitEnums + // method_exists($enum, 'tryFrom') implies the enum is a BackedEnum + // @phpstan-ignore-next-line + fn ($case) => $case->value, + $this->enum::cases() + ); + } + + throw new \InvalidArgumentException( + 'The enum property of a parameter must be either a PHP enum or an array of values' + ); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/Group.php b/vendor/knuckleswtf/scribe/src/Attributes/Group.php new file mode 100644 index 00000000..4f425556 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/Group.php @@ -0,0 +1,45 @@ + $this->getName(), + 'groupDescription' => $this->description, + ]; + + if (! is_null($this->authenticated)) { + $data['authenticated'] = $this->authenticated; + } + + return $data; + } + + protected function getName(): string + { + if (is_string($this->name)) { + return $this->name; + } + + if (interface_exists('BackedEnum') && is_a($this->name, 'BackedEnum')) { + return $this->name->value; + } + + throw new \InvalidArgumentException( + 'The name property of a group must be either a PHP Backed Enum or a string' + ); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/Header.php b/vendor/knuckleswtf/scribe/src/Attributes/Header.php new file mode 100644 index 00000000..5884696e --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/Header.php @@ -0,0 +1,20 @@ + $this->name, + 'example' => $this->example, + ]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/QueryParam.php b/vendor/knuckleswtf/scribe/src/Attributes/QueryParam.php new file mode 100644 index 00000000..acb2905b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/QueryParam.php @@ -0,0 +1,6 @@ + $this->status, + 'content' => match (true) { + is_null($this->content) => null, + is_string($this->content) => $this->content, + default => json_encode($this->content, JSON_THROW_ON_ERROR), + }, + 'description' => $this->description, + ]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/ResponseField.php b/vendor/knuckleswtf/scribe/src/Attributes/ResponseField.php new file mode 100644 index 00000000..7363f7a3 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/ResponseField.php @@ -0,0 +1,20 @@ +model)) { + return $this->model; + } + + return ApiResourceResponseTools::tryToInferApiResourceModel($this->name); + } + + public function isCollection(): bool + { + if (! is_null($this->collection)) { + return $this->collection; + } + + return is_subclass_of($this->name, ResourceCollection::class); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/ResponseFromFile.php b/vendor/knuckleswtf/scribe/src/Attributes/ResponseFromFile.php new file mode 100644 index 00000000..d7eec36d --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/ResponseFromFile.php @@ -0,0 +1,25 @@ + $this->status, + 'description' => $this->description, + 'content' => ResponseFileTools::getResponseContents($this->file, $this->merge), + ]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/ResponseFromTransformer.php b/vendor/knuckleswtf/scribe/src/Attributes/ResponseFromTransformer.php new file mode 100644 index 00000000..32df0964 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/ResponseFromTransformer.php @@ -0,0 +1,23 @@ + $this->getName(), + 'subgroupDescription' => $this->description, + ]; + } + + protected function getName(): string + { + if (is_string($this->name)) { + return $this->name; + } + + if (interface_exists('BackedEnum') && is_a($this->name, 'BackedEnum')) { + return $this->name->value; + } + + throw new \InvalidArgumentException( + 'The name property of a subgroup must be either a PHP Backed Enum or a string' + ); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/Unauthenticated.php b/vendor/knuckleswtf/scribe/src/Attributes/Unauthenticated.php new file mode 100644 index 00000000..3dbad6d5 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/Unauthenticated.php @@ -0,0 +1,12 @@ + false]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Attributes/UrlParam.php b/vendor/knuckleswtf/scribe/src/Attributes/UrlParam.php new file mode 100644 index 00000000..30057f62 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Attributes/UrlParam.php @@ -0,0 +1,6 @@ +option('config')); + $defaultConfig = require __DIR__.'/../../config/scribe.php'; + + $ignore = ['example_languages', 'routes', 'description', 'auth.extra_info', 'intro_text', 'groups']; + $asList = ['strategies.*', 'examples.models_source']; + $differ = new ConfigDiffer(original: $defaultConfig, changed: $usersConfig, ignorePaths: $ignore, asList: $asList); + + $diff = $differ->getDiff(); + + if (empty($diff)) { + $this->info('------ SAME AS DEFAULT CONFIG ------'); + + return; + } + + foreach ($diff as $key => $item) { + $this->line("{$key} => {$item}"); + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Commands/GenerateDocumentation.php b/vendor/knuckleswtf/scribe/src/Commands/GenerateDocumentation.php new file mode 100644 index 00000000..fc4f6c98 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Commands/GenerateDocumentation.php @@ -0,0 +1,257 @@ +`} + "; + + protected $description = 'Generate API documentation from your Laravel routes.'; + + protected DocumentationConfig $docConfig; + + protected bool $shouldExtract; + + protected bool $forcing; + + protected PathConfig $paths; + + public function handle(RouteMatcherInterface $routeMatcher, GroupedEndpointsFactory $groupedEndpointsFactory): void + { + $this->bootstrap(); + + if (! empty($this->docConfig->get('default_group'))) { + $this->warn('It looks like you just upgraded to Scribe v4.'); + $this->warn('Please run the upgrade command first: `php artisan scribe:upgrade`.'); + + exit(1); + } + + // Extraction stage - extract endpoint info either from app or existing Camel files (previously extracted data) + $groupedEndpointsInstance = $groupedEndpointsFactory->make($this, $routeMatcher, $this->paths); + $extractedEndpoints = $groupedEndpointsInstance->get(); + $userDefinedEndpoints = Camel::loadUserDefinedEndpoints(Camel::camelDir($this->paths)); + $groupedEndpoints = $this->mergeUserDefinedEndpoints($extractedEndpoints, $userDefinedEndpoints); + + // Output stage + $configFileOrder = $this->docConfig->get('groups.order', []); + $groupedEndpoints = Camel::prepareGroupedEndpointsForOutput($groupedEndpoints, $configFileOrder); + + if (! count($userDefinedEndpoints)) { + // Update the example custom file if there were no custom endpoints + $this->writeExampleCustomEndpoint(); + } + + /** @var Writer $writer */ + $writer = app(Writer::class, ['config' => $this->docConfig, 'paths' => $this->paths]); + $writer->writeDocs($groupedEndpoints); + + // Retiring the automatic upgrade check, since the config file is no longer changing as frequently. + // $this->upgradeConfigFileIfNeeded(); + + $this->sayGoodbye(errored: $groupedEndpointsInstance->hasEncounteredErrors()); + } + + public function isForcing(): bool + { + return $this->forcing; + } + + public function shouldExtract(): bool + { + return $this->shouldExtract; + } + + public function getDocConfig(): DocumentationConfig + { + return $this->docConfig; + } + + /** + * Get access to output components for task-based output. + * + * @return Factory + */ + public function outputComponents() + { + return $this->components; + } + + public function bootstrap(): void + { + // The --verbose option is included with all Artisan commands. + Globals::$shouldBeVerbose = $this->option('verbose'); + + c::bootstrapOutput($this->output); + c::setCommand($this); + + $configName = $this->option('config'); + if (! config($configName)) { + throw new \InvalidArgumentException("The specified config (config/{$configName}.php) doesn't exist."); + } + + $this->paths = new PathConfig($configName); + if ($this->hasOption('scribe-dir') && ! empty($this->option('scribe-dir'))) { + $this->paths = new PathConfig( + $configName, + scribeDir: $this->option('scribe-dir') + ); + } + + $this->docConfig = new DocumentationConfig(config($this->paths->configName)); + + // Force root URL so it works in Postman collection + $baseUrl = $this->docConfig->get('base_url') ?? config('app.url'); + + try { + // Renamed from forceRootUrl in Laravel 11.43 or so + URL::useOrigin($baseUrl); + } catch (\BadMethodCallException) { + URL::forceRootUrl($baseUrl); + } + + $this->forcing = $this->option('force'); + $this->shouldExtract = ! $this->option('no-extraction'); + + if ($this->forcing && ! $this->shouldExtract) { + throw new \InvalidArgumentException("Can't use --force and --no-extraction together."); + } + + $this->runBootstrapHook(); + } + + protected function runBootstrapHook() + { + if (is_callable(Globals::$__bootstrap)) { + call_user_func_array(Globals::$__bootstrap, [$this]); + } + } + + protected function mergeUserDefinedEndpoints(array $groupedEndpoints, array $userDefinedEndpoints): array + { + foreach ($userDefinedEndpoints as $endpoint) { + $indexOfGroupWhereThisEndpointShouldBeAdded = Arr::first(array_keys($groupedEndpoints), function ($key) use ($groupedEndpoints, $endpoint) { + $group = $groupedEndpoints[$key]; + + return $group['name'] === ($endpoint['metadata']['groupName'] ?? $this->docConfig->get('groups.default', '')); + }); + + if ($indexOfGroupWhereThisEndpointShouldBeAdded !== null) { + $groupedEndpoints[$indexOfGroupWhereThisEndpointShouldBeAdded]['endpoints'][] = $endpoint; + } else { + $newGroup = [ + 'name' => $endpoint['metadata']['groupName'] ?? $this->docConfig->get('groups.default', ''), + 'description' => $endpoint['metadata']['groupDescription'] ?? null, + 'endpoints' => [$endpoint], + ]; + + $groupedEndpoints[$newGroup['name']] = $newGroup; + } + } + + return $groupedEndpoints; + } + + protected function writeExampleCustomEndpoint(): void + { + // We add an example to guide users in case they need to add a custom endpoint. + copy(__DIR__.'/../../resources/example_custom_endpoint.yaml', Camel::camelDir($this->paths).'/custom.0.yaml'); + } + + protected function upgradeConfigFileIfNeeded(): void + { + if ($this->option('no-upgrade-check')) { + return; + } + + $this->info('Checking for any pending upgrades to your config file...'); + + try { + $defaultConfig = require __DIR__.'/../../config/scribe.php'; + $ignore = ['example_languages', 'routes', 'description', 'auth.extra_info', 'intro_text', 'groups', 'database_connections_to_transact']; + $asList = ['strategies.*', 'examples.models_source']; + $differ = new ConfigDiffer(original: $this->docConfig->data, changed: $defaultConfig, ignorePaths: $ignore, asList: $asList); + + $diff = $differ->getDiff(); + // Remove items the user has set + $realDiff = []; + foreach ($diff as $key => $value) { + if (is_null($this->docConfig->get($key))) { + $realDiff[$key] = $value; + } + } + if (! empty($realDiff)) { + $this->newLine(); + + $this->warn("You're using an updated version of Scribe, which may have added new items to the config file."); + $this->info("Here's what is different:"); + foreach ($realDiff as $key => $item) { + $this->line("{$key} --now defaults to-> {$item}"); + } + + if (! $this->input->isInteractive()) { + $this->info(sprintf('To upgrade, see the full changelog at: https://github.com/knuckleswtf/scribe/blob/%s/CHANGELOG.md,', Scribe::VERSION)); + $this->info('And config reference at https://scribe.knuckles.wtf/laravel/reference/config'); + + return; + } + } + } catch (\Throwable $e) { + $this->warn('Check failed with error:'); + e::dumpExceptionIfVerbose($e); + $this->warn('This did not affect your docs. Please report this issue in the project repo: https://github.com/knuckleswtf/scribe'); + } + } + + protected function sayGoodbye(bool $errored = false): void + { + $message = 'All done.'; + $url = null; + + if ($this->docConfig->outputRoutedThroughLaravel()) { + if ($this->docConfig->get('laravel.add_routes')) { + $url = url($this->docConfig->get('laravel.docs_url')); + } + } elseif (Str::endsWith(base_path('public'), 'public') && Str::startsWith($this->docConfig->get('static.output_path'), 'public/')) { + $url = url(str_replace('public/', '', $this->docConfig->get('static.output_path'))); + } + + $this->newLine(); + if ($url) { + $this->components->twoColumnDetail($message, $url); + } else { + $this->components->info($message); + } + + if ($errored) { + $this->components->warn('Generated docs, but encountered some errors while processing routes.'); + $this->components->warn('Check the output above for details.'); + if (empty($_SERVER['SCRIBE_TESTS'])) { + exit(2); + } + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Commands/MakeStrategy.php b/vendor/knuckleswtf/scribe/src/Commands/MakeStrategy.php new file mode 100644 index 00000000..deaf4df7 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Commands/MakeStrategy.php @@ -0,0 +1,32 @@ +applyChanges = ! $this->option('dry-run'); + + $this->configName = $this->option('config'); + if (! ($oldConfig = config($this->configName))) { + $this->error("The specified config (config/{$this->configName}.php) doesn't exist."); + + return; + } + + if (array_key_exists('interactive', $oldConfig)) { + $this->error("This upgrade tool is for upgrading from Scribe v3 to v4, but it looks like you're coming from v2."); + $this->error('Please install v3 and follow its upgrade guide first.'); + + return; + } + + $isMajorUpgrade = array_key_exists('default_group', $oldConfig) || array_key_exists('faker_seed', $oldConfig); + + if ($isMajorUpgrade) { + $this->info('Welcome to the Scribe v3 to v4 upgrader.'); + } + $this->line('Checking for config file changes...'); + + $upgrader = Upgrader::ofConfigFile("config/{$this->configName}.php", __DIR__.'/../../config/scribe.php') + ->dontTouch( + 'routes', + 'laravel.middleware', + 'postman.overrides', + 'openapi.overrides', + 'example_languages', + 'database_connections_to_transact', + 'strategies', + 'examples.models_source', + 'external.html_attributes' + ) + ->move('default_group', 'groups.default') + ->move('faker_seed', 'examples.faker_seed'); + + if (! $isMajorUpgrade) { + $upgrader->dontTouch('groups'); + } + + $changes = $upgrader->dryRun(); + if (empty($changes)) { + $this->info('✔ No config file changes needed.'); + } else { + $this->info('The following changes will be made to your config file:'); + $this->newLine(); + foreach ($changes as $change) { + $this->line($change['description']); + } + + if ($this->applyChanges) { + $upgrader->upgrade(); + $this->info("✔ Upgraded your config file. Your old config is backed up at config/{$this->configName}.php.bak."); + } + } + $this->newLine(); + + if (! $isMajorUpgrade) { + $this->info('✔ Done.'); + $this->info(sprintf('See the full changelog at https://github.com/knuckleswtf/scribe/blob/%s/CHANGELOG.md', Scribe::VERSION)); + + return; + } + + $this->finishV4Upgrade(); + } + + protected function finishV4Upgrade(): void + { + $this->migrateToConfigFileSort(); + + if ($this->applyChanges) { + if ($this->confirm('Do you have any custom strategies?')) { + $this->line('1. Add a new property public ?ExtractedEndpointData $endpointData;.'); + $this->line('2. Replace the array $routeRules parameter in __invoke() with array $routeRules = [] .'); + } + $this->newLine(); + $this->info('✔ Done.'); + } + + $this->line('See the release announcement at http://scribe.knuckles.wtf/blog/laravel-v4 for the full upgrade guide!'); + } + + /** + * In v3, you sorted endpoints by reordering them in the group file, and groups by renaming the group files alphabetically + * (or by using `beforeGroup`/`afterGroup` for custom endpoints). + * v4 replaces them with the config item `groups.order`. + */ + protected function migrateToConfigFileSort() + { + $this->info('In v3, you sorted endpoints/groups by editing/renaming the generated YAML files (or `beforeGroup`/`afterGroup` for custom endpoints).'); + $this->info("We'll automatically import your current sorting into the config item `groups.order`."); + + $defaultGroup = config($this->configName.'.default_group'); + $pathConfig = new PathConfig($this->configName); + $extractedEndpoints = GroupedEndpointsFactory::fromCamelDir($pathConfig)->get(); + + $order = array_map(function (array $group) { + return array_map(function (array $endpoint) { + return $endpoint['metadata']['title'] ?: ($endpoint['httpMethods'][0].' /'.$endpoint['uri']); + }, $group['endpoints']); + }, $extractedEndpoints); + $groupsOrder = array_keys($order); + $keyIndices = array_flip($groupsOrder); + + $userDefinedEndpoints = Camel::loadUserDefinedEndpoints(Camel::camelDir($pathConfig)); + + if ($userDefinedEndpoints) { + foreach ($userDefinedEndpoints as $endpoint) { + $groupName = $endpoint['metadata']['groupName'] ?? $defaultGroup; + $endpointTitle = $endpoint['metadata']['title'] ?? ($endpoint['httpMethods'][0].' /'.$endpoint['uri']); + + if (! isset($order[$groupName])) { + // This is a new group; place it at the right spot. + if ($nextGroup = $endpoint['metadata']['beforeGroup'] ?? null) { + $index = $keyIndices[$nextGroup]; + array_splice($groupsOrder, $index, 0, [$groupName]); + } elseif ($previousGroup = $endpoint['metadata']['afterGroup'] ?? null) { + $index = $keyIndices[$previousGroup]; + array_splice($groupsOrder, $index + 1, 0, [$groupName]); + } else { + $groupsOrder[] = $groupName; + } + $order[$groupName] = [$endpointTitle]; + } else { + // Existing group, add endpoint + $order[$groupName] = [...$order[$groupName], $endpointTitle]; + } + } + } + + // Re-add them, so it's sorted in the right order + $newOrder = []; + foreach ($groupsOrder as $groupName) { + $newOrder[$groupName] = $order[$groupName]; + } + + $output = VarExporter::export($newOrder); + if ($this->applyChanges) { + $configFile = "config/{$this->configName}.php"; + $output = str_replace("\n", "\n ", $output); + $newContents = str_replace( + "'order' => [],", + "'order' => {$output},", + file_get_contents($configFile) + ); + file_put_contents($configFile, $newContents); + $this->info('✔ Updated `groups.order`.'); + } else { + $this->line('- `groups.order` will be set to:'); + $this->info($output); + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Commands/stubs/strategy.stub b/vendor/knuckleswtf/scribe/src/Commands/stubs/strategy.stub new file mode 100644 index 00000000..7a25513a --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Commands/stubs/strategy.stub @@ -0,0 +1,33 @@ + $value) { + unset($strategiesList[$key]); + } + + return $strategiesList; +} + +/** + * Add/replace a strategy and its settings in a list of strategies. + * This method generates a tuple containing [strategyName, settingsArray], + * and adds or replaces the strategy entry in the list. + * + * @param array $configurationTuple Tuple of [strategyName, settingsArray]. + * By default, all strategies support the "only" and "except" setting to apply them to specific endpoints. + * You can easily create the tuple by calling Strategy::wrapWithSettings(only: [], except: []). + */ +function configureStrategy(array $strategiesList, array $configurationTuple): array +{ + $strategyFound = false; + $strategiesList = array_map(function ($strategy) use ($configurationTuple, &$strategyFound) { + $strategyName = is_string($strategy) ? $strategy : $strategy[0]; + if ($strategyName === $configurationTuple[0]) { + $strategyFound = true; + + return $configurationTuple; + } + + return $strategy; + }, $strategiesList); + + // If strategy wasn't in there, add it. + if (! $strategyFound) { + $strategiesList = array_merge($strategiesList, [$configurationTuple]); + } + + return $strategiesList; +} diff --git a/vendor/knuckleswtf/scribe/src/Exceptions/CouldntFindFactory.php b/vendor/knuckleswtf/scribe/src/Exceptions/CouldntFindFactory.php new file mode 100644 index 00000000..b25b8fbd --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Exceptions/CouldntFindFactory.php @@ -0,0 +1,11 @@ +getMessage()}", + 0, + $innerException + ); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Exceptions/CouldntStartDatabaseTransaction.php b/vendor/knuckleswtf/scribe/src/Exceptions/CouldntStartDatabaseTransaction.php new file mode 100644 index 00000000..b76ff345 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Exceptions/CouldntStartDatabaseTransaction.php @@ -0,0 +1,16 @@ +getMessage()}", + 0, + $innerException + ); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Exceptions/ScribeException.php b/vendor/knuckleswtf/scribe/src/Exceptions/ScribeException.php new file mode 100644 index 00000000..127f8e21 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Exceptions/ScribeException.php @@ -0,0 +1,9 @@ +markdownOutputPath = $paths->intermediateOutputPath(); // .scribe by default + // If no config is injected, pull from global. Makes testing easier. + $this->config = $config ?: new DocumentationConfig(config($paths->configName)); + $this->baseUrl = $this->config->get('base_url') ?? config('app.url'); + $this->preserveUserChanges = $preserveUserChanges; + + $this->fileHashesTrackingFile = $this->markdownOutputPath.'/.filehashes'; + $this->lastKnownFileContentHashes = []; + } + + public function writeMarkdownFiles(): void + { + c::task( + 'Extracting intro and auth Markdown files to: '.$this->markdownOutputPath, + function () { + if (! is_dir($this->markdownOutputPath)) { + mkdir($this->markdownOutputPath, 0o777, true); + } + + $this->fetchFileHashesFromTrackingFile(); + + $this->writeIntroMarkdownFile(); + $this->writeAuthMarkdownFile(); + + $this->writeContentsTrackingFile(); + + return true; + } + ); + } + + public function writeIntroMarkdownFile(): void + { + $introMarkdownFile = $this->markdownOutputPath.'/intro.md'; + if ($this->hasFileBeenModified($introMarkdownFile)) { + if ($this->preserveUserChanges) { + c::warn("Skipping modified file {$introMarkdownFile}"); + + return; + } + + c::warn("Discarding manual changes for file {$introMarkdownFile} because you specified --force"); + } + + $introMarkdown = view('scribe::markdown.intro') + ->with('description', $this->config->get('description', '')) + ->with('introText', $this->config->get('intro_text', '')) + ->with('baseUrl', $this->baseUrl)->render(); + $this->writeMarkdownFileAndRecordHash($introMarkdownFile, $introMarkdown); + } + + public function writeAuthMarkdownFile(): void + { + $authMarkdownFile = $this->markdownOutputPath.'/auth.md'; + if ($this->hasFileBeenModified($authMarkdownFile)) { + if ($this->preserveUserChanges) { + c::warn("Skipping modified file {$authMarkdownFile}"); + + return; + } + + c::warn("Discarding manual changes for file {$authMarkdownFile} because you specified --force"); + } + + $isAuthed = $this->config->get('auth.enabled', false); + $authDescription = ''; + $extraInfo = ''; + + if ($isAuthed) { + $strategy = $this->config->get('auth.in'); + $parameterName = $this->config->get('auth.name'); + $authDescription = u::trans( + "scribe::auth.instruction.{$strategy}", + [ + 'parameterName' => $parameterName, + 'placeholder' => $this->config->get('auth.placeholder') ?: 'your-token'] + ); + $authDescription .= "\n\n".u::trans('scribe::auth.details'); + $extraInfo = $this->config->get('auth.extra_info', ''); + } + + $authMarkdown = view('scribe::markdown.auth', [ + 'isAuthed' => $isAuthed, + 'authDescription' => $authDescription, + 'extraAuthInfo' => $extraInfo, + ])->render(); + $this->writeMarkdownFileAndRecordHash($authMarkdownFile, $authMarkdown); + } + + protected function writeMarkdownFileAndRecordHash(string $filePath, string $markdown): void + { + file_put_contents($filePath, $markdown); + $this->lastKnownFileContentHashes[$filePath] = hash_file('md5', $filePath); + } + + protected function writeContentsTrackingFile(): void + { + $content = "# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.\n"; + $content .= "# Scribe uses this file to know when you change something manually in your docs.\n"; + $content .= collect($this->lastKnownFileContentHashes) + ->map(fn ($hash, $filePath) => "{$filePath}={$hash}")->implode("\n"); + file_put_contents($this->fileHashesTrackingFile, $content); + } + + protected function hasFileBeenModified(string $filePath): bool + { + if (! file_exists($filePath)) { + return false; + } + + $oldFileHash = $this->lastKnownFileContentHashes[$filePath] ?? null; + + if ($oldFileHash) { + $currentFileHash = hash_file('md5', $filePath); + + // No danger of a timing attack, so no need for hash_equals() comparison + return $currentFileHash !== $oldFileHash; + } + + return false; + } + + protected function fetchFileHashesFromTrackingFile() + { + if (file_exists($this->fileHashesTrackingFile)) { + $lastKnownFileHashes = explode("\n", mb_trim(file_get_contents($this->fileHashesTrackingFile))); + // First two lines are comments + array_shift($lastKnownFileHashes); + array_shift($lastKnownFileHashes); + $this->lastKnownFileContentHashes = collect($lastKnownFileHashes) + ->mapWithKeys(function ($line) { + [$filePath, $hash] = explode('=', $line); + + return [$filePath => $hash]; + })->toArray(); + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/DatabaseTransactionHelpers.php b/vendor/knuckleswtf/scribe/src/Extracting/DatabaseTransactionHelpers.php new file mode 100644 index 00000000..b10fb74b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/DatabaseTransactionHelpers.php @@ -0,0 +1,69 @@ +getConfig()->get('database_connections_to_transact', []); + } + + private function startDbTransaction() + { + foreach ($this->connectionsToTransact() as $connection) { + $database ??= app('db'); + + $driver = $database->connection($connection); + + if (self::driverSupportsTransactions($driver)) { + try { + $driver->beginTransaction(); + } catch (\Throwable $e) { + throw CouldntStartDatabaseTransaction::forConnection($connection, $e); + } + } else { + $driverClassName = get_class($driver); + + throw DatabaseTransactionsNotSupported::create($connection, $driverClassName); + } + } + } + + private function endDbTransaction() + { + foreach ($this->connectionsToTransact() as $connection) { + $database ??= app('db'); + + $driver = $database->connection($connection); + + try { + $driver->rollback(); + } catch (\Exception $e) { + // Any error handling should have been done on the startDbTransaction() side + } + } + } + + private static function driverSupportsTransactions($driver): bool + { + $methods = ['beginTransaction', 'rollback']; + + foreach ($methods as $method) { + if (! method_exists($driver, $method)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Extractor.php b/vendor/knuckleswtf/scribe/src/Extracting/Extractor.php new file mode 100644 index 00000000..e18ed741 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Extractor.php @@ -0,0 +1,520 @@ +config = $config ?: new DocumentationConfig(config('scribe')); + } + + /** + * External interface that allows users to know what route is currently being processed. + */ + public static function getRouteBeingProcessed(): ?Route + { + return self::$routeBeingProcessed; + } + + /** + * @param array $routeRules Rules to apply when generating documentation for this route. Deprecated. Use strategy config instead. + */ + public function processRoute(Route $route, array $routeRules = []): ExtractedEndpointData + { + self::$routeBeingProcessed = $route; + + $endpointData = ExtractedEndpointData::fromRoute($route); + + $inheritedDocsOverrides = []; + if ($endpointData->controller->hasMethod('inheritedDocsOverrides')) { + $inheritedDocsOverrides = call_user_func([$endpointData->controller->getName(), 'inheritedDocsOverrides']); + $inheritedDocsOverrides = $inheritedDocsOverrides[$endpointData->method->getName()] ?? []; + } + + $this->fetchMetadata($endpointData, $routeRules); + $this->mergeInheritedMethodsData('metadata', $endpointData, $inheritedDocsOverrides); + + $this->fetchUrlParameters($endpointData, $routeRules); + $this->mergeInheritedMethodsData('urlParameters', $endpointData, $inheritedDocsOverrides); + $endpointData->cleanUrlParameters = self::cleanParams($endpointData->urlParameters); + + $this->addAuthField($endpointData); + + $this->fetchQueryParameters($endpointData, $routeRules); + $this->mergeInheritedMethodsData('queryParameters', $endpointData, $inheritedDocsOverrides); + $endpointData->cleanQueryParameters = self::cleanParams($endpointData->queryParameters); + + $this->fetchRequestHeaders($endpointData, $routeRules); + $this->mergeInheritedMethodsData('headers', $endpointData, $inheritedDocsOverrides); + + $this->fetchBodyParameters($endpointData, $routeRules); + $endpointData->cleanBodyParameters = self::cleanParams($endpointData->bodyParameters); + $this->mergeInheritedMethodsData('bodyParameters', $endpointData, $inheritedDocsOverrides); + + if (count($endpointData->cleanBodyParameters) && ! isset($endpointData->headers['Content-Type'])) { + // Set content type if the user forgot to set it + $endpointData->headers['Content-Type'] = 'application/json'; + } + // We need to do all this so response calls can work correctly, + // even though they're only needed for output + // Note that this + [$files, $regularParameters] = OutputEndpointData::splitIntoFileAndRegularParameters($endpointData->cleanBodyParameters); + if (count($files)) { + $endpointData->headers['Content-Type'] = 'multipart/form-data'; + } + $endpointData->fileParameters = $files; + $endpointData->cleanBodyParameters = $regularParameters; + + $this->fetchResponses($endpointData, $routeRules); + $this->mergeInheritedMethodsData('responses', $endpointData, $inheritedDocsOverrides); + + $this->fetchResponseFields($endpointData, $routeRules); + $this->mergeInheritedMethodsData('responseFields', $endpointData, $inheritedDocsOverrides); + + self::$routeBeingProcessed = null; + + return $endpointData; + } + + public function shouldSkipRoute($route, array $routesToExclude, array $routesToInclude): bool + { + if (! empty($routesToExclude) && RoutePatternMatcher::matches($route, $routesToExclude)) { + return true; + } + if (! empty($routesToInclude) && ! RoutePatternMatcher::matches($route, $routesToInclude)) { + return true; + } + + return false; + } + + /** + * This method prepares and simplifies request parameters for use in example requests and response calls. + * It takes in an array with rich details about a parameter eg + * ['age' => new Parameter([ + * 'description' => 'The age', + * 'example' => 12, + * 'required' => false, + * ])] + * And transforms them into key-example pairs : ['age' => 12] + * It also filters out parameters which have null values and have 'required' as false. + * It converts all file params that have string examples to actual files (instances of UploadedFile). + * + * @param array $parameters + */ + public static function cleanParams(array $parameters): array + { + $cleanParameters = []; + + /** + * @var string $paramName + * @var Parameter $details + */ + foreach ($parameters as $paramName => $details) { + // Remove params which have no intentional examples and are optional. + if (! $details->exampleWasSpecified) { + if (is_null($details->example) && $details->required === false) { + continue; + } + } + + if ($details->type === 'file') { + if (is_string($details->example)) { + $details->example = self::convertStringValueToUploadedFileInstance($details->example); + } elseif (is_null($details->example)) { + $details->example = (new self)->generateDummyValue($details->type); + } + } + + if (Str::startsWith($paramName, '[].')) { // Entire body is an array + if (empty($parameters['[]'])) { // Make sure there's a parent + $cleanParameters['[]'] = [[], []]; + $parameters['[]'] = new Parameter([ + 'name' => '[]', + 'type' => 'object[]', + 'description' => '', + 'required' => true, + 'example' => [$paramName => $details->example], + ]); + } + } + + if (Str::contains($paramName, '.')) { // Object field (or array of objects) + self::setObject($cleanParameters, $paramName, $details->example, $parameters, $details->required); + } else { + $cleanParameters[$paramName] = $details->example; + } + } + + // Finally, if the body is an array, flatten it. + if (isset($cleanParameters['[]'])) { + $cleanParameters = $cleanParameters['[]']; + } + + return $cleanParameters; + } + + public static function setObject(array &$results, string $path, $value, array $source, bool $isRequired) + { + $parts = explode('.', $path); + + array_pop($parts); // Get rid of the field name + + $baseName = implode('.', $parts); + // For array fields, the type should be indicated in the source object by now; + // eg test.items[] would actually be described as name: test.items, type: object[] + // So we get rid of that ending [] + // For other fields (eg test.items[].name), it remains as-is + $baseNameInOriginalParams = $baseName; + while (Str::endsWith($baseNameInOriginalParams, '[]')) { + $baseNameInOriginalParams = mb_substr($baseNameInOriginalParams, 0, -2); + } + // When the body is an array, param names will be "[].paramname", + // so $baseNameInOriginalParams here will be empty + if (Str::startsWith($path, '[].')) { + $baseNameInOriginalParams = '[]'; + } + + if (Arr::has($source, $baseNameInOriginalParams)) { + /** @var Parameter $parentData */ + $parentData = Arr::get($source, $baseNameInOriginalParams); + // Path we use for data_set + $dotPath = str_replace('[]', '.0', $path); + + // Don't overwrite parent if there's already data there + + if ($parentData->type === 'object') { + $parentPath = explode('.', $dotPath); + $property = array_pop($parentPath); + $parentPath = implode('.', $parentPath); + + $exampleFromParent = Arr::get($results, $dotPath) ?? $parentData->example[$property] ?? null; + if (empty($exampleFromParent)) { + Arr::set($results, $dotPath, $value); + } + } elseif ($parentData->type === 'object[]') { + // When the body is an array, param names will be "[].paramname", so dot paths won't work correctly with "[]" + if (Str::startsWith($path, '[].')) { + $valueDotPath = mb_substr($dotPath, 3); // Remove initial '.0.' + if (isset($results['[]'][0]) && ! Arr::has($results['[]'][0], $valueDotPath)) { + Arr::set($results['[]'][0], $valueDotPath, $value); + } + } else { + $parentPath = explode('.', $dotPath); + $index = (int) array_pop($parentPath); + $parentPath = implode('.', $parentPath); + + $exampleFromParent = Arr::get($results, $dotPath) ?? $parentData->example[$index] ?? null; + if (empty($exampleFromParent)) { + Arr::set($results, $dotPath, $value); + } + } + } + } + } + + public function addAuthField(ExtractedEndpointData $endpointData): void + { + $isApiAuthed = $this->config->get('auth.enabled', false); + if (! $isApiAuthed || ! $endpointData->metadata->authenticated) { + return; + } + + $strategy = $this->config->get('auth.in'); + $parameterName = $this->config->get('auth.name'); + + $faker = Factory::create(); + if ($seed = $this->config->get('examples.faker_seed')) { + $faker->seed($seed); + } + $token = $faker->shuffleString('abcdefghkvaZVDPE1864563'); + $valueToUse = $this->config->get('auth.use_value'); + $valueToDisplay = $this->config->get('auth.placeholder'); + + switch ($strategy) { + case 'query': + case 'query_or_body': + $endpointData->auth = ['queryParameters', $parameterName, $valueToUse ?: $token]; + $endpointData->queryParameters[$parameterName] = new Parameter([ + 'name' => $parameterName, + 'type' => 'string', + 'example' => $valueToDisplay ?: $token, + 'description' => 'Authentication key.', + 'required' => true, + ]); + + return; + + case 'body': + $endpointData->auth = ['bodyParameters', $parameterName, $valueToUse ?: $token]; + $endpointData->bodyParameters[$parameterName] = new Parameter([ + 'name' => $parameterName, + 'type' => 'string', + 'example' => $valueToDisplay ?: $token, + 'description' => 'Authentication key.', + 'required' => true, + ]); + + return; + + case 'bearer': + $endpointData->auth = ['headers', 'Authorization', 'Bearer '.($valueToUse ?: $token)]; + $endpointData->headers['Authorization'] = 'Bearer '.($valueToDisplay ?: $token); + + return; + + case 'basic': + $endpointData->auth = ['headers', 'Authorization', 'Basic '.($valueToUse ?: base64_encode($token))]; + $endpointData->headers['Authorization'] = 'Basic '.($valueToDisplay ?: base64_encode($token)); + + return; + + case 'header': + $endpointData->auth = ['headers', $parameterName, $valueToUse ?: $token]; + $endpointData->headers[$parameterName] = $valueToDisplay ?: $token; + + return; + } + } + + public static function transformOldRouteRulesIntoNewSettings($stage, $rulesToApply, $strategyName, $strategySettings = []) + { + if ($stage === 'responses' && Str::is('*ResponseCalls*', $strategyName) && ! empty($rulesToApply['response_calls'])) { + // Transform `methods` to `only` + $strategySettings = []; + + if (isset($rulesToApply['response_calls']['methods'])) { + if (empty($rulesToApply['response_calls']['methods'])) { + // This means all routes are forbidden + $strategySettings['except'] = ['*']; + } else { + $strategySettings['only'] = array_map( + fn ($method) => "{$method} *", + $rulesToApply['response_calls']['methods'] + ); + } + } + + // Copy all other keys over as is + foreach (array_keys($rulesToApply['response_calls']) as $key) { + if ($key === 'methods') { + continue; + } + $strategySettings[$key] = $rulesToApply['response_calls'][$key]; + } + } + + return $strategySettings; + } + + protected function fetchMetadata(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $endpointData->metadata = new Metadata([ + 'groupName' => $this->config->get('groups.default', ''), + 'authenticated' => $this->config->get('auth.default', false), + ]); + + $this->iterateThroughStrategies('metadata', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + foreach ($results as $key => $item) { + $hadPreviousValue = ! is_null($endpointData->metadata->{$key}); + $noNewValueSet = is_null($item) || $item === ''; + if ($hadPreviousValue && $noNewValueSet) { + continue; + } + $endpointData->metadata->{$key} = $item; + } + }); + } + + protected function fetchUrlParameters(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $this->iterateThroughStrategies('urlParameters', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + foreach ($results as $key => $item) { + if (empty($item['name'])) { + $item['name'] = $key; + } + $endpointData->urlParameters[$key] = Parameter::create($item, $endpointData->urlParameters[$key] ?? []); + } + }); + } + + protected function fetchQueryParameters(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $this->iterateThroughStrategies('queryParameters', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + foreach ($results as $key => $item) { + if (empty($item['name'])) { + $item['name'] = $key; + } + $endpointData->queryParameters[$key] = Parameter::create($item, $endpointData->queryParameters[$key] ?? []); + } + }); + } + + protected function fetchBodyParameters(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $this->iterateThroughStrategies('bodyParameters', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + foreach ($results as $key => $item) { + if (empty($item['name'])) { + $item['name'] = $key; + } + $endpointData->bodyParameters[$key] = Parameter::create($item, $endpointData->bodyParameters[$key] ?? []); + } + }); + } + + protected function fetchResponses(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $this->iterateThroughStrategies('responses', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + // Responses from different strategies are all added, not overwritten + $endpointData->responses->concat($results); + }); + // Ensure 200 responses come first + $endpointData->responses = new ResponseCollection($endpointData->responses->sortBy('status')->values()); + } + + protected function fetchResponseFields(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $this->iterateThroughStrategies('responseFields', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + foreach ($results as $key => $item) { + $endpointData->responseFields[$key] = Parameter::create($item, $endpointData->responseFields[$key] ?? []); + } + }); + } + + protected function fetchRequestHeaders(ExtractedEndpointData $endpointData, array $rulesToApply): void + { + $this->iterateThroughStrategies('headers', $endpointData, $rulesToApply, function ($results) use ($endpointData) { + foreach ($results as $key => $item) { + if ($item) { + $endpointData->headers[$key] = $item; + } + } + }); + } + + /** + * Iterate through all defined strategies for this stage. + * A strategy may return an array of attributes + * to be added to that stage data, or it may modify the stage data directly. + * + * @param array $rulesToApply Deprecated. Use strategy config instead. + * @param callable $handler function to run after each strategy returns its results (an array) + */ + protected function iterateThroughStrategies( + string $stage, + ExtractedEndpointData $endpointData, + array $rulesToApply, + callable $handler, + ): void { + $strategies = $this->config->get("strategies.{$stage}", []); + + foreach ($strategies as $strategyClassOrTuple) { + if (is_array($strategyClassOrTuple)) { + [$strategyClass, &$settings] = $strategyClassOrTuple; + if ($strategyClass === 'override') { + c::warn("The 'override' strategy was renamed to 'static_data', and will stop working in the future. Please replace 'override' in your config file with 'static_data'."); + $strategyClass = 'static_data'; + } + if ($strategyClass === 'static_data') { + $strategyClass = StaticData::class; + // Static data can be short: ['static_data', ['key' => 'value']], + // or extended ['static_data', ['data' => ['key' => 'value'], 'only' => ['GET *'], 'except' => []]], + $settingsFormat = array_key_exists('data', $settings) ? 'extended' : 'short'; + $settings = match ($settingsFormat) { + 'extended' => $settings, + 'short' => ['data' => $settings], + }; + } + } else { + $strategyClass = $strategyClassOrTuple; + $settings = []; + } + + $routesToExclude = Arr::wrap($settings['except'] ?? []); + $routesToInclude = Arr::wrap($settings['only'] ?? []); + + if ($this->shouldSkipRoute($endpointData->route, $routesToExclude, $routesToInclude)) { + continue; + } + + $strategy = new $strategyClass($this->config); + $results = $strategy($endpointData, $settings); + if (is_array($results)) { + $handler($results); + } + } + } + + protected static function convertStringValueToUploadedFileInstance(string $filePath): UploadedFile + { + $fileName = basename($filePath); + + return new File($fileName, fopen($filePath, 'r')); + } + + protected function mergeInheritedMethodsData(string $stage, ExtractedEndpointData $endpointData, array $inheritedDocsOverrides = []): void + { + $overrides = $inheritedDocsOverrides[$stage] ?? []; + $normalizeParamData = fn ($data, $key) => array_merge($data, ['name' => $key]); + if (is_array($overrides)) { + foreach ($overrides as $key => $item) { + switch ($stage) { + case 'responses': + $endpointData->responses->concat($overrides); + $endpointData->responses->sortBy('status'); + + break; + + case 'urlParameters': + case 'bodyParameters': + case 'queryParameters': + $endpointData->{$stage}[$key] = Parameter::make($normalizeParamData($item, $key)); + + break; + + case 'responseFields': + $endpointData->{$stage}[$key] = ResponseField::make($normalizeParamData($item, $key)); + + break; + + default: + $endpointData->{$stage}[$key] = $item; + } + } + } elseif (is_callable($overrides)) { + $results = $overrides($endpointData); + + $endpointData->{$stage} = match ($stage) { + 'responses' => ResponseCollection::make($results), + 'urlParameters', 'bodyParameters', 'queryParameters' => collect($results)->map(fn ($param, $name) => Parameter::make($normalizeParamData($param, $name)))->all(), + 'responseFields' => collect($results)->map(fn ($field, $name) => ResponseField::make($normalizeParamData($field, $name)))->all(), + default => $results, + }; + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/FindsFormRequestForMethod.php b/vendor/knuckleswtf/scribe/src/Extracting/FindsFormRequestForMethod.php new file mode 100644 index 00000000..ac142128 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/FindsFormRequestForMethod.php @@ -0,0 +1,36 @@ +getParameters() as $argument) { + $argType = $argument->getType(); + if ($argType === null || $argType instanceof \ReflectionUnionType) { + continue; + } + + $argumentClassName = $argType->getName(); + + if (! class_exists($argumentClassName)) { + continue; + } + + try { + $argumentClass = new \ReflectionClass($argumentClassName); + } catch (\ReflectionException $e) { + continue; + } + + if ($argumentClass->isSubclassOf(FormRequest::class)) { + return $argumentClass; + } + } + + return null; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/InstantiatesExampleModels.php b/vendor/knuckleswtf/scribe/src/Extracting/InstantiatesExampleModels.php new file mode 100644 index 00000000..a934fa28 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/InstantiatesExampleModels.php @@ -0,0 +1,109 @@ +getParameters()); + $parameterType = $parameter->hasType() ? $parameter->getType() : null; + if ($parameterType instanceof \ReflectionNamedType + && ! $parameterType->isBuiltin() && class_exists($parameterType->getName())) { + // Ladies and gentlemen, we have a type! + $type = $parameterType->getName(); + } + } + if ($type === null) { + throw new \Exception("Couldn't detect a transformer model from your doc block. Did you remember to specify a model using @transformerModel?"); + } + + $configuredStrategies = $this->config->get('examples.models_source', ['factoryCreate', 'factoryMake', 'databaseFirst']); + + $strategies = [ + 'factoryCreate' => fn () => $this->getExampleModelFromFactoryCreate($type, $factoryStates, $relations, $withCount), + 'factoryCreateQuietly' => fn () => $this->getExampleModelFromFactoryCreate($type, $factoryStates, $relations, $withCount, true), + 'factoryMake' => fn () => $this->getExampleModelFromFactoryMake($type, $factoryStates, $relations), + 'databaseFirst' => fn () => $this->getExampleModelFromDatabaseFirst($type, $relations), + ]; + + foreach ($configuredStrategies as $strategyName) { + try { + $model = $strategies[$strategyName](); + if ($model) { + return $model; + } + } catch (\Throwable $e) { + c::warn("Couldn't get example model for {$type} via {$strategyName}."); + e::dumpExceptionIfVerbose($e); + } + } + + return new $type; + } + + /** + * @param class-string $type + * @param string[] $factoryStates + * @param string[] $relations + * @param string[] $withCount + * @return null|Model + */ + protected function getExampleModelFromFactoryCreate(string $type, array $factoryStates = [], array $relations = [], array $withCount = [], bool $quietly = false) + { + // Since $relations and $withCount refer to the same underlying relationships in the model, + // combining them ensures that all required relationships are initialized when passed to the factory. + $allRelations = array_unique(array_merge($relations, $withCount)); + + $factory = Utils::getModelFactory($type, $factoryStates, $allRelations); + + $factory = $quietly ? Model::withoutEvents(fn () => $factory->create()) : $factory->create(); + + return $factory->refresh()->load($relations)->loadCount($withCount); + } + + /** + * @param class-string $type + * @param string[] $factoryStates + * @return null|Model + */ + protected function getExampleModelFromFactoryMake(string $type, array $factoryStates = [], array $relations = []) + { + $factory = Utils::getModelFactory($type, $factoryStates, $relations); + + return $factory->make(); + } + + /** + * @param class-string $type + * @param string[] $relations + * @return null|Model + */ + protected function getExampleModelFromDatabaseFirst(string $type, array $relations = []) + { + return $type::with($relations)->first(); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/MethodAstParser.php b/vendor/knuckleswtf/scribe/src/Extracting/MethodAstParser.php new file mode 100644 index 00000000..11d0b5ae --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/MethodAstParser.php @@ -0,0 +1,104 @@ +name; + $fileName = $method->getFileName(); + + $methodAst = self::getCachedMethodAst($fileName, $methodName); + + if ($methodAst) { + return $methodAst; + } + + $classAst = self::getClassAst($fileName); + $methodAst = self::findMethodInClassAst($classAst, $methodName); + self::cacheMethodAst($fileName, $methodName, $methodAst); + + return $methodAst; + } + + /** + * @return null|Stmt[] + */ + protected static function parseClassSourceCode(string $sourceCode): ?array + { + $parser = (new ParserFactory)->createForHostVersion(); + + try { + $ast = $parser->parse($sourceCode); + } catch (\Throwable $error) { + throw new \Exception("Parse error: {$error->getMessage()}"); + } + + $traverser = new NodeTraverser(new NameResolver(options: ['replaceNodes' => false])); + + try { + $traverser->traverse($ast); + } catch (\Throwable $error) { + throw new \Exception("Traverse error: {$error->getMessage()}"); + } + + return $ast; + } + + /** + * @param Stmt[] $ast + * @return null|Node + */ + protected static function findMethodInClassAst(array $ast, string $methodName) + { + $nodeFinder = new NodeFinder; + + return $nodeFinder->findFirst($ast, function (Node $node) use ($methodName) { + // Todo handle closures + return $node instanceof Stmt\ClassMethod + && $node->name->toString() === $methodName; + }); + } + + protected static function getCachedMethodAst(string $fileName, string $methodName) + { + $key = self::getAstCacheId($fileName, $methodName); + + return self::$methodAsts[$key] ?? null; + } + + protected static function cacheMethodAst(string $fileName, string $methodName, Node $methodAst) + { + $key = self::getAstCacheId($fileName, $methodName); + self::$methodAsts[$key] = $methodAst; + } + + private static function getAstCacheId(string $fileName, string $methodName): string + { + return $fileName.'///'.$methodName; + } + + private static function getClassAst(string $fileName) + { + $classAst = self::$classAsts[$fileName] + ?? self::parseClassSourceCode(file_get_contents($fileName)); + + return self::$classAsts[$fileName] = $classAst; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/ParamHelpers.php b/vendor/knuckleswtf/scribe/src/Extracting/ParamHelpers.php new file mode 100644 index 00000000..1ece6eeb --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/ParamHelpers.php @@ -0,0 +1,270 @@ + str_replace($base, 'boolean', $typeName), + 'int' => str_replace($base, 'integer', $typeName), + 'float', 'double' => str_replace($base, 'number', $typeName), + 'array' => (empty($value) || array_keys($value)[0] === 0) + ? static::normalizeTypeName(gettype($value[0] ?? '')).'[]' + : 'object', + default => $typeName + }; + } + + protected function getFakeFactoryByName(string $name): ?\Closure + { + $faker = $this->getFaker(); + + $name = mb_strtolower(array_reverse(explode('.', $name))[0]); + $normalizedName = match (true) { + Str::endsWith($name, ['email', 'email_address']) => 'email', + Str::endsWith($name, ['uuid']) => 'uuid', + Str::endsWith($name, ['url']) => 'url', + Str::endsWith($name, ['locale']) => 'locale', + Str::endsWith($name, ['timezone']) => 'timezone', + default => $name, + }; + + return match ($normalizedName) { + 'email' => fn () => $faker->safeEmail(), + 'password', 'pwd' => fn () => $faker->password(), + 'url' => fn () => $faker->url(), + 'description' => fn () => $faker->sentence(), + 'uuid' => fn () => $faker->uuid(), + 'locale' => fn () => $faker->locale(), + 'timezone' => fn () => $faker->timezone(), + default => null, + }; + } + + protected function getFaker(): Generator + { + $faker = Factory::create(); + if ($seed = $this->config->get('examples.faker_seed')) { + $faker->seed($seed); + } + + return $faker; + } + + protected function generateDummyValue(string $type, array $hints = []) + { + if (! empty($hints['enumValues'])) { + return Arr::random($hints['enumValues']); + } + + $fakeFactory = $this->getDummyValueGenerator($type, $hints); + + return $fakeFactory(); + } + + protected function getDummyValueGenerator(string $type, array $hints = []): \Closure + { + $baseType = $type; + $isListType = false; + + if (Str::endsWith($type, '[]')) { + $baseType = mb_strtolower(mb_substr($type, 0, mb_strlen($type) - 2)); + $isListType = true; + } + + $size = $hints['size'] ?? null; + if ($isListType) { + // Return a one-array item for a list by default. + return $size + ? fn () => [$this->generateDummyValue($baseType, range(0, min($size - 1, 5)))] + : fn () => [$this->generateDummyValue($baseType, $hints)]; + } + + if (($hints['name'] ?? false) && $baseType !== 'file') { + $fakeFactoryByName = $this->getFakeFactoryByName($hints['name']); + if ($fakeFactoryByName) { + return $fakeFactoryByName; + } + } + + $faker = $this->getFaker(); + $min = $hints['min'] ?? null; + $max = $hints['max'] ?? null; + // If max and min were provided, the override size. + $isExactSize = is_null($min) && is_null($max) && ! is_null($size); + + $fakeFactoriesByType = [ + 'integer' => function () use ($size, $isExactSize, $max, $faker, $min) { + if ($isExactSize) { + return $size; + } + + return $max ? $faker->numberBetween((int) $min, (int) $max) : $faker->numberBetween(1, 20); + }, + 'number' => function () use ($size, $isExactSize, $max, $faker, $min) { + if ($isExactSize) { + return $size; + } + + return $max ? $faker->numberBetween((int) $min, (int) $max) : $faker->randomFloat(); + }, + 'boolean' => fn () => $faker->boolean(), + 'string' => fn () => $size ? $faker->lexify(str_repeat('?', $size)) : $faker->word(), + 'object' => fn () => [], + 'file' => fn () => UploadedFile::fake()->create('test.jpg')->size($size ?: 10), + ]; + + return $fakeFactoriesByType[$baseType] ?? $fakeFactoriesByType['string']; + } + + protected function isSupportedTypeInDocBlocks(string $type): bool + { + $types = [ + 'integer', + 'int', + 'number', + 'float', + 'double', + 'boolean', + 'bool', + 'string', + 'object', + ]; + + return in_array(str_replace('[]', '', $type), $types); + } + + /** + * Cast a value to a specified type. + * + * @param mixed $value + * @return mixed + */ + protected function castToType($value, string $type) + { + if ($value === null) { + return null; + } + + if ($type === 'array') { + $type = 'string[]'; + } + + if (Str::endsWith($type, '[]')) { + $baseType = mb_strtolower(mb_substr($type, 0, mb_strlen($type) - 2)); + + return is_array($value) ? array_map(function ($v) use ($baseType) { + return $this->castToType($v, $baseType); + }, $value) : json_decode($value); + } + + if ($type === 'object') { + return is_array($value) ? $value : json_decode($value, true); + } + + $casts = [ + 'integer' => 'intval', + 'int' => 'intval', + 'float' => 'floatval', + 'number' => 'floatval', + 'double' => 'floatval', + 'boolean' => 'boolval', + 'bool' => 'boolval', + ]; + + // First, we handle booleans. We can't use a regular cast, + // because PHP considers string 'false' as true. + if ($value === 'false' && ($type === 'boolean' || $type === 'bool')) { + return false; + } + + if (isset($casts[$type])) { + return $casts[$type]($value); + } + + // Return the value unchanged if there's no applicable cast + return $value; + } + + /** + * Allows users to specify that we shouldn't generate an example for the parameter + * by writing 'No-example'. + * + * @return bool if true, don't generate an example for this + */ + protected function shouldExcludeExample(string $description): bool + { + return mb_strpos($description, ' No-example') !== false; + } + + /** + * Allows users to specify an example for the parameter by writing 'Example: the-example', + * to be used in example requests and response calls. + * + * @param string $type The type of the parameter. Used to cast the example provided, if any. + * @return array the description and included example + */ + protected function parseExampleFromParamDescription(string $description, string $type): array + { + $exampleWasSpecified = false; + $example = null; + $enumValues = []; + + if (preg_match('/(.*)\bExample:\s*([\s\S]+)\s*/s', $description, $content)) { + $exampleWasSpecified = true; + $description = mb_trim($content[1]); + + if ($content[2] === 'null') { + // If we intentionally put null as example we return null as example + $example = null; + } else { + // Examples are parsed as strings by default, we need to cast them properly + $example = $this->castToType($content[2], $type); + } + } + + if (preg_match('/(.*)\bEnum:\s*([\s\S]+)\s*/s', $description, $content)) { + $description = mb_trim($content[1]); + + $enumValues = array_map( + fn ($value) => $this->castToType(mb_trim($value), $type), + explode(',', mb_rtrim(mb_trim($content[2]), '.')) + ); + } + + return [$description, $example, $enumValues, $exampleWasSpecified]; + } + + private function getDummyDataGeneratorBetween(string $type, $min, $max = 90, ?string $fieldName = null): \Closure + { + $hints = [ + 'name' => $fieldName, + 'size' => $this->getFaker()->numberBetween($min, $max), + 'min' => $min, + 'max' => $max, + ]; + + return $this->getDummyValueGenerator($type, $hints); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/ParsesValidationRules.php b/vendor/knuckleswtf/scribe/src/Extracting/ParsesValidationRules.php new file mode 100644 index 00000000..98546306 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/ParsesValidationRules.php @@ -0,0 +1,992 @@ +normaliseRules($validationRulesByParameters); + + $parameters = []; + $rulesWhichDependOnType = ['between', 'max', 'min', 'size', 'gt', 'gte', 'lt', 'lte', 'before', 'after', 'before_or_equal', 'after_or_equal']; + foreach ($validationRulesByParameters as $parameter => $ruleset) { + $userSpecifiedParameterInfo = $customParameterData[$parameter] ?? []; + $stringRules = array_filter($ruleset, fn ($rule) => is_string($rule)); + $rulesAndArguments = array_map(fn ($rule) => $this->parseStringRuleIntoRuleAndArguments($rule), $stringRules); + + try { + $this->warnAboutMissingCustomParameterData($parameter, $customParameterData); + + // Make sure the user-specified description comes first (and add full stops where needed). + $description = $userSpecifiedParameterInfo['description'] ?? ''; + if (! empty($description) && ! Str::endsWith($description, '.')) { + $description .= '.'; + } + $parameterData = [ + 'name' => $parameter, + 'required' => false, + 'sometimes' => false, + 'type' => null, + 'example' => self::$MISSING_VALUE, + 'description' => $description, + 'nullable' => false, + ]; + + $closureRules = array_filter($ruleset, fn ($rule) => ($rule instanceof ClosureValidationRule || $rule instanceof Closure)); + foreach ($closureRules as $rule) { + $this->processClosureRule($rule, $parameterData); + } + + $enumValidationRules = array_filter($ruleset, fn ($rule) => $rule instanceof Enum); + foreach ($enumValidationRules as $rule) { + $this->processEnumValidationRule($rule, $parameterData); + } + + $ruleObjects = array_filter($ruleset, fn ($rule) => ($rule instanceof Rule || $rule instanceof ValidationRule)); + foreach ($ruleObjects as $rule) { + $this->processRuleObject($rule, $parameterData); + } + + // TODO support more rules + // Process in 3 passes + // 1. Rules which provide no info about type or example + // (required, required_*, same, different, nullable, exists, and others in the "utilities" group) + // 2. Rules which set a type. + // 3. Rules whose processing depends on the type. ('between', 'max', 'min', 'size', 'gt', 'gte', 'lt', 'lte') + // - Note: 'in' does not provide type info (does it?), but is enough to generate an example + + // First pass: process rules which provide no type or example info + $firstPassRuleNames = [ + 'sometimes', + 'required', + 'required_*', + 'accepted', + 'same', + 'different', + 'nullable', + ]; + + $firstPassRules = array_filter($rulesAndArguments, fn ($ruleAndArgs) => Str::is($firstPassRuleNames, $ruleAndArgs[0])); + foreach ($firstPassRules as $ruleAndArgs) { + $this->processRule($ruleAndArgs[0], $ruleAndArgs[1], $parameterData, $validationRulesByParameters); + } + + $secondPassRules = array_filter($rulesAndArguments, fn ($ruleAndArgs) => ! Str::is($firstPassRuleNames, $ruleAndArgs[0]) && ! in_array($ruleAndArgs[0], $rulesWhichDependOnType)); + foreach ($secondPassRules as $ruleAndArgs) { + $this->processRule($ruleAndArgs[0], $ruleAndArgs[1], $parameterData, $validationRulesByParameters); + } + + // The second pass should have set a type. If not, set a default type + if (is_null($parameterData['type'])) { + $parameterData['type'] = 'string'; + } + + if ($parameterData['required'] === true) { + $parameterData['nullable'] = false; + } + + // Now parse any "dependent" rules and set examples. At this point, we should know all field's types. + $thirdPassRules = array_filter($rulesAndArguments, fn ($ruleAndArgs) => in_array($ruleAndArgs[0], $rulesWhichDependOnType)); + foreach ($thirdPassRules as $ruleAndArgs) { + $this->processRule($ruleAndArgs[0], $ruleAndArgs[1], $parameterData, $validationRulesByParameters); + } + + // Make sure the user-specified example overwrites ours. + if (array_key_exists('example', $userSpecifiedParameterInfo)) { + if ($userSpecifiedParameterInfo['example'] !== null && $this->shouldCastUserExample()) { + // Examples in comments are strings, we need to cast them properly + $parameterData['example'] = $this->castToType($userSpecifiedParameterInfo['example'], $parameterData['type'] ?? 'string'); + } else { + $parameterData['example'] = $userSpecifiedParameterInfo['example']; + } + } + + // End descriptions with a full stop + if (! empty($parameterData['description']) && ! Str::endsWith($parameterData['description'], '.')) { + $parameterData['description'] .= '.'; + } + + $parameterData['description'] = mb_trim($parameterData['description']); + $parameters[$parameter] = $parameterData; + } catch (\Throwable $e) { + if ($e instanceof ScribeException) { + // This is a lower-level error that we've encountered and wrapped; + // Pass it on to the user. + throw $e; + } + + throw ProblemParsingValidationRules::forParam($parameter, $e); + } + } + + return $parameters; + } + + /** + * Laravel uses .* notation for arrays. This PR aims to normalise that into our "new syntax". + * + * 'years.*' with type 'integer' becomes 'years' with type 'integer[]' + * 'cars.*.age' with type 'string' becomes 'cars[].age' with type 'string' and 'cars' with type 'object[]' + * 'cars.*.things.*.*' with type 'string' becomes 'cars[].things' with type 'string[][]' and 'cars' with type + * 'object[]' + * + * Additionally, if the user declared a subfield but not the parent, we create a parameter for the parent. + * + * @param array[] $parametersFromValidationRules + */ + public function normaliseArrayAndObjectParameters(array $parametersFromValidationRules): array + { + // Convert any `array` types into concrete types like `object[]`, object, or `string[]` + $parameters = $this->convertGenericArrayType($parametersFromValidationRules); + + // Change cars.*.dogs.things.*.* with type X to cars.*.dogs.things with type X[][] + $parameters = $this->convertArraySubfields($parameters); + + // Add the fields `cars.*.dogs` and `cars` if they don't exist + $parameters = $this->addMissingParentFields($parameters); + + return $this->setExamples($parameters); + } + + public function convertGenericArrayType(array $parameters): array + { + $converted = []; + $allKeys = array_keys($parameters); + foreach (array_reverse($parameters) as $name => $details) { + if ($details['type'] === 'array') { + // This is a parent field, a generic array type. Scribe only supports concrete array types (T[]), + // so we convert this to the correct type (such as object or object[]) + + // Supposing current key is "users", with type "array". To fix this: + // 1. If `users.*` or `users.*.thing` exists, `users` is an `X[]` (where X is the type of `users.*` + // 2. If `users.` exists, `users` is an `object` + // 3. Otherwise, default to `object` + // Important: We're iterating in reverse, to ensure we set child items before parent items + // (assuming the user specified parents first, which is the more common thing)y + if (Arr::first($allKeys, fn ($key) => Str::startsWith($key, "{$name}.*."))) { + $details['type'] = 'object[]'; + unset($details['setter']); + } elseif ($childKey = Arr::first($allKeys, fn ($key) => Str::startsWith($key, "{$name}.*"))) { + $childType = ($converted[$childKey] ?? $parameters[$childKey])['type']; + $details['type'] = "{$childType}[]"; + } else { // `array` types default to `object` if no subtype is specified + $details['type'] = 'object'; + unset($details['setter']); + } + } + + $converted[$name] = $details; + } + + // Re-add items in the original order, so as to not cause side effects + foreach ($allKeys as $key) { + $parameters[$key] = $converted[$key] ?? $parameters[$key]; + } + + return $parameters; + } + + public function convertArraySubfields(array $parameters): array + { + $results = []; + foreach ($parameters as $name => $details) { + if (Str::endsWith($name, '.*')) { + // The user might have set the example via bodyParameters() + $exampleWasSpecified = $this->examplePresent($details); + + // Change cars.*.dogs.things.*.* with type X to cars.*.dogs.things with type X[][] + while (Str::endsWith($name, '.*')) { + $details['type'] .= '[]'; + $name = mb_substr($name, 0, -2); + + if ($exampleWasSpecified) { + $details['example'] = [$details['example']]; + } elseif (isset($details['setter'])) { + $previousSetter = $details['setter']; + $details['setter'] = fn () => [$previousSetter()]; + } + } + } + + $results[$name] = $details; + } + + return $results; + } + + public function setExamples(array $parameters): array + { + $examples = []; + + foreach ($parameters as $name => $details) { + if ($this->examplePresent($details)) { + // Record already-present examples (eg from bodyParameters()). + // This allows a user to set 'data' => ['example' => ['title' => 'A title'], + // and we automatically set this as the example for `data.title` + // Note that this approach assumes parent fields are listed before the children; meh. + $examples[$details['name']] = $details['example']; + } elseif (preg_match('/.+\.[^*]+$/', $details['name'])) { + // For object fields (eg 'data.details.title'), set examples from their parents if present as described above. + [$parentName, $fieldName] = preg_split('/\.(?=[\w-]+$)/', $details['name']); + if (array_key_exists($parentName, $examples) && is_array($examples[$parentName]) + && array_key_exists($fieldName, $examples[$parentName])) { + $examples[$details['name']] = $details['example'] = $examples[$parentName][$fieldName]; + } + } + + $details['example'] = $this->getParameterExample($details); + unset($details['setter']); + + $parameters[$name] = $details; + } + + return $parameters; + } + + /** + * Transform validation rules: + * 1. from strings to arrays: + * 'param1' => 'int|required' TO 'param1' => ['int', 'required'] + * 2. from '*.foo' to ' + * + * @param array $rules + */ + protected function normaliseRules(array $rules): array + { + // We can simply call Validator::make($data, $rules)->getRules() to get the normalised rules, + // but Laravel will ignore any nested array rules (`ids.*') + // unless the key referenced (`ids`) exists in the dataset and is a non-empty array + // So we'll create a single-item array for each array parameter + $testData = []; + foreach ($rules as $key => $ruleset) { + if (! Str::contains($key, '.*')) { + continue; + } + + // All we need is for Laravel to see this key exists + Arr::set($testData, str_replace('.*', '.0', $key), Str::random()); + } + + // Now this will return the complete ruleset. + // Nested array parameters will be present, with '*' replaced by '0' + $newRules = Validator::make($testData, $rules)->getRules(); + + return collect($newRules)->mapWithKeys(function ($val, $paramName) use ($rules) { + // Transform the key names back from '__asterisk__' to '*' + if (Str::contains($paramName, '__asterisk__')) { + // In Laravel < v11.44, * keys were replaced with only "__asterisk__" + // After that, * keys were replaced with "__asterisk__", eg "__asterisk__dkjiu78gujjhb + // See https://github.com/laravel/framework/pull/54845/ + $paramName = preg_replace('/__asterisk__[^.]*\b/', '*', $paramName); + } + + // Transform the key names back from 'ids.0' to 'ids.*' + if (Str::contains($paramName, '.0')) { + $genericArrayKeyName = str_replace('.0', '.*', $paramName); + + // But only if that was the original value + if (isset($rules[$genericArrayKeyName])) { + $paramName = $genericArrayKeyName; + } + } + + return [$paramName => $val]; + })->toArray(); + } + + // For inline Closure rules, turn any comment above it into a description + // + // $request->validate([ + // 'my_param' => [ + // 'required', + // /** Must be a hexadecimal number. */ + // function ($attribute, $value, $fail) { + // if (!preg_match('/^[0-9a-f]+$/', $value)) { + // $fail('Must be in hex format'); + // } + // }, + // ], + // ]); + protected function processClosureRule($rule, array &$parameterData): void + { + $docComment = (new \ReflectionFunction($rule instanceof ClosureValidationRule ? $rule->callback : $rule)) + ->getDocComment(); + + if (is_string($docComment)) { + $description = ''; + foreach (explode("\n", $docComment) as $line) { + $cleaned = preg_replace(['/\*+\/$/', '/^\/\*+\s*/', '/^\*+\s*/'], '', mb_trim($line)); + if ($cleaned !== '') { + $description .= ' '.$cleaned; + } + } + + $parameterData['description'] .= $description; + } + } + + protected function processEnumValidationRule($rule, array &$parameterData, array $allParameters = []): void + { + $property = (new \ReflectionClass($rule))->getProperty('type'); + $property->setAccessible(true); + $enumClass = $property->getValue($rule); + + if (enum_exists($enumClass) && method_exists($enumClass, 'tryFrom')) { + // $case->value only exists on BackedEnums, not UnitEnums + // method_exists($enum, 'tryFrom') implies the enum is a BackedEnum + // @phpstan-ignore-next-line + $cases = array_map(fn ($case) => $case->value, $enumClass::cases()); + $parameterData['type'] = gettype($cases[0]); + $parameterData['enumValues'] = $cases; + $parameterData['setter'] = fn () => Arr::random($cases); + } + } + + protected function processRuleObject($rule, array &$parameterData): void + { + if (method_exists($rule, 'invokable')) { + // Laravel wraps InvokableRule instances in an InvokableValidationRule class, + // so we must retrieve the original rule + $rule = $rule->invokable(); + } + + // Users can define a custom "docs" method on a rule to give Scribe more info. + if (method_exists($rule, 'docs')) { + $customData = call_user_func_array([$rule, 'docs'], []) ?: []; + + if (isset($customData['description'])) { + $parameterData['description'] .= ' '.$customData['description']; + } + if (isset($customData['example'])) { + $parameterData['setter'] = fn () => $customData['example']; + } elseif (isset($customData['setter'])) { + $parameterData['setter'] = $customData['setter']; + } + + $parameterData = array_merge($parameterData, Arr::except($customData, [ + 'description', 'example', 'setter', + ])); + } + } + + /** + * Parse a validation rule and extract a parameter type, description and setter (used to generate an example). + * + * @param array $allParameters all parameters, used to check if an argument in the date rules (eg `before:some_date`) + * is a parameter in the request body + * @param mixed $rule + * @param mixed $ruleArguments + */ + protected function processRule($rule, $ruleArguments, array &$parameterData, array $allParameters = []): bool + { + // Reminder: Always append to the description (with a leading space); don't overwrite. + try { + switch ($rule) { + case 'sometimes': + $parameterData['sometimes'] = true; + + break; + + case 'required': + if (! $parameterData['sometimes']) { + $parameterData['required'] = true; + } + + break; + + case 'accepted': + if (! $parameterData['sometimes']) { + $parameterData['required'] = true; + } + $parameterData['type'] = 'boolean'; + $parameterData['description'] .= ' Must be accepted.'; + $parameterData['setter'] = fn () => true; + + break; + + case 'accepted_if': + $parameterData['type'] = 'boolean'; + $parameterData['description'] .= " Must be accepted when {$ruleArguments[0]} is ".w::getListOfValuesAsFriendlyHtmlString(array_slice($ruleArguments, 1)); + $parameterData['setter'] = fn () => true; + + break; + + // Primitive types. No description should be added + case 'bool': + case 'boolean': + $parameterData['setter'] = function () { + return Arr::random([true, false]); + }; + $parameterData['type'] = 'boolean'; + + break; + + case 'string': + $parameterData['setter'] = function () use ($parameterData) { + return $this->generateDummyValue('string', ['name' => $parameterData['name']]); + }; + $parameterData['type'] = 'string'; + + break; + + case 'int': + case 'integer': + $parameterData['setter'] = function () { + return $this->generateDummyValue('integer'); + }; + $parameterData['type'] = 'integer'; + + break; + + case 'numeric': + $parameterData['setter'] = function () { + return $this->generateDummyValue('number'); + }; + $parameterData['type'] = 'number'; + + break; + + case 'array': + $parameterData['setter'] = function () { + return [$this->generateDummyValue('string')]; + }; + $parameterData['type'] = 'array'; // The cleanup code in normaliseArrayAndObjectParameters() will set this to a valid type (x[] or object) + + break; + + case 'file': + $parameterData['type'] = 'file'; + $parameterData['description'] .= ' Must be a file.'; + $parameterData['setter'] = function () { + return $this->generateDummyValue('file'); + }; + + break; + + // Special string types + case 'alpha': + $parameterData['description'] .= ' Must contain only letters.'; + $parameterData['setter'] = function () { + return $this->getFaker()->lexify('??????'); + }; + + break; + + case 'alpha_dash': + $parameterData['description'] .= ' Must contain only letters, numbers, dashes and underscores.'; + $parameterData['setter'] = function () { + return $this->getFaker()->lexify('???-???_?'); + }; + + break; + + case 'alpha_num': + $parameterData['description'] .= ' Must contain only letters and numbers.'; + $parameterData['setter'] = function () { + return $this->getFaker()->bothify('#?#???#'); + }; + + break; + + case 'timezone': + // Laravel's message merely says "The value must be a valid zone" + $parameterData['description'] .= ' Must be a valid time zone, such as Africa/Accra.'; + $parameterData['setter'] = $this->getFakeFactoryByName('timezone'); + + break; + + case 'email': + $parameterData['description'] .= ' '.$this->getDescription($rule); + $parameterData['setter'] = $this->getFakeFactoryByName('email'); + $parameterData['type'] = 'string'; + + break; + + case 'url': + $parameterData['setter'] = $this->getFakeFactoryByName('url'); + $parameterData['type'] = 'string'; + // Laravel's message is "The value format is invalid". Ugh.🤮 + $parameterData['description'] .= ' Must be a valid URL.'; + + break; + + case 'ip': + $parameterData['description'] .= ' '.$this->getDescription($rule); + $parameterData['type'] = 'string'; + $parameterData['setter'] = function () { + return $this->getFaker()->ipv4(); + }; + + break; + + case 'json': + $parameterData['type'] = 'string'; + $parameterData['description'] .= ' '.$this->getDescription($rule); + $parameterData['setter'] = function () { + return json_encode([$this->getFaker()->word(), $this->getFaker()->word()]); + }; + + break; + + case 'date': + $parameterData['type'] = 'string'; + $parameterData['description'] .= ' '.$this->getDescription($rule); + $parameterData['setter'] = fn () => date('Y-m-d\TH:i:s', time()); + + break; + + case 'date_format': + $parameterData['type'] = 'string'; + // Laravel description here is "The value must match the format Y-m-d". Not descriptive enough. + $parameterData['description'] .= " Must be a valid date in the format {$ruleArguments[0]}."; + $parameterData['setter'] = function () use ($ruleArguments) { + return date($ruleArguments[0], time()); + }; + + break; + + case 'after': + case 'after_or_equal': + $parameterData['type'] = 'string'; + $parameterData['description'] .= ' '.$this->getDescription($rule, [':date' => "{$ruleArguments[0]}"]); + // TODO introduce the concept of "modifiers", like date_format + // The startDate may refer to another field, in which case, we just ignore it for now. + $startDate = array_key_exists($ruleArguments[0], $allParameters) ? 'today' : $ruleArguments[0]; + $parameterData['setter'] = fn () => $this->getFaker()->dateTimeBetween($startDate, '+100 years')->format('Y-m-d'); + + break; + + case 'before': + case 'before_or_equal': + $parameterData['type'] = 'string'; + // The argument can be either another field or a date + // The endDate may refer to another field, in which case, we just ignore it for now. + $endDate = array_key_exists($ruleArguments[0], $allParameters) ? 'today' : $ruleArguments[0]; + $parameterData['description'] .= ' '.$this->getDescription($rule, [':date' => "{$ruleArguments[0]}"]); + $parameterData['setter'] = fn () => $this->getFaker()->dateTimeBetween('-30 years', $endDate)->format('Y-m-d'); + + break; + + case 'starts_with': + $parameterData['description'] .= ' Must start with one of '.w::getListOfValuesAsFriendlyHtmlString($ruleArguments); + $parameterData['setter'] = fn () => $this->getFaker()->lexify("{$ruleArguments[0]}????"); + + break; + + case 'ends_with': + $parameterData['description'] .= ' Must end with one of '.w::getListOfValuesAsFriendlyHtmlString($ruleArguments); + $parameterData['setter'] = fn () => $this->getFaker()->lexify("????{$ruleArguments[0]}"); + + break; + + case 'uuid': + $parameterData['description'] .= ' '.$this->getDescription($rule).' '; + $parameterData['setter'] = $this->getFakeFactoryByName('uuid'); + + break; + + case 'regex': + $parameterData['description'] .= ' '.$this->getDescription($rule, [':regex' => $ruleArguments[0]]); + $parameterData['setter'] = fn () => $this->getFaker()->regexify($ruleArguments[0]); + + break; + + // Special number types. + case 'digits': + $parameterData['description'] .= ' '.$this->getDescription($rule, [':digits' => $ruleArguments[0]]); + $parameterData['setter'] = fn () => $this->getFaker()->numerify(str_repeat('#', $ruleArguments[0])); + $parameterData['type'] = 'string'; + + break; + + case 'digits_between': + $parameterData['description'] .= ' '.$this->getDescription($rule, [':min' => $ruleArguments[0], ':max' => $ruleArguments[1]]); + $parameterData['setter'] = fn () => $this->getFaker()->numerify(str_repeat('#', rand($ruleArguments[0], $ruleArguments[1]))); + $parameterData['type'] = 'string'; + + break; + + // These rules can apply to numbers, strings, arrays or files + case 'size': + $parameterData['description'] .= ' '.$this->getDescription( + $rule, + [':size' => $ruleArguments[0]], + $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $parameterData['setter'] = $this->getDummyValueGenerator($parameterData['type'], ['size' => $ruleArguments[0]]); + + break; + + case 'min': + $parameterData['description'] .= ' '.$this->getDescription( + $rule, + [':min' => $ruleArguments[0]], + $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], (float) ($ruleArguments[0]), fieldName: $parameterData['name']); + + break; + + case 'max': + $parameterData['description'] .= ' '.$this->getDescription( + $rule, + [':max' => $ruleArguments[0]], + $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + $max = min($ruleArguments[0], 25); + $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], 1, $max, $parameterData['name']); + + break; + + case 'between': + $parameterData['description'] .= ' '.$this->getDescription( + $rule, + [':min' => $ruleArguments[0], ':max' => $ruleArguments[1]], + $this->getLaravelValidationBaseTypeMapping($parameterData['type']) + ); + // Avoid exponentially complex operations by using the minimum length + $parameterData['setter'] = $this->getDummyDataGeneratorBetween($parameterData['type'], (float) ($ruleArguments[0]), (float) ($ruleArguments[0]) + 1, $parameterData['name']); + + break; + + // Special file types. + case 'image': + $parameterData['type'] = 'file'; + $parameterData['description'] .= ' '.$this->getDescription($rule).' '; + $parameterData['setter'] = function () { + // This is fine because the file example generator generates an image + return $this->generateDummyValue('file'); + }; + + break; + + // Other rules. + case 'in': + // Cast numeric values in enumValues, but don't change the parameter type + // The type should remain as set by other rules (eg 'string' rule) or default 'string' + $castedValues = $ruleArguments; + $allNumeric = count($ruleArguments) > 0 && array_reduce( + $ruleArguments, + fn ($carry, $val) => $carry && is_numeric($val), + true + ); + + if ($allNumeric) { + // Check if all are integers (no decimal points) + $allIntegers = array_reduce( + $ruleArguments, + fn ($carry, $val) => $carry && ! Str::contains($val, '.'), + true + ); + + $castedValues = $allIntegers + ? array_map(fn ($v) => (int) $v, $ruleArguments) + : array_map(fn ($v) => (float) $v, $ruleArguments); + } + + $parameterData['enumValues'] = $castedValues; + $parameterData['setter'] = function () use ($castedValues) { + return Arr::random($castedValues); + }; + + break; + + // These rules only add a description. Generating valid examples is too complex. + case 'not_in': + $parameterData['description'] .= ' Must not be one of '.w::getListOfValuesAsFriendlyHtmlString($ruleArguments).' '; + + break; + + case 'required_if': + $parameterData['description'] .= sprintf( + " This field is required when {$ruleArguments[0]} is %s. ", + w::getListOfValuesAsFriendlyHtmlString(array_slice($ruleArguments, 1)) + ); + + break; + + case 'required_unless': + $parameterData['description'] .= sprintf( + " This field is required unless {$ruleArguments[0]} is in %s. ", + w::getListOfValuesAsFriendlyHtmlString(array_slice($ruleArguments, 1)) + ); + + break; + + case 'required_with': + $parameterData['description'] .= sprintf( + ' This field is required when %s is present. ', + w::getListOfValuesAsFriendlyHtmlString($ruleArguments) + ); + + break; + + case 'required_without': + $parameterData['description'] .= sprintf( + ' This field is required when %s is not present. ', + w::getListOfValuesAsFriendlyHtmlString($ruleArguments) + ); + + break; + + case 'required_with_all': + $parameterData['description'] .= sprintf( + ' This field is required when %s are present. ', + w::getListOfValuesAsFriendlyHtmlString($ruleArguments, 'and') + ); + + break; + + case 'required_without_all': + $parameterData['description'] .= sprintf( + ' This field is required when none of %s are present. ', + w::getListOfValuesAsFriendlyHtmlString($ruleArguments, 'and') + ); + + break; + + case 'same': + $parameterData['description'] .= " The value and {$ruleArguments[0]} must match."; + + break; + + case 'different': + $parameterData['description'] .= " The value and {$ruleArguments[0]} must be different."; + + break; + + case 'nullable': + $parameterData['nullable'] = true; + + break; + + case 'exists': + $parameterData['description'] .= " The {$ruleArguments[1]} of an existing record in the {$ruleArguments[0]} table."; + + break; + + default: + // Other rules not supported + break; + } + } catch (\Throwable $e) { + throw CouldntProcessValidationRule::forParam($parameterData['name'], $rule, $e); + } + + $parameterData['description'] = mb_trim($parameterData['description']); + + return true; + } + + /** + * Parse a string rule into the base rule and arguments. + * eg "in:1,2" becomes ["in", ["1", "2"]] + * Laravel validation rules are specified in the format {rule}:{arguments} + * Arguments are separated by commas. + * For instance the rule "max:3" states that the value may only be three letters. + * + * @param Rule|string $rule + */ + protected function parseStringRuleIntoRuleAndArguments($rule): array + { + $ruleArguments = []; + + if (str_contains($rule, ':')) { + [$rule, $argumentsString] = explode(':', $rule, 2); + + // These rules can have commas in their arguments, so we don't split on commas + if (in_array(mb_strtolower($rule), ['regex', 'date', 'date_format'])) { + $ruleArguments = [$argumentsString]; + } else { + $ruleArguments = str_getcsv($argumentsString); + } + } + + return [mb_strtolower(mb_trim($rule)), $ruleArguments]; + } + + protected function getParameterExample(array $parameterData) + { + // If no example was given by the user, set an autogenerated example. + // Each parsed rule returns a 'setter' function. We'll evaluate the last one. + if ($parameterData['example'] === self::$MISSING_VALUE) { + if (isset($parameterData['setter'])) { + return $parameterData['setter'](); + } + + return $parameterData['required'] + ? $this->generateDummyValue($parameterData['type']) + : null; + } + if (! is_null($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE) { + if ($parameterData['example'] === 'No-example' && ! $parameterData['required']) { + return null; + } + + // Casting again is important since values may have been cast to string in the validator + return $this->castToType($parameterData['example'], $parameterData['type']); + } + + return $parameterData['example'] === self::$MISSING_VALUE ? null : $parameterData['example']; + } + + protected function addMissingParentFields(array $parameters): array + { + $results = []; + foreach ($parameters as $name => $details) { + if (isset($results[$name])) { + continue; + } + + $parentPath = $name; + while (Str::contains($parentPath, '.')) { + $parentPath = preg_replace('/\.[^.]+$/', '', $parentPath); + $normalisedParentPath = str_replace('.*.', '[].', $parentPath); + + if (empty($results[$normalisedParentPath])) { + // Parent field doesn't exist, create it. + + if (Str::endsWith($parentPath, '.*')) { + $parentPath = mb_substr($parentPath, 0, -2); + $normalisedParentPath = str_replace('.*.', '[].', $parentPath); + + if (! empty($results[$normalisedParentPath])) { + break; + } + + $type = 'object[]'; + $example = [[]]; + } else { + $type = 'object'; + $example = []; + } + $results[$normalisedParentPath] = [ + 'name' => $normalisedParentPath, + 'type' => $type, + 'required' => false, + 'description' => '', + 'example' => $example, + ]; + } + } + + $details['name'] = $name = str_replace('.*.', '[].', $name); + + if (isset($parameters[$details['name']]) && $this->examplePresent($parameters[$details['name']])) { + $details['example'] = $parameters[$details['name']]['example']; + } + + $results[$name] = $details; + } + + return $results; + } + + protected function getDescription(string $rule, array $arguments = [], $baseType = 'string'): string + { + if ($rule === 'regex') { + return "Must match the regex {$arguments[':regex']}."; + } + + $translationString = "validation.{$rule}"; + $description = trans($translationString); + + // For rules that can apply to multiple types (eg 'max' rule), There is an array of possible messages + // 'numeric' => 'The :attribute must not be greater than :max' + // 'file' => 'The :attribute must have a size less than :max kilobytes' + // Depending on the translation engine, trans may return the array, or it will fail to translate the string + // and will need to be called with the baseType appended. + if ($description === $translationString) { + $translationString = "{$translationString}.{$baseType}"; + $translated = trans($translationString); + if ($translated !== $translationString) { + $description = $translated; + } + } elseif (is_array($description)) { + $description = $description[$baseType]; + } + + // Convert messages from failure type ("The :attribute is not a valid date.") to info ("The :attribute must be a valid date.") + $description = str_replace(['is not', 'does not'], ['must be', 'must'], $description); + $description = str_replace('may not', 'must not', $description); + + foreach ($arguments as $placeholder => $argument) { + $description = str_replace($placeholder, $argument, $description); + } + + // Laravel 10 added `field` to its messages: https://github.com/laravel/framework/pull/45974 + $description = str_replace('The :attribute field ', 'The value ', $description); + + $description = preg_replace('/(?!<\W):attribute\b/', 'value', $description); + + return str_replace( + ['The value must ', ' 1 characters', ' 1 digits', ' 1 kilobytes'], + ['Must ', ' 1 character', ' 1 digit', ' 1 kilobyte'], + $description + ); + } + + protected function getMissingCustomDataMessage($parameterName) + { + return ''; + } + + protected function shouldCastUserExample() + { + return false; + } + + protected function warnAboutMissingCustomParameterData(string $parameter, array $customParameterData): array + { + $parameterPlusDot = $parameter.'.'; + if (count($customParameterData) && ! isset($customParameterData[$parameter]) + && ! Arr::first(array_keys($customParameterData), fn ($key) => str_starts_with($key, $parameterPlusDot)) + ) { + c::debug($this->getMissingCustomDataMessage($parameter)); + } + + return $customParameterData; + } + + private function examplePresent(array $parameterData) + { + return isset($parameterData['example']) && $parameterData['example'] !== self::$MISSING_VALUE; + } + + private function getLaravelValidationBaseTypeMapping(string $parameterType): string + { + $mapping = [ + 'number' => 'numeric', + 'integer' => 'numeric', + 'file' => 'file', + 'string' => 'string', + 'array' => 'array', + ]; + + if (Str::endsWith($parameterType, '[]')) { + return 'array'; + } + + return $mapping[$parameterType] ?? 'string'; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/RouteDocBlocker.php b/vendor/knuckleswtf/scribe/src/Extracting/RouteDocBlocker.php new file mode 100644 index 00000000..584677be --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/RouteDocBlocker.php @@ -0,0 +1,98 @@ +hasMethod($methodName)) { + throw new \Exception('Error while fetching docblock for route '.c::getRouteRepresentation($route).": Class {$className} does not contain method {$methodName}"); + } + + $method = u::getReflectedRouteMethod([$className, $methodName]); + + $docBlocks = [ + 'method' => new DocBlock($method->getDocComment() ?: ''), + 'class' => new DocBlock($class->getDocComment() ?: ''), + ]; + self::cacheDocBlocks($route, $normalizedClassName, $methodName, $docBlocks); + + return $docBlocks; + } + + /** + * @param object|string $classNameOrInstance + */ + protected static function normalizeClassName($classNameOrInstance): string + { + if (is_object($classNameOrInstance)) { + // Route handlers are not destroyed until the script ends so this should be perfectly safe. + $classNameOrInstance = get_class($classNameOrInstance).'::'.spl_object_id($classNameOrInstance); + } + + return $classNameOrInstance; + } + + protected static function getCachedDocBlock(Route $route, string $className, string $methodName) + { + $routeId = self::getRouteCacheId($route, $className, $methodName); + + return self::$docBlocks[$routeId] ?? null; + } + + protected static function cacheDocBlocks(Route $route, string $className, string $methodName, array $docBlocks) + { + $routeId = self::getRouteCacheId($route, $className, $methodName); + self::$docBlocks[$routeId] = $docBlocks; + } + + private static function getRouteCacheId(Route $route, string $className, string $methodName): string + { + return $route->uri() + .':' + .implode(array_diff($route->methods(), ['HEAD'])) + .$className + .$methodName; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ApiResourceResponseTools.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ApiResourceResponseTools.php new file mode 100644 index 00000000..12045f80 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ApiResourceResponseTools.php @@ -0,0 +1,136 @@ +getContent(); + } + + public static function callApiResourceAndGetResponse(JsonResource $resource, ExtractedEndpointData $endpointData): JsonResponse + { + $uri = Utils::getUrlWithBoundParameters($endpointData->route->uri(), $endpointData->cleanUrlParameters); + $method = $endpointData->route->methods()[0]; + $request = Request::create($uri, $method); + $request->headers->add(['Accept' => 'application/json']); + // Set the route properly, so it works for users who have code that checks for the route. + $request->setRouteResolver(fn () => $endpointData->route); + + $previousBoundRequest = app('request'); + app()->bind('request', fn () => $request); + + $response = $resource->toResponse($request); + + app()->bind('request', fn () => $previousBoundRequest); + + return $response; + } + + public static function getApiResourceOrCollectionInstance( + string $apiResourceClass, + bool $isCollection, + ?callable $modelInstantiator, + array $paginationStrategy = [], + array $additionalData = [], + ): JsonResource { + // If the API Resource uses an empty $resource (e.g. an empty array), the $modelInstantiator will be null + // See https://github.com/knuckleswtf/scribe/issues/652 + $modelInstance = is_callable($modelInstantiator) ? $modelInstantiator() : []; + + try { + $resource = new $apiResourceClass($modelInstance); + } catch (\Exception) { + // If it is a ResourceCollection class, it might throw an error + // when trying to instantiate with something other than a collection + $resource = new $apiResourceClass(collect([$modelInstance])); + } + + if ($isCollection) { + // Collections can either use the regular JsonResource class (via `::collection()`, + // or a ResourceCollection (via `new`) + // See https://laravel.com/docs/5.8/eloquent-resources + $models = [$modelInstance, $modelInstantiator()]; + // Pagination can be in two forms: + // [15] : means ::paginate(15) + // [15, 'simple'] : means ::simplePaginate(15) + if (count($paginationStrategy) === 1) { + $perPage = $paginationStrategy[0]; + $paginator = new LengthAwarePaginator( + // For some reason, the LengthAware paginator needs only first page items to work correctly + collect($models)->slice(0, $perPage), + count($models), + $perPage + ); + $list = $paginator; + } elseif (count($paginationStrategy) === 2 && $paginationStrategy[1] === 'simple') { + $perPage = $paginationStrategy[0]; + $paginator = new Paginator($models, $perPage); + $list = $paginator; + } elseif (count($paginationStrategy) === 2 && $paginationStrategy[1] === 'cursor') { + $perPage = $paginationStrategy[0]; + $paginator = new CursorPaginator($models, $perPage); + $list = $paginator; + } else { + $list = collect($models); + } + + /** @var JsonResource $resource */ + $resource = $resource instanceof ResourceCollection + ? new $apiResourceClass($list) : $apiResourceClass::collection($list); + } + + return $resource->additional($additionalData); + } + + /** + * Check if the ApiResource class has an `@mixin` docblock, and fetch the model from there. + */ + public static function tryToInferApiResourceModel(string $apiResourceClass): ?string + { + $class = new \ReflectionClass($apiResourceClass); + $docBlock = new DocBlock($class->getDocComment() ?: ''); + + /** @var null|Tag $mixinTag */ + $mixinTag = Arr::first(Utils::filterDocBlockTags($docBlock->getTags(), 'mixin')); + if (empty($mixinTag) || empty($modelClass = mb_trim($mixinTag->getContent()))) { + return null; + } + + if (class_exists($modelClass)) { + return $modelClass; + } + + return null; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFieldTools.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFieldTools.php new file mode 100644 index 00000000..55756663 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFieldTools.php @@ -0,0 +1,38 @@ +responses)->first( + fn (Response $r) => $r->status >= 200 && $r->status < 300 + ); + if ($validResponse && ($validResponseContent = json_decode($validResponse->content, true))) { + $nonexistent = new \stdClass; + $value = $validResponseContent[$data['name']] + ?? $validResponseContent['data'][$data['name']] // Maybe it's a Laravel ApiResource + ?? $validResponseContent[0][$data['name']] // Maybe it's a list + ?? $validResponseContent['data'][0][$data['name']] // Maybe an Api Resource Collection? + ?? $nonexistent; + + if ($value !== $nonexistent) { + return self::normalizeTypeName(gettype($value), $value); + } + } + + return ''; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFileTools.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFileTools.php new file mode 100644 index 00000000..76a392d5 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ResponseFileTools.php @@ -0,0 +1,36 @@ +setSerializer(app($serializer)); + } + + $modelInstance = $modelInstantiator(); + if ($isCollection) { + $models = [$modelInstance, $modelInstantiator()]; + $resource = new Collection($models, new $transformerClass); + + ['adapter' => $paginatorAdapter, 'perPage' => $perPage] = $pagination; + if ($paginatorAdapter) { + $total = count($models); + // Need to pass only the first page to both adapter and paginator, otherwise they will display ebverything + $firstPage = collect($models)->slice(0, $perPage); + $resource = new Collection($firstPage, new $transformerClass, $resourceKey); + $paginator = new LengthAwarePaginator($firstPage, $total, $perPage); + $resource->setPaginator(new $paginatorAdapter($paginator)); + } + } else { + $resource = (new Item($modelInstance, new $transformerClass, $resourceKey)); + } + + return response($fractal->createData($resource)->toJson())->getContent(); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/UrlParamsNormalizer.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/UrlParamsNormalizer.php new file mode 100644 index 00000000..3e043a45 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/UrlParamsNormalizer.php @@ -0,0 +1,226 @@ +uri; + preg_match_all('#\{(\w+?)}#', $uri, $params); + + $resourceRouteNames = ['.index', '.show', '.update', '.destroy', '.store']; + + $typeHintedEloquentModels = self::getTypeHintedEloquentModels($method); + $routeName = $route->action['as'] ?? ''; + if (Str::endsWith($routeName, $resourceRouteNames)) { + // Note that resource routes can be nested eg users.posts.show + $pluralResources = explode('.', $routeName); + array_pop($pluralResources); // Remove the name of the action (eg `show`) + + $alreadyFoundResourceParam = false; + foreach (array_reverse($pluralResources) as $pluralResource) { + $singularResource = Str::singular($pluralResource); + + // Laravel turns hyphens in parameters to underscores + // (`cool-things/{cool-thing}` to `cool-things/{cool_thing_id}`) + // so we do the same + $singularResourceParam = str_replace('-', '_', $singularResource); + + $urlPatternsToSearchFor = [ + "{$pluralResource}/{{$singularResourceParam}}", + "{$pluralResource}/{{$singularResource}}", + "{$pluralResource}/{{$singularResourceParam}?}", + "{$pluralResource}/{{$singularResource}?}", + ]; + + $binding = self::getRouteKeyForUrlParam( + $route, + $singularResource, + $typeHintedEloquentModels, + 'id' + ); + + if (! $alreadyFoundResourceParam) { + // This is the first resource param (from the end). + // We set it to `params/{id}` (or whatever field it's bound to) + $replaceWith = [ + "{$pluralResource}/{{$binding}}", + "{$pluralResource}/{{$binding}}", + "{$pluralResource}/{{$binding}?}", + "{$pluralResource}/{{$binding}?}", + ]; + $alreadyFoundResourceParam = true; + } else { + // Other resource parameters will be `params/{_id}` + $replaceWith = [ + "{$pluralResource}/{{$singularResourceParam}_{$binding}}", + "{$pluralResource}/{{$singularResource}_{$binding}}", + "{$pluralResource}/{{$singularResourceParam}_{$binding}?}", + "{$pluralResource}/{{$singularResource}_{$binding}?}", + ]; + } + $uri = str_replace($urlPatternsToSearchFor, $replaceWith, $uri); + } + } + + foreach ($params[1] as $param) { + // For non-resource parameters, if there's a field binding/type-hinted variable, replace that too: + if ($binding = self::getRouteKeyForUrlParam($route, $param, $typeHintedEloquentModels)) { + $urlPatternsToSearchFor = ["{{$param}}", "{{$param}?}"]; + $replaceWith = ["{{$param}_{$binding}}", "{{$param}_{$binding}?}"]; + $uri = str_replace($urlPatternsToSearchFor, $replaceWith, $uri); + } + } + + return $uri; + } + + /** + * Return the type-hinted method arguments in the action that are Eloquent models, + * The arguments will be returned as an array of the form: [ => $instance]. + */ + public static function getTypeHintedEloquentModels(\ReflectionFunctionAbstract $method): array + { + $arguments = []; + foreach ($method->getParameters() as $argument) { + if (($instance = self::instantiateMethodArgument($argument)) && $instance instanceof Model) { + $arguments[$argument->getName()] = $instance; + } + } + + return $arguments; + } + + /** + * Return the type-hinted method arguments in the action that are enums, + * The arguments will be returned as an array of the form: [ => $instance]. + */ + public static function getTypeHintedEnums(\ReflectionFunctionAbstract $method): array + { + $arguments = []; + foreach ($method->getParameters() as $argument) { + $argumentType = $argument->getType(); + if (! $argumentType instanceof \ReflectionNamedType) { + continue; + } + + try { + $reflectionEnum = new \ReflectionEnum($argumentType->getName()); + $arguments[$argument->getName()] = $reflectionEnum; + } catch (\ReflectionException) { + continue; + } + } + + return $arguments; + } + + /** + * Given a URL that uses Eloquent model binding (for instance `/posts/{post}` -> `public function show(Post + * $post)`), we need to figure out the field that Eloquent uses to retrieve the Post object. By default, this would + * be `id`, but can be configured in a couple of ways: + * + * - Inline: `/posts/{post:slug}` + * - `class Post { public function getRouteKeyName() { return 'slug'; } }` + * + * There are other ways, but they're dynamic and beyond our scope. + * + * @param string $paramName The name of the URL parameter + * @param array $typeHintedEloquentModels + * @param null|string $default Default field to use + */ + protected static function getRouteKeyForUrlParam( + Route $route, + string $paramName, + array $typeHintedEloquentModels = [], + ?string $default = null, + ): ?string { + if ($binding = self::getInlineRouteKey($route, $paramName)) { + return $binding; + } + + return self::getRouteKeyFromModel($paramName, $typeHintedEloquentModels) ?: $default; + } + + /** + * Return the `slug` in /posts/{post:slug}. + */ + protected static function getInlineRouteKey(Route $route, string $paramName): ?string + { + return $route->bindingFieldFor($paramName); + } + + /** + * Check if there's a type-hinted argument on the controller method matching the URL param name: + * eg /posts/{post} -> public function show(Post $post) + * If there is, check if it's an Eloquent model. + * If it is, return it's `getRouteKeyName()`. + * + * @param Model[] $typeHintedEloquentModels + */ + protected static function getRouteKeyFromModel(string $paramName, array $typeHintedEloquentModels): ?string + { + // Ensure param name is in camelCase so it matches the argument name (e.g. The '$userAddress' in `function show(BigThing $userAddress`) + $paramName = Str::camel($paramName); + + if (array_key_exists($paramName, $typeHintedEloquentModels)) { + $argumentInstance = $typeHintedEloquentModels[$paramName]; + + return $argumentInstance->getRouteKeyName(); + } + + return null; + } + + /** + * Instantiate an argument on a controller method via its typehint. For instance, $post in: + * + * public function show(Post $post) + * + * This method takes in a method argument and returns an instance, or null if it couldn't be instantiated safely. + * Cases where instantiation may fail: + * - the argument has no type (eg `public function show($postId)`) + * - the argument has a primitive type (eg `public function show(string $postId)`) + * - the argument is an injected dependency that itself needs other dependencies + * (eg `public function show(PostsManager $manager)`) + */ + protected static function instantiateMethodArgument(\ReflectionParameter $argument): ?object + { + $argumentType = $argument->getType(); + // No type-hint, or primitive type + if (! $argumentType instanceof \ReflectionNamedType) { + return null; + } + + $argumentClassName = $argumentType->getName(); + if (class_exists($argumentClassName)) { + try { + return new $argumentClassName; + } catch (\Throwable $e) { + return null; + } + } + + if (interface_exists($argumentClassName)) { + try { + return app($argumentClassName); + } catch (\Throwable $e) { + return null; + } + } + + return null; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidate.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidate.php new file mode 100644 index 00000000..47e7c312 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidate.php @@ -0,0 +1,43 @@ +validate(...); + * or just + * $request->validate(...);. + * + * Also supports `$req` instead of `$request` + * Also supports `->validateWithBag('', ...)` + */ +class RequestValidate +{ + public static function find(Node $node) + { + if (! $node instanceof Node\Stmt\Expression) { + return; + } + + $expr = $node->expr; + if ($expr instanceof Node\Expr\Assign) { + $expr = $expr->expr; // If it's an assignment, get the expression on the RHS + } + + if ( + $expr instanceof Node\Expr\MethodCall + && $expr->var instanceof Node\Expr\Variable + && in_array($expr->var->name, ['request', 'req']) + ) { + if ($expr->name->name === 'validate') { + return $expr->args[0]->value; + } + + if ($expr->name->name === 'validateWithBag') { + return $expr->args[1]->value; + } + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidateFacade.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidateFacade.php new file mode 100644 index 00000000..bf5a150e --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/RequestValidateFacade.php @@ -0,0 +1,43 @@ +validateWithBag('', ...)` + */ +class RequestValidateFacade +{ + public static function find(Node $node) + { + if (! $node instanceof Node\Stmt\Expression) { + return; + } + + $expr = $node->expr; + if ($expr instanceof Node\Expr\Assign) { + $expr = $expr->expr; // If it's an assignment, get the expression on the RHS + } + + if ( + $expr instanceof Node\Expr\StaticCall + && $expr->class instanceof Node\Name + && in_array($expr->class->name, ['Request', Request::class]) + ) { + if ($expr->name->name === 'validate') { + return $expr->args[0]->value; + } + + if ($expr->name->name === 'validateWithBag') { + return $expr->args[1]->value; + } + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ThisValidate.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ThisValidate.php new file mode 100644 index 00000000..0e934c19 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ThisValidate.php @@ -0,0 +1,38 @@ +validate($request, ...); + * or just + * $this->validate($request, ...);. + * + * Also supports `$req` instead of `$request` + */ +class ThisValidate +{ + public static function find(Node $node) + { + if (! $node instanceof Node\Stmt\Expression) { + return; + } + + $expr = $node->expr; + if ($expr instanceof Node\Expr\Assign) { + $expr = $expr->expr; // If it's an assignment, get the expression on the RHS + } + + if ( + $expr instanceof Node\Expr\MethodCall + && $expr->var instanceof Node\Expr\Variable + && $expr->var->name === 'this' + ) { + if ($expr->name->name === 'validate') { + return $expr->args[1]->value; + } + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ValidatorMake.php b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ValidatorMake.php new file mode 100644 index 00000000..f9acac15 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Shared/ValidationRulesFinders/ValidatorMake.php @@ -0,0 +1,35 @@ +validate(). + * + * The variable names (`$validator` and `$request`) don't matter. + */ +class ValidatorMake +{ + public static function find(Node $node) + { + // Make sure it's an assignment + if (! $node instanceof Node\Stmt\Expression) { + return; + } + + $validatorNode = (new NodeFinder)->findFirst($node, function ($node): bool { + return $node instanceof Node\Expr\StaticCall + && ! empty($node->class->name) + && str_ends_with($node->class->name, 'Validator') + && $node->name->name === 'make'; + }); + + if ($validatorNode instanceof Node\Expr\StaticCall) { + return $validatorNode->args[1]->value; + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamAttribute.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamAttribute.php new file mode 100644 index 00000000..2254553f --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamAttribute.php @@ -0,0 +1,14 @@ + + */ +class GetFromBodyParamAttribute extends GetParamsFromAttributeStrategy +{ + protected static array $attributeNames = [BodyParam::class]; +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php new file mode 100644 index 00000000..c4f1ee67 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromBodyParamTag.php @@ -0,0 +1,47 @@ + <"required" (optional)> <"deprecated" (optional)> + // Examples: + // @bodyParam text string required The text. + // @bodyParam user_id integer The ID of the user. + // @bodyParam status string required deprecated Use `is_active` instead. + preg_match('/(.+?)\s+(.+?)\s+(required\s+)?(deprecated\s+)?([\s\S]*)/', $tagContent, $parsedContent); + + if (empty($parsedContent)) { + // This means only name and type were supplied + [$name, $type] = preg_split('/\s+/', $tagContent); + $required = false; + $deprecated = false; + $description = ''; + } else { + [$_, $name, $type, $required, $deprecated, $description] = $parsedContent; + $description = mb_trim(str_replace(['No-example.', 'No-example'], '', $description)); + if ($description === 'required') { + $required = $description; + $description = ''; + } elseif ($description === 'deprecated') { + $deprecated = $description; + $description = ''; + } + $required = mb_trim($required) === 'required'; + $deprecated = mb_trim($deprecated) === 'deprecated'; + } + + $type = static::normalizeTypeName($type); + [$description, $example, $enumValues, $exampleWasSpecified] + = $this->getDescriptionAndExample($description, $type, $tagContent, $name); + + return compact('name', 'type', 'description', 'required', 'deprecated', 'example', 'enumValues', 'exampleWasSpecified'); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php new file mode 100644 index 00000000..e5a13421 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php @@ -0,0 +1,23 @@ +getDocComment(); + if (mb_strpos(mb_strtolower($formRequestDocBlock), 'query parameters') !== false + || $formRequestReflectionClass->hasMethod('queryParameters')) { + return false; + } + + return true; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromInlineValidator.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromInlineValidator.php new file mode 100644 index 00000000..e1f0e6d0 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/BodyParameters/GetFromInlineValidator.php @@ -0,0 +1,21 @@ +getComments(); + $comments = implode("\n", array_map(fn ($comment) => $comment->getReformattedText(), $comments)); + if (mb_strpos(mb_strtolower($comments), 'query parameters') !== false) { + return false; + } + + return true; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFieldsFromTagStrategy.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFieldsFromTagStrategy.php new file mode 100644 index 00000000..a48da838 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFieldsFromTagStrategy.php @@ -0,0 +1,68 @@ +getName()) !== mb_strtolower($this->tagName)) { + continue; + } + + $fieldData = $this->parseTag(mb_trim($tag->getContent())); + $fields[$fieldData['name']] = $fieldData; + } + + foreach ($tagsOnMethod as $tag) { + if (mb_strtolower($tag->getName()) !== mb_strtolower($this->tagName)) { + continue; + } + + $fieldData = $this->parseTag(mb_trim($tag->getContent())); + $fields[$fieldData['name']] = $fieldData; + } + + return $fields; + } + + abstract protected function parseTag(string $tagContent): array; + + protected function getDescriptionAndExample( + string $description, + string $type, + string $tagContent, + string $fieldName, + ): array { + [$description, $example, $enumValues, $exampleWasSpecified] = $this->parseExampleFromParamDescription($description, $type); + + if ($exampleWasSpecified && $example === null) { + $example = null; + } else { + $example = $this->setExampleIfNeeded($example, $type, $tagContent, $fieldName, $enumValues); + } + + return [$description, $example, $enumValues, $exampleWasSpecified]; + } + + protected function setExampleIfNeeded( + mixed $currentExample, + string $type, + string $tagContent, + string $fieldName, + ?array $enumValues = [], + ): mixed { + return (is_null($currentExample) && ! $this->shouldExcludeExample($tagContent)) + ? $this->generateDummyValue($type, hints: ['name' => $fieldName, 'enumValues' => $enumValues]) + : $currentExample; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromFormRequestBase.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromFormRequestBase.php new file mode 100644 index 00000000..9ca8f52b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromFormRequestBase.php @@ -0,0 +1,98 @@ +getParametersFromFormRequest($endpointData->method, $endpointData->route); + } + + public function getParametersFromFormRequest(\ReflectionFunctionAbstract $method, Route $route): array + { + if (! $formRequestReflectionClass = $this->getFormRequestReflectionClass($method)) { + return []; + } + + if (! $this->isFormRequestMeantForThisStrategy($formRequestReflectionClass)) { + return []; + } + + $className = $formRequestReflectionClass->getName(); + + if (Globals::$__instantiateFormRequestUsing) { + $formRequest = call_user_func_array(Globals::$__instantiateFormRequestUsing, [$className, $route, $method]); + } else { + $formRequest = new $className; + } + // Set the route properly so it works for users who have code that checks for the route. + /** @var FormRequest $formRequest */ + $formRequest->setRouteResolver(function () use ($formRequest, $route) { + // Also need to bind the request to the route in case their code tries to inspect current request + return $route->bind($formRequest); + }); + $formRequest->server->set('REQUEST_METHOD', $route->methods()[0]); + + $parametersFromFormRequest = $this->getParametersFromValidationRules( + $this->getRouteValidationRules($formRequest), + $this->getCustomParameterData($formRequest) + ); + + return $this->normaliseArrayAndObjectParameters($parametersFromFormRequest); + } + + /** + * @return mixed + */ + protected function getRouteValidationRules(FormRequest $formRequest) + { + if (method_exists($formRequest, 'validator')) { + $validationFactory = app(ValidationFactory::class); + + // @phpstan-ignore-next-line + return app()->call([$formRequest, 'validator'], [$validationFactory]) + ->getRules(); + } + if (method_exists($formRequest, 'rules')) { + return app()->call([$formRequest, 'rules']); + } + + return []; + } + + protected function getCustomParameterData(FormRequest $formRequest) + { + if (method_exists($formRequest, $this->customParameterDataMethodName)) { + return call_user_func_array([$formRequest, $this->customParameterDataMethodName], []); + } + + c::warn("No {$this->customParameterDataMethodName}() method found in ".get_class($formRequest).'. Scribe will only be able to extract basic information from the rules() method.'); + + return []; + } + + protected function getMissingCustomDataMessage($parameterName) + { + return "No data found for parameter '{$parameterName}' in your {$this->customParameterDataMethodName}() method. Add an entry for '{$parameterName}' so you can add a description and example."; + } + + protected function isFormRequestMeantForThisStrategy(\ReflectionClass $formRequestReflectionClass): bool + { + return $formRequestReflectionClass->hasMethod($this->customParameterDataMethodName); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromInlineValidatorBase.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromInlineValidatorBase.php new file mode 100644 index 00000000..747bd4b2 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetFromInlineValidatorBase.php @@ -0,0 +1,207 @@ +method instanceof \ReflectionMethod) { + return []; + } + + $methodAst = MethodAstParser::getMethodAst($endpointData->method); + [$validationRules, $customParameterData] = $this->lookForInlineValidationRules($methodAst); + + $bodyParametersFromValidationRules = $this->getParametersFromValidationRules($validationRules, $customParameterData); + + return $this->normaliseArrayAndObjectParameters($bodyParametersFromValidationRules); + } + + public function lookForInlineValidationRules(ClassMethod $methodAst): array + { + // Validation usually happens early on, so let's assume it's in the first 10 statements + $statements = array_slice($methodAst->stmts, 0, 10); + + [$index, $validationStatement, $validationRules] = $this->findValidationExpression($statements); + + if ($validationStatement + && ! $this->isValidationStatementMeantForThisStrategy($validationStatement)) { + return [[], []]; + } + + // If validation rules were saved in a variable (like $rules), + // try to find the var and expand the value + if ($validationRules instanceof Node\Expr\Variable) { + foreach (array_reverse(array_slice($statements, 0, $index)) as $earlierStatement) { + if ( + $earlierStatement instanceof Node\Stmt\Expression + && $earlierStatement->expr instanceof Node\Expr\Assign + && $earlierStatement->expr->var instanceof Node\Expr\Variable + && $earlierStatement->expr->var->name === $validationRules->name + ) { + $validationRules = $earlierStatement->expr->expr; + + break; + } + } + } + + if (! $validationRules instanceof Node\Expr\Array_) { + return [[], []]; + } + + $rules = []; + $customParameterData = []; + foreach ($validationRules->items as $item) { + /** @var Node\ArrayItem $item */ + if (! $item->key instanceof Node\Scalar\String_) { + continue; + } + + $paramName = $item->key->value; + + // Might be an expression or concatenated string, etc. + // For now, let's focus on simple strings and arrays of strings + if ($item->value instanceof Node\Scalar\String_) { + $rules[$paramName] = $item->value->value; + } elseif ($item->value instanceof Node\Expr\Array_) { + $rulesList = []; + foreach ($item->value->items as $arrayItem) { + /** @var Node\ArrayItem $arrayItem */ + if ($arrayItem->value instanceof Node\Scalar\String_) { + $rulesList[] = $arrayItem->value->value; + } + // Try to extract Enum rule + elseif ( + ($enum = $this->extractEnumClassFromArrayItem($arrayItem)) + && enum_exists($enum) && method_exists($enum, 'tryFrom') + ) { + // $case->value only exists on BackedEnums, not UnitEnums + // method_exists($enum, 'tryFrom') implies the enum is a BackedEnum + // @phpstan-ignore-next-line + $rulesList[] = 'in:'.implode(',', array_map(fn ($case) => $case->value, $enum::cases())); + } + } + $rules[$paramName] = implode('|', $rulesList); + } else { + $rules[$paramName] = []; + } + + $dataFromComment = []; + $comments = implode("\n", array_map( + fn ($comment) => mb_ltrim(mb_ltrim($comment->getReformattedText(), '/')), + $item->getComments() + )); + + if ($comments) { + if (str_contains($comments, 'No-example')) { + $dataFromComment['example'] = null; + } + + $dataFromComment['description'] = mb_trim(str_replace(['No-example.', 'No-example'], '', $comments)); + if (preg_match('/(.*\s+|^)Example:\s*([\s\S]+)\s*/s', $dataFromComment['description'], $matches)) { + $dataFromComment['description'] = mb_trim($matches[1]); + $dataFromComment['example'] = $matches[2]; + } + } + + $customParameterData[$paramName] = $dataFromComment; + } + + return [$rules, $customParameterData]; + } + + protected function extractEnumClassFromArrayItem(Node\ArrayItem $arrayItem): ?string + { + $args = []; + + // Enum rule with the form "new Enum(...)" + if ($arrayItem->value instanceof Node\Expr\New_ + && $arrayItem->value->class instanceof Node\Name + && str_ends_with($arrayItem->value->class->name, 'Enum') + ) { + $args = $arrayItem->value->args; + } + + // Enum rule with the form "Rule::enum(...)" + elseif ($arrayItem->value instanceof Node\Expr\StaticCall + && $arrayItem->value->class instanceof Node\Name + && str_ends_with($arrayItem->value->class->name, 'Rule') + && $arrayItem->value->name instanceof Node\Identifier + && $arrayItem->value->name->name === 'enum' + ) { + $args = $arrayItem->value->args; + } + + if (count($args) !== 1 || ! $args[0] instanceof Node\Arg) { + return null; + } + + $arg = $args[0]; + if ($arg->value instanceof Node\Expr\ClassConstFetch + && $arg->value->class instanceof Node\Name + ) { + $className = $arg->value->class->getAttribute('resolvedName'); + // Only prepend '\\' if the class name is already fully qualified (contains '\') + // For relative names, return as-is and let enum_exists use autoloading to resolve. + if (mb_strpos($className, '\\') !== false) { + return '\\'.$className; + } + + return $className; + } + if ($arg->value instanceof Node\Scalar\String_) { + return $arg->value->value; + } + + return null; + } + + protected function getMissingCustomDataMessage($parameterName) + { + return "No extra data found for parameter '{$parameterName}' from your inline validator. You can add a comment above '{$parameterName}' with a description and example."; + } + + protected function shouldCastUserExample() + { + return true; + } + + protected function isValidationStatementMeantForThisStrategy(Node $validationStatement): bool + { + return true; + } + + protected function findValidationExpression($statements): ?array + { + $strategies = [ + RequestValidate::class, // $request->validate(...); + RequestValidateFacade::class, // Request::validate(...); + ValidatorMake::class, // Validator::make($request, ...) + ThisValidate::class, // $this->validate(...); + ]; + + foreach ($statements as $index => $node) { + foreach ($strategies as $strategy) { + if ($validationRules = $strategy::find($node)) { + return [$index, $node, $validationRules]; + } + } + } + + return [null, null, null]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php new file mode 100644 index 00000000..90bcde8e --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/GetParamsFromAttributeStrategy.php @@ -0,0 +1,52 @@ + + */ +class GetParamsFromAttributeStrategy extends PhpAttributeStrategy +{ + use ParamHelpers; + + protected function extractFromAttributes( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest = [], + array $attributesOnController = [], + ): ?array { + $parameters = []; + foreach ([...$attributesOnController, ...$attributesOnFormRequest, ...$attributesOnMethod] as $attributeInstance) { + $parameters[$attributeInstance->name] = $attributeInstance->toArray(); + } + + return array_map([$this, 'normalizeParameterData'], $parameters); + } + + protected function normalizeParameterData(array $data): array + { + $data['type'] = static::normalizeTypeName($data['type']); + if (is_null($data['example'])) { + $data['example'] = $this->generateDummyValue($data['type'], [ + 'name' => $data['name'], + 'enumValues' => $data['enumValues'], + ]); + } elseif ($data['example'] === 'No-example' || $data['example'] === 'No-example.') { + $data['example'] = null; + } + + if ($data['required']) { + $data['nullable'] = false; + } + + $data['description'] = mb_trim($data['description'] ?? ''); + + return $data; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderAttribute.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderAttribute.php new file mode 100644 index 00000000..4491e4dd --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderAttribute.php @@ -0,0 +1,31 @@ + + */ +class GetFromHeaderAttribute extends PhpAttributeStrategy +{ + protected static array $attributeNames = [Header::class]; + + protected function extractFromAttributes( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest = [], + array $attributesOnController = [], + ): ?array { + $headers = []; + foreach ([...$attributesOnController, ...$attributesOnFormRequest, ...$attributesOnMethod] as $attributeInstance) { + $data = $attributeInstance->toArray(); + $data['example'] ??= $this->generateDummyValue('string'); + $headers[$data['name']] = $data['example']; + } + + return $headers; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderTag.php new file mode 100644 index 00000000..adf0e06c --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Headers/GetFromHeaderTag.php @@ -0,0 +1,39 @@ +mapWithKeys(function (Tag $tag) { + // Format: + // @header + // Examples: + // @header X-Custom An API header + preg_match('/([\S]+)(.*)?/', $tag->getContent(), $content); + + [$_, $name, $example] = $content; + $example = mb_trim($example); + if (empty($example)) { + $example = $this->generateDummyValue('string'); + } + + return [$name => $example]; + })->toArray(); + + return $headers; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromDocBlocks.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromDocBlocks.php new file mode 100644 index 00000000..a7e2e935 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromDocBlocks.php @@ -0,0 +1,156 @@ +route); + $methodDocBlock = $docBlocks['method']; + $classDocBlock = $docBlocks['class']; + + return $this->getMetadataFromDocBlock($methodDocBlock, $classDocBlock); + } + + public function getMetadataFromDocBlock(DocBlock $methodDocBlock, DocBlock $classDocBlock): array + { + [$groupName, $groupDescription, $title] = $this->getEndpointGroupAndTitleDetails($methodDocBlock, $classDocBlock); + + $metadata = [ + 'groupName' => $groupName, + 'groupDescription' => $groupDescription, + 'subgroup' => $this->getEndpointSubGroup($methodDocBlock, $classDocBlock), + 'subgroupDescription' => $this->getEndpointSubGroupDescription($methodDocBlock, $classDocBlock), + 'title' => $title ?: $methodDocBlock->getShortDescription(), + 'description' => $methodDocBlock->getLongDescription()->getContents(), + 'deprecated' => $this->getDeprecatedStatusFromDocBlock($methodDocBlock, $classDocBlock), + ]; + if (! is_null($authStatus = $this->getAuthStatusFromDocBlock($methodDocBlock, $classDocBlock))) { + $metadata['authenticated'] = $authStatus; + } + + return $metadata; + } + + protected function getAuthStatusFromDocBlock(DocBlock $methodDocBlock, ?DocBlock $classDocBlock = null): ?bool + { + foreach ($methodDocBlock->getTags() as $tag) { + if (mb_strtolower($tag->getName()) === 'authenticated') { + return true; + } + + if (mb_strtolower($tag->getName()) === 'unauthenticated') { + return false; + } + } + + return $classDocBlock + ? $this->getAuthStatusFromDocBlock($classDocBlock) + : null; + } + + protected function getDeprecatedStatusFromDocBlock(DocBlock $methodDocBlock, ?DocBlock $classDocBlock = null): bool|string + { + foreach ($methodDocBlock->getTags() as $tag) { + if (mb_strtolower($tag->getName()) === 'deprecated') { + return $tag->getContent() === '' ? true : $tag->getContent(); + } + } + + if ($classDocBlock instanceof DocBlock) { + return $this->getDeprecatedStatusFromDocBlock($classDocBlock); + } + + return false; + } + + /** + * @return array The endpoint's group name, the group description, and the endpoint title + */ + protected function getEndpointGroupAndTitleDetails(DocBlock $methodDocBlock, DocBlock $controllerDocBlock) + { + foreach ($methodDocBlock->getTags() as $tag) { + if ($tag->getName() === 'group') { + $endpointGroupParts = explode("\n", mb_trim($tag->getContent())); + $endpointGroupName = array_shift($endpointGroupParts); + $endpointGroupDescription = mb_trim(implode("\n", $endpointGroupParts)); + + // If the endpoint has no title (the methodDocBlock's "short description"), + // we'll assume the endpointGroupDescription is actually the title + // Something like this: + // /** + // * Fetch cars. <-- This is endpoint title. + // * @group Cars <-- This is group name. + // * APIs for cars. <-- This is group description (not required). + // **/ + // VS + // /** + // * @group Cars <-- This is group name. + // * Fetch cars. <-- This is endpoint title, NOT group description. + // **/ + + // BTW, this is a spaghetti way of doing this. + // It shall be refactored soon. Deus vult!💪 + // ...Or maybe not + if (empty($methodDocBlock->getShortDescription())) { + return [$endpointGroupName, '', $endpointGroupDescription]; + } + + return [$endpointGroupName, $endpointGroupDescription, $methodDocBlock->getShortDescription()]; + } + } + + // Fall back to the controller + foreach ($controllerDocBlock->getTags() as $tag) { + if ($tag->getName() === 'group') { + $endpointGroupParts = explode("\n", mb_trim($tag->getContent())); + $endpointGroupName = array_shift($endpointGroupParts); + $endpointGroupDescription = implode("\n", $endpointGroupParts); + + return [$endpointGroupName, $endpointGroupDescription, $methodDocBlock->getShortDescription()]; + } + } + + return [null, '', $methodDocBlock->getShortDescription()]; + } + + protected function getEndpointSubGroup(DocBlock $methodDocBlock, DocBlock $controllerDocBlock): ?string + { + foreach ($methodDocBlock->getTags() as $tag) { + if (mb_strtolower($tag->getName()) === 'subgroup') { + return mb_trim($tag->getContent()); + } + } + + foreach ($controllerDocBlock->getTags() as $tag) { + if (mb_strtolower($tag->getName()) === 'subgroup') { + return mb_trim($tag->getContent()); + } + } + + return null; + } + + protected function getEndpointSubGroupDescription(DocBlock $methodDocBlock, DocBlock $controllerDocBlock): ?string + { + foreach ($methodDocBlock->getTags() as $tag) { + if (mb_strtolower($tag->getName()) === 'subgroupdescription') { + return mb_trim($tag->getContent()); + } + } + + foreach ($controllerDocBlock->getTags() as $tag) { + if (mb_strtolower($tag->getName()) === 'subgroupdescription') { + return mb_trim($tag->getContent()); + } + } + + return null; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromMetadataAttributes.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromMetadataAttributes.php new file mode 100644 index 00000000..89b3b9f0 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Metadata/GetFromMetadataAttributes.php @@ -0,0 +1,51 @@ + + */ +class GetFromMetadataAttributes extends PhpAttributeStrategy +{ + use ParamHelpers; + + protected static array $attributeNames = [ + Group::class, + Subgroup::class, + Endpoint::class, + Authenticated::class, + Unauthenticated::class, + Deprecated::class, + ]; + + protected function extractFromAttributes( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest = [], + array $attributesOnController = [], + ): ?array { + $metadata = [ + 'groupName' => '', + 'groupDescription' => '', + 'subgroup' => '', + 'subgroupDescription' => '', + 'title' => '', + 'description' => '', + ]; + foreach ([...$attributesOnController, ...$attributesOnFormRequest, ...$attributesOnMethod] as $attributeInstance) { + $metadata = array_merge($metadata, $attributeInstance->toArray()); + } + + return $metadata; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/PhpAttributeStrategy.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/PhpAttributeStrategy.php new file mode 100644 index 00000000..e17cfaa7 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/PhpAttributeStrategy.php @@ -0,0 +1,66 @@ +endpointData = $endpointData; + [$attributesOnMethod, $attributesOnFormRequest, $attributesOnController] + = $this->getAttributes($endpointData->method, $endpointData->controller); + + return $this->extractFromAttributes($endpointData, $attributesOnMethod, $attributesOnFormRequest, $attributesOnController); + } + + /** + * @return array{array, array, array} + */ + protected function getAttributes(\ReflectionFunctionAbstract $method, ?\ReflectionClass $class = null): array + { + $attributesOnMethod = collect(static::$attributeNames) + ->flatMap(fn (string $name) => $method->getAttributes($name, \ReflectionAttribute::IS_INSTANCEOF)) + ->map(fn (\ReflectionAttribute $a) => $a->newInstance())->all(); + + // If there's a FormRequest, we check there. + if ($formRequestClass = $this->getFormRequestReflectionClass($method)) { + $attributesOnFormRequest = collect(static::$attributeNames) + ->flatMap(fn (string $name) => $formRequestClass->getAttributes($name, \ReflectionAttribute::IS_INSTANCEOF)) + ->map(fn (\ReflectionAttribute $a) => $a->newInstance())->all(); + } + + if ($class) { + $attributesOnController = collect(static::$attributeNames) + ->flatMap(fn (string $name) => $class->getAttributes($name, \ReflectionAttribute::IS_INSTANCEOF)) + ->map(fn (\ReflectionAttribute $a) => $a->newInstance())->all(); + } + + return [$attributesOnMethod, $attributesOnFormRequest ?? [], $attributesOnController ?? []]; + } + + /** + * @param array $attributesOnMethod + * @param array $attributesOnController + */ + abstract protected function extractFromAttributes( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest = [], + array $attributesOnController = [], + ): ?array; +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromFormRequest.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromFormRequest.php new file mode 100644 index 00000000..e2f6e499 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromFormRequest.php @@ -0,0 +1,22 @@ +getDocComment(); + if (mb_strpos(mb_strtolower($formRequestDocBlock), 'query parameters') !== false) { + return true; + } + + return parent::isFormRequestMeantForThisStrategy($formRequestReflectionClass); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromInlineValidator.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromInlineValidator.php new file mode 100644 index 00000000..bbffcb79 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromInlineValidator.php @@ -0,0 +1,21 @@ +getComments(); + $comments = implode("\n", array_map(fn ($comment) => $comment->getReformattedText(), $comments)); + if (mb_strpos(mb_strtolower($comments), 'query parameters') !== false) { + return true; + } + + return false; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamAttribute.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamAttribute.php new file mode 100644 index 00000000..c4e96901 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamAttribute.php @@ -0,0 +1,14 @@ + + */ +class GetFromQueryParamAttribute extends GetParamsFromAttributeStrategy +{ + protected static array $attributeNames = [QueryParam::class]; +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php new file mode 100644 index 00000000..acf6a4a6 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/QueryParameters/GetFromQueryParamTag.php @@ -0,0 +1,80 @@ + <"required" (optional)> <"deprecated" (optional)> + // Examples: + // @queryParam text required The text. + // @queryParam user_id integer The ID of the user. + // @queryParam sort string deprecated Use `order` parameter instead. + preg_match('/(.+?)\s+([a-zA-Z\[\]]+\s+)?(required\s+)?(deprecated\s+)?([\s\S]*)/', $tagContent, $content); + + if (empty($content)) { + // This means only name was supplied + $name = $tagContent; + $required = false; + $deprecated = false; + $description = ''; + $type = 'string'; + } else { + [$_, $name, $type, $required, $deprecated, $description] = $content; + + $description = mb_trim(str_replace(['No-example.', 'No-example'], '', $description)); + if ($description === 'required') { + // No description was supplied + $required = true; + $description = ''; + } elseif ($description === 'deprecated') { + $deprecated = true; + $description = ''; + } else { + $required = mb_trim($required) === 'required'; + $deprecated = mb_trim($deprecated) === 'deprecated'; + } + + $type = mb_trim($type); + if ($type) { + if ($type === 'required') { + // Type wasn't supplied + $type = 'string'; + $required = true; + } elseif ($type === 'deprecated') { + // Type wasn't supplied but deprecated was + $type = 'string'; + $deprecated = true; + } else { + $type = static::normalizeTypeName($type); + // Type in annotation is optional + if (! $this->isSupportedTypeInDocBlocks($type)) { + // Then that wasn't a type, but part of the description + $description = mb_trim("{$type} {$description}"); + $type = ''; + } + } + } elseif ($this->isSupportedTypeInDocBlocks($description)) { + // Only type was supplied + $type = $description; + $description = ''; + } + + $type = empty($type) + ? (Str::contains(mb_strtolower($description), ['number', 'count', 'page']) ? 'integer' : 'string') + : static::normalizeTypeName($type); + } + + [$description, $example, $enumValues, $exampleWasSpecified] + = $this->getDescriptionAndExample($description, $type, $tagContent, $name); + + return compact('name', 'description', 'required', 'deprecated', 'example', 'type', 'enumValues', 'exampleWasSpecified'); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldAttribute.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldAttribute.php new file mode 100644 index 00000000..357261a0 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldAttribute.php @@ -0,0 +1,75 @@ + + */ +class GetFromResponseFieldAttribute extends PhpAttributeStrategy +{ + protected static array $attributeNames = [ResponseField::class]; + + protected function extractFromAttributes( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest = [], + array $attributesOnController = [], + ): ?array { + return [ + ...$this->getNonApiResourceFields($endpointData, $attributesOnMethod, $attributesOnFormRequest, $attributesOnController), + ...$this->getApiResourceFields($endpointData), + ]; + } + + protected function getApiResourceFields(ExtractedEndpointData $endpointData): array + { + $apiResourceAttributes = $endpointData->method->getAttributes(ResponseFromApiResource::class); + + return collect($apiResourceAttributes) + ->flatMap(fn (\ReflectionAttribute $attribute) => $this->extractFieldsFromApiResource($attribute, $endpointData)) + ->toArray(); + } + + protected function extractFieldsFromApiResource(\ReflectionAttribute $attribute, ExtractedEndpointData $endpointData): array + { + $className = $attribute->newInstance()->name; + $method = u::getReflectedRouteMethod([$className, 'toArray']); + $wrapKey = $className::$wrap ?? null; + + return collect($method->getAttributes(ResponseField::class)) + ->mapWithKeys(function (\ReflectionAttribute $attr) use ($endpointData, $wrapKey) { + $data = $attr->newInstance()->toArray(); + $data['type'] = ResponseFieldTools::inferTypeOfResponseField($data, $endpointData); + + if ($wrapKey !== null) { + $data['name'] = $wrapKey.'.'.$data['name']; + } + + return [$data['name'] => $data]; + })->toArray(); + } + + protected function getNonApiResourceFields( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest, + array $attributesOnController, + ): array { + return collect([...$attributesOnController, ...$attributesOnFormRequest, ...$attributesOnMethod]) + ->mapWithKeys(function ($attributeInstance) use ($endpointData) { + /** @var ResponseField $attributeInstance */ + $data = $attributeInstance->toArray(); + + $data['type'] = ResponseFieldTools::inferTypeOfResponseField($data, $endpointData); + + return [$data['name'] => $data]; + })->toArray(); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldTag.php new file mode 100644 index 00000000..e228c613 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/ResponseFields/GetFromResponseFieldTag.php @@ -0,0 +1,125 @@ +getApiResourceFields($tagsOnMethod); + + return [...$nonApiResourceFields, ...$apiResourceFields]; + } + + public function getClassNameFromApiResourceTag(string $apiResourceTag): string + { + ['content' => $className] = a::parseIntoContentAndFields($apiResourceTag, UseApiResourceTags::apiResourceAllowedFields()); + + return $className; + } + + protected function parseTag(string $tagContent): array + { + // Format: + // @responseField <"required" (optional)> + // Examples: + // @responseField text string required The text. + // @responseField user_id integer The ID of the user. + preg_match('/(.+?)\s+(.+?)\s+(.+?)\s+([\s\S]*)/', $tagContent, $content); + if (empty($content)) { + // This means only name and type were supplied + [$name, $type] = preg_split('/\s+/', $tagContent); + $description = ''; + $required = false; + } else { + [$_, $name, $type, $required, $description] = $content; + if ($required !== 'required') { + $description = $required.' '.$description; + } + + $required = $required === 'required'; + $description = mb_trim($description); + } + + $type = static::normalizeTypeName($type); + $data = compact('name', 'type', 'required', 'description'); + + // Support optional type in annotation + // The type can also be a union or nullable type (eg ?string or string|null) + if (! $this->isSupportedTypeInDocBlocks(explode('|', mb_trim($type, '?'))[0])) { + // Then that wasn't a type, but part of the description + $data['description'] = mb_trim("{$type} {$description}"); + $data['type'] = ''; + + $data['type'] = ResponseFieldTools::inferTypeOfResponseField($data, $this->endpointData); + } + + return $data; + } + + protected function getApiResourceFields(array $tagsOnMethod): array + { + $apiResourceClassName = $this->getApiResourceClassName($tagsOnMethod); + + if (empty($apiResourceClassName)) { + return []; + } + + return $this->extractFieldsFromApiResource($apiResourceClassName); + } + + protected function getApiResourceClassName(array $tagsOnMethod): ?string + { + $apiResourceTags = array_values( + array_filter($tagsOnMethod, function ($tag) { + return in_array(mb_strtolower($tag->getName()), ['apiresource', 'apiresourcecollection']); + }) + ); + + if (empty($apiResourceTags)) { + return null; + } + + return $this->getClassNameFromApiResourceTag($apiResourceTags[0]->getContent()); + } + + protected function extractFieldsFromApiResource(string $className): array + { + $method = u::getReflectedRouteMethod([$className, 'toArray']); + $docBlock = new DocBlock($method->getDocComment() ?: ''); + $tagsOnApiResource = $docBlock->getTags(); + + $wrapKey = $className::$wrap ?? null; + $fields = parent::getFromTags($tagsOnApiResource, []); + + return $this->applyWrapKeyPrefix($fields, $wrapKey); + } + + protected function applyWrapKeyPrefix(array $fields, ?string $wrapKey): array + { + if ($wrapKey === null) { + return $fields; + } + + $wrappedFields = []; + foreach ($fields as $fieldName => $fieldData) { + $fieldData['name'] = $wrapKey.'.'.$fieldData['name']; + $wrappedFields[$fieldData['name']] = $fieldData; + } + + return $wrappedFields; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/ResponseCalls.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/ResponseCalls.php new file mode 100644 index 00000000..bc51dc91 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/ResponseCalls.php @@ -0,0 +1,356 @@ +responses->hasSuccessResponse()) { + return null; + } + + return $this->makeResponseCall($endpointData, $settings); + } + + public function makeResponseCall(ExtractedEndpointData $endpointData, array $settings): ?array + { + $this->configureEnvironment($settings); + + // Mix in parsed parameters with manually specified parameters. + $bodyParameters = array_merge($endpointData->cleanBodyParameters, $settings['bodyParams'] ?? []); + $queryParameters = array_merge($endpointData->cleanQueryParameters, $settings['queryParams'] ?? []); + $urlParameters = $endpointData->cleanUrlParameters; + $headers = $endpointData->headers; + + if ($endpointData->auth) { + [$where, $name, $value] = $endpointData->auth; + + switch ($where) { + case 'queryParameters': + $queryParameters[$name] = $value; + + break; + + case 'bodyParameters': + $bodyParameters[$name] = $value; + + break; + + case 'headers': + $headers[$name] = $value; + + break; + + default: + throw new \InvalidArgumentException("Unknown auth location: {$where}"); + } + } + + $hardcodedFileParams = $settings['fileParams'] ?? []; + $hardcodedFileParams = collect($hardcodedFileParams)->map(function ($filePath) { + $fileName = basename($filePath); + + return new UploadedFile( + $filePath, + $fileName, + mime_content_type($filePath), + test: true + ); + })->toArray(); + $fileParameters = array_merge($endpointData->fileParameters, $hardcodedFileParams); + + $request = $this->prepareRequest( + $endpointData->route, + $endpointData->uri, + $settings, + $urlParameters, + $bodyParameters, + $queryParameters, + $fileParameters, + $headers + ); + + $this->runPreRequestHook($request, $endpointData); + + try { + $response = $this->makeApiCall($request, $endpointData->route); + + $this->runPostRequestHook($request, $endpointData, $response); + + $response = [ + [ + 'status' => $response->getStatusCode(), + 'content' => $this->getContentFromResponse($response), + 'headers' => $this->getResponseHeaders($response), + ], + ]; + } catch (\Exception $e) { + c::warn('Exception thrown during response call for'.$endpointData->name()); + e::dumpExceptionIfVerbose($e); + + $response = null; + } finally { + $this->finish(); + } + + return $response; + } + + public function getMethods(Route $route): array + { + return array_diff($route->methods(), ['HEAD']); + } + + /** + * @param array $only The routes which this strategy should be applied to. Can not be specified with $except. + * Specify route names ("users.index", "users.*"), or method and path ("GET *", "POST /safe/*"). + * @param array $except The routes which this strategy should be applied to. Can not be specified with $only. + * Specify route names ("users.index", "users.*"), or method and path ("GET *", "POST /safe/*"). + * @param array $config any extra Laravel config() values to before starting the response call + * @param array $queryParams Query params to always send with the response call. Key-value array. + * @param array $bodyParams Body params to always send with the response call. Key-value array. + * @param array $fileParams File params to always send with the response call. Key-value array. Key is param name, value is file path. + * @param array $cookies Cookies to always send with the response call. Key-value array. + */ + public static function withSettings( + array $only = [], + array $except = [], + array $config = [], + array $queryParams = [], + array $bodyParams = [], + array $fileParams = [ + // 'key' => 'storage/app/image.png', + ], + array $cookies = [], + ): array { + return static::wrapWithSettings( + only: $only, + except: $except, + otherSettings: compact( + 'config', + 'queryParams', + 'bodyParams', + 'fileParams', + 'cookies', + ) + ); + } + + protected function prepareRequest( + Route $route, + string $url, + array $settings, + array $urlParams, + array $bodyParams, + array $queryParams, + array $fileParameters, + array $headers, + ): Request { + $uri = Utils::getUrlWithBoundParameters($url, $urlParams); + $routeMethods = $this->getMethods($route); + $method = array_shift($routeMethods); + $cookies = $settings['cookies'] ?? []; + + // Note that we initialise the request with the bodyParams here + // and later still add them to the ParameterBag (`addBodyParameters`) + // The first is so the body params get added to the request content + // (where Laravel reads body from) + // The second is so they get added to the request bag + // (where Symfony usually reads from and Laravel sometimes does) + // Adding to both ensures consistency + + // Always use the current app domain for response calls + $rootUrl = config('app.url'); + $request = Request::create( + "{$rootUrl}/{$uri}", + $method, + [], + $cookies, + $fileParameters, + $this->transformHeadersToServerVars($headers), + json_encode($bodyParams) + ); + // Add headers again to catch any ones we didn't transform properly. + $this->addHeaders($request, $route, $headers); + $this->addQueryParameters($request, $queryParams); + $this->addBodyParameters($request, $bodyParams); + + return $request; + } + + protected function runPreRequestHook(Request $request, ExtractedEndpointData $endpointData): void + { + if (is_callable(Globals::$__beforeResponseCall)) { + call_user_func_array(Globals::$__beforeResponseCall, [$request, $endpointData]); + } + } + + protected function runPostRequestHook(Request $request, ExtractedEndpointData $endpointData, mixed $response): void + { + if (is_callable(Globals::$__afterResponseCall)) { + call_user_func_array(Globals::$__afterResponseCall, [$request, $endpointData, $response]); + } + } + + /** + * @return Response + * + * @throws \Exception + */ + protected function makeApiCall(Request $request, Route $route) + { + return $this->callLaravelRoute($request); + } + + protected function callLaravelRoute(Request $request): Response + { + /** @var \Illuminate\Foundation\Http\Kernel $kernel */ + $kernel = app(Kernel::class); + $response = $kernel->handle($request); + $kernel->terminate($request, $response); + + return $response; + } + + /** + * Transform headers array to array of $_SERVER vars with HTTP_* format. + */ + protected function transformHeadersToServerVars(array $headers): array + { + $server = []; + $prefix = 'HTTP_'; + foreach ($headers as $name => $value) { + $name = strtr(mb_strtoupper($name), '-', '_'); + if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') { + $name = $prefix.$name; + } + $server[$name] = $value; + } + + return $server; + } + + protected function getResponseHeaders($response): array + { + $headers = $response->headers->all(); + $formattedHeaders = []; + + foreach ($headers as $header => $values) { + $formattedHeaders[$header] = implode('; ', $values); + } + + return $formattedHeaders; + } + + protected function getContentFromResponse(Response $response): false|string + { + if (! $response instanceof StreamedResponse) { + return $response->getContent(); + } + + // For streamed responses, the content is null, and only output directly via "echo" when we call "sendContent". + // We use output buffering to capture the output into a new fake response. + $renderedResponse = new Response('', $response->getStatusCode()); + $originalCallback = $response->getCallback(); + $response->setCallback(function () use ($originalCallback, $renderedResponse) { + ob_start(function ($output) use ($renderedResponse) { + $renderedResponse->setContent($output); + }); + $originalCallback(); + ob_end_flush(); + }); + $response->sendContent(); + $renderedResponse->headers = $response->headers; + + return $renderedResponse->getContent(); + } + + private function configureEnvironment(array $settings) + { + $this->startDbTransaction(); + $this->setLaravelConfigs($settings['config'] ?? []); + } + + private function setLaravelConfigs(array $config) + { + if (empty($config)) { + return; + } + + foreach ($config as $name => $value) { + $this->previousConfigs[$name] = Config::get($name); + Config::set([$name => $value]); + } + } + + private function rollbackLaravelConfigChanges() + { + foreach ($this->previousConfigs as $name => $value) { + Config::set([$name => $value]); + } + } + + private function finish() + { + $this->endDbTransaction(); + $this->rollbackLaravelConfigChanges(); + } + + private function addHeaders(Request $request, Route $route, ?array $headers): void + { + // Set the proper domain + if ($route->getDomain()) { + $request->headers->add([ + 'HOST' => $route->getDomain(), + ]); + $request->server->add([ + 'HTTP_HOST' => $route->getDomain(), + 'SERVER_NAME' => $route->getDomain(), + ]); + } + + $headers = collect($headers); + + if (($headers->get('Accept') ?: $headers->get('accept')) === 'application/json') { + $request->setRequestFormat('json'); + } + } + + private function addQueryParameters(Request $request, array $query): void + { + $request->query->add($query); + $request->server->add(['QUERY_STRING' => http_build_query($query)]); + } + + private function addBodyParameters(Request $request, array $body): void + { + $request->request->add($body); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseApiResourceTags.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseApiResourceTags.php new file mode 100644 index 00000000..3f470d80 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseApiResourceTags.php @@ -0,0 +1,164 @@ +route)['method']; + + $tags = $methodDocBlock->getTags(); + if (empty($apiResourceTag = $this->getApiResourceTag($tags))) { + return null; + } + + return $this->getApiResourceResponseFromTags($apiResourceTag, $tags, $endpointData); + } + + /** + * Get a response from the @apiResource/@apiResourceCollection, @apiResourceModel and @apiResourceAdditional tags. + * + * @param Tag[] $allTags + * @return null|array[] + * + * @throws \Exception + */ + public function getApiResourceResponseFromTags(Tag $apiResourceTag, array $allTags, ExtractedEndpointData $endpointData): ?array + { + [$statusCode, $description, $apiResourceClass, $isCollection, $extra] = $this->getStatusCodeAndApiResourceClass($apiResourceTag); + [$modelClass, $factoryStates, $relations, $pagination] = $this->getClassToBeTransformedAndAttributes($allTags, $apiResourceClass, $extra); + $additionalData = $this->getAdditionalData($allTags); + + $modelInstantiator = fn () => $this->instantiateExampleModel($modelClass, $factoryStates, $relations); + + $this->startDbTransaction(); + $content = ApiResourceResponseTools::fetch( + $apiResourceClass, + $isCollection, + $modelInstantiator, + $endpointData, + $pagination, + $additionalData, + ); + $this->endDbTransaction(); + + return [ + [ + 'status' => $statusCode ?: 200, + 'description' => $description, + 'content' => $content, + ], + ]; + } + + // These fields were originally only set on @apiResourceModel, but now we also support them on @apiResource + public static function apiResourceExtraFields() + { + return ['states', 'with', 'paginate']; + } + + public static function apiResourceAllowedFields() + { + return ['status', 'scenario', ...static::apiResourceExtraFields()]; + } + + public static function apiResourceModelAllowedFields() + { + return ['states', 'with', 'paginate']; + } + + public function getApiResourceTag(array $tags): ?Tag + { + return Arr::first(Utils::filterDocBlockTags($tags, 'apiresource', 'apiresourcecollection')); + } + + protected function getClassToBeTransformedAndAttributes(array $tags, string $apiResourceClass, array $extra): array + { + $modelTag = Arr::first(Utils::filterDocBlockTags($tags, 'apiresourcemodel')); + + $modelClass = null; + + if ($modelTag) { + ['content' => $modelClass, 'fields' => $fields] = a::parseIntoContentAndFields($modelTag->getContent(), static::apiResourceModelAllowedFields()); + } + + $fields = array_merge($extra, $fields ?? []); + $states = $fields['states'] ? explode(',', $fields['states']) : []; + $relations = $fields['with'] ? explode(',', $fields['with']) : []; + $pagination = $fields['paginate'] ? explode(',', $fields['paginate']) : []; + + if (empty($modelClass)) { + $modelClass = ApiResourceResponseTools::tryToInferApiResourceModel($apiResourceClass); + } + + if (empty($modelClass)) { + c::warn( + <<<'WARN' + Couldn't detect an Eloquent API resource model from your `@apiResource`. + Either specify a model using the `@apiResourceModel` annotation, or add an `@mixin` annotation in your resource's docblock. + WARN + ); + } + + return [$modelClass, $states, $relations, $pagination]; + } + + private function getStatusCodeAndApiResourceClass(Tag $tag): array + { + preg_match('/^(\d{3})?\s?([\s\S]*)$/', $tag->getContent(), $result); + + $status = $result[1] ?: 0; + $content = $result[2]; + + [ + 'fields' => $fields, + 'content' => $content, + ] = a::parseIntoContentAndFields($content, static::apiResourceAllowedFields()); + + $status = $fields['status'] ?: $status; + $apiResourceClass = $content; + $description = $fields['scenario'] ?: ''; + + $isCollection = mb_strtolower($tag->getName()) === 'apiresourcecollection'; + + return [ + (int) $status, + $description, + $apiResourceClass, + $isCollection, + collect($fields)->only(...static::apiResourceExtraFields())->toArray(), + ]; + } + + /** + * Returns data for simulating JsonResource ->additional() function. + * + * @param Tag[] $tags + */ + private function getAdditionalData(array $tags): array + { + $tag = Arr::first(Utils::filterDocBlockTags($tags, 'apiresourceadditional')); + + return $tag ? a::parseIntoFields($tag->getContent()) : []; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseAttributes.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseAttributes.php new file mode 100644 index 00000000..f6e5e88f --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseAttributes.php @@ -0,0 +1,125 @@ + + */ +class UseResponseAttributes extends PhpAttributeStrategy +{ + use DatabaseTransactionHelpers; + use InstantiatesExampleModels; + use ParamHelpers; + + protected static array $attributeNames = [ + Response::class, + ResponseFromFile::class, + ResponseFromApiResource::class, + ResponseFromTransformer::class, + ]; + + protected function extractFromAttributes( + ExtractedEndpointData $endpointData, + array $attributesOnMethod, + array $attributesOnFormRequest = [], + array $attributesOnController = [], + ): ?array { + $responses = []; + foreach ([...$attributesOnController, ...$attributesOnFormRequest, ...$attributesOnMethod] as $attributeInstance) { + // @phpstan-ignore-next-line + $responses[] = match (true) { + $attributeInstance instanceof Response => $attributeInstance->toArray(), + $attributeInstance instanceof ResponseFromFile => $attributeInstance->toArray(), + $attributeInstance instanceof ResponseFromApiResource => $this->getApiResourceResponse($attributeInstance), + $attributeInstance instanceof ResponseFromTransformer => $this->getTransformerResponse($attributeInstance), + }; + } + + return $responses; + } + + protected function getApiResourceResponse(ResponseFromApiResource $attributeInstance) + { + $modelToBeTransformed = $attributeInstance->modelToBeTransformed(); + if (empty($modelToBeTransformed)) { + c::warn( + <<<'WARN' + Couldn't detect an Eloquent API resource model from your ResponseFromApiResource. + Either specify a model using the `model:` parameter, or add an `@mixin` annotation in your resource's docblock. + WARN + ); + $modelInstantiator = null; + } else { + $modelInstantiator = fn () => $this->instantiateExampleModel($modelToBeTransformed, $attributeInstance->factoryStates, $attributeInstance->with, null, $attributeInstance->withCount); + } + + $pagination = []; + if ($attributeInstance->paginate) { + $pagination = [$attributeInstance->paginate]; + } elseif ($attributeInstance->simplePaginate) { + $pagination = [$attributeInstance->simplePaginate, 'simple']; + } elseif ($attributeInstance->cursorPaginate) { + $pagination = [$attributeInstance->cursorPaginate, 'cursor']; + } + + $this->startDbTransaction(); + $content = ApiResourceResponseTools::fetch( + $attributeInstance->name, + $attributeInstance->isCollection(), + $modelInstantiator, + $this->endpointData, + $pagination, + $attributeInstance->additional, + ); + $this->endDbTransaction(); + + return [ + 'status' => $attributeInstance->status, + 'description' => $attributeInstance->description, + 'content' => $content, + ]; + } + + protected function getTransformerResponse(ResponseFromTransformer $attributeInstance) + { + $modelInstantiator = fn () => $this->instantiateExampleModel( + $attributeInstance->model, + $attributeInstance->factoryStates, + $attributeInstance->with, + (new \ReflectionClass($attributeInstance->name))->getMethod('transform') + ); + + $pagination = $attributeInstance->paginate ? [ + 'perPage' => $attributeInstance->paginate[1] ?? null, 'adapter' => $attributeInstance->paginate[0], + ] : []; + $this->startDbTransaction(); + $content = TransformerResponseTools::fetch( + $attributeInstance->name, + $attributeInstance->collection, + $modelInstantiator, + $pagination, + $attributeInstance->resourceKey, + $this->config->get('fractal.serializer'), + ); + $this->endDbTransaction(); + + return [ + 'status' => $attributeInstance->status, + 'description' => $attributeInstance->description, + 'content' => $content, + ]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseFileTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseFileTag.php new file mode 100644 index 00000000..fd606f9b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseFileTag.php @@ -0,0 +1,54 @@ +route); + + return $this->getFileResponses($docBlocks['method']->getTags()); + } + + /** + * @param Tag[] $tags + */ + public function getFileResponses(array $tags): ?array + { + $responseFileTags = Utils::filterDocBlockTags($tags, 'responsefile'); + + if (empty($responseFileTags)) { + return null; + } + + return array_map(function (Tag $responseFileTag) { + preg_match('/^(\d{3})?\s*(.*?)({.*})?$/', $responseFileTag->getContent(), $result); + [$_, $status, $mainContent] = $result; + $json = $result[3] ?? null; + + ['fields' => $fields, 'content' => $filePath] = a::parseIntoContentAndFields($mainContent, ['status', 'scenario']); + + $status = $fields['status'] ?: ($status ?: 200); + $description = $fields['scenario'] ?: ''; + $content = ResponseFileTools::getResponseContents($filePath, $json); + + return [ + 'content' => $content, + 'status' => (int) $status, + 'description' => $description, + ]; + }, $responseFileTags); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseTag.php new file mode 100644 index 00000000..588e98ff --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseResponseTag.php @@ -0,0 +1,50 @@ +route); + + return $this->getDocBlockResponses($docBlocks['method']->getTags()); + } + + /** + * @param Tag[] $tags + */ + public function getDocBlockResponses(array $tags): ?array + { + $responseTags = Utils::filterDocBlockTags($tags, 'response'); + + if (empty($responseTags)) { + return null; + } + + return array_map(function (Tag $responseTag) { + // Status code (optional) followed by response + preg_match('/^(\d{3})?\s?([\s\S]*)$/', $responseTag->getContent(), $result); + + $status = $result[1] ?: 200; + $content = $result[2] ?: '{}'; + + ['fields' => $fields, 'content' => $content] = a::parseIntoContentAndFields($content, ['status', 'scenario']); + + $status = $fields['status'] ?: $status; + $description = $fields['scenario'] ?: ''; + + return ['content' => $content, 'status' => (int) $status, 'description' => $description]; + }, $responseTags); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseTransformerTags.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseTransformerTags.php new file mode 100644 index 00000000..8dbaff0d --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/Responses/UseTransformerTags.php @@ -0,0 +1,132 @@ +route)['method']; + $tags = $methodDocBlock->getTags(); + + return $this->getTransformerResponseFromTags($tags); + } + + /** + * Get a response from the @transformer/@transformerCollection and @transformerModel tags. + * + * @param Tag[] $allTags + */ + public function getTransformerResponseFromTag(Tag $transformerTag, array $allTags): ?array + { + [$statusCode, $transformerClass, $isCollection] = $this->getStatusCodeAndTransformerClass($transformerTag); + [$model, $factoryStates, $relations, $resourceKey] = $this->getClassToBeTransformed($allTags); + + $modelInstantiator = fn () => $this->instantiateExampleModel($model, $factoryStates, $relations, (new \ReflectionClass($transformerClass))->getMethod('transform')); + $pagination = $this->getTransformerPaginatorData($allTags); + $serializer = $this->config->get('fractal.serializer'); + + $this->startDbTransaction(); + $content = TransformerResponseTools::fetch( + $transformerClass, + $isCollection, + $modelInstantiator, + $pagination, + $resourceKey, + $serializer + ); + $this->endDbTransaction(); + + return [ + [ + 'status' => $statusCode ?: 200, + 'content' => $content, + ], + ]; + } + + public function getTransformerResponseFromTags(array $tags): ?array + { + if (empty($transformerTag = $this->getTransformerTag($tags))) { + return null; + } + + return $this->getTransformerResponseFromTag($transformerTag, $tags); + } + + private function getStatusCodeAndTransformerClass(Tag $tag): array + { + preg_match('/^(\d{3})?\s?([\s\S]*)$/', $tag->getContent(), $result); + $status = (int) ($result[1] ?: 200); + $transformerClass = $result[2]; + $isCollection = mb_strtolower($tag->getName()) === 'transformercollection'; + + return [$status, $transformerClass, $isCollection]; + } + + /** + * @throws \Exception + */ + private function getClassToBeTransformed(array $tags): array + { + $modelTag = Arr::first(Utils::filterDocBlockTags($tags, 'transformermodel')); + + $type = null; + $states = []; + $relations = []; + $resourceKey = null; + if ($modelTag) { + ['content' => $type, 'fields' => $fields] = a::parseIntoContentAndFields($modelTag->getContent(), ['states', 'with', 'resourceKey']); + $states = $fields['states'] ? explode(',', $fields['states']) : []; + $relations = $fields['with'] ? explode(',', $fields['with']) : []; + $resourceKey = $fields['resourceKey'] ?? null; + } + + return [$type, $states, $relations, $resourceKey]; + } + + private function getTransformerTag(array $tags): ?Tag + { + return Arr::first(Utils::filterDocBlockTags($tags, 'transformer', 'transformercollection')); + } + + /** + * Gets pagination data from the `@transformerPaginator` tag, like this: + * `@transformerPaginator League\Fractal\Pagination\IlluminatePaginatorAdapter 15` + * + * @param Tag[] $tags + */ + private function getTransformerPaginatorData(array $tags): array + { + $tag = Arr::first(Utils::filterDocBlockTags($tags, 'transformerpaginator')); + if (empty($tag)) { + return ['adapter' => null, 'perPage' => null]; + } + + preg_match('/^\s*(.+?)(\s+\d+)?$/', $tag->getContent(), $result); + $paginatorAdapter = $result[1]; + $perPage = $result[2] ?? null; + if ($perPage) { + $perPage = mb_trim($perPage); + } + + return ['adapter' => $paginatorAdapter, 'perPage' => $perPage ?: null]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/StaticData.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/StaticData.php new file mode 100644 index 00000000..da7d5585 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/StaticData.php @@ -0,0 +1,30 @@ +config; + } + + /** + * Helper method that returns a tuple of [$strategyName, $settingsArray]. + * Main real advantage is that it validates the mutual exclusion of $only and $except. + * + * @param array $only The routes which this strategy should be applied to. Can not be specified with $except. + * Specify route names ("users.index", "users.*"), or method and path ("GET *", "POST /safe/*"). + * @param array $except The routes which this strategy should be applied to. Can not be specified with $only. + * Specify route names ("users.index", "users.*"), or method and path ("GET *", "POST /safe/*"). + * @return array{string,array} tuple of strategy class FQN and specified settings + */ + public static function wrapWithSettings( + array $only = [], + array $except = [], + array $otherSettings = [], + ): array { + return [ + static::class, + ['only' => $only, 'except' => $except, ...$otherSettings], + ]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/TagStrategyWithFormRequestFallback.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/TagStrategyWithFormRequestFallback.php new file mode 100644 index 00000000..ac0f2c8b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/TagStrategyWithFormRequestFallback.php @@ -0,0 +1,46 @@ +endpointData = $endpointData; + + return $this->getParametersFromDocBlockInFormRequestOrMethod($endpointData->route, $endpointData->method); + } + + public function getParametersFromDocBlockInFormRequestOrMethod(Route $route, \ReflectionFunctionAbstract $method): array + { + $classTags = RouteDocBlocker::getDocBlocksFromRoute($route)['class']?->getTags() ?: []; + // If there's a FormRequest, w.e check there for tags. + if ($formRequestClass = $this->getFormRequestReflectionClass($method)) { + $formRequestDocBlock = new DocBlock($formRequestClass->getDocComment()); + $parametersFromFormRequest = $this->getFromTags($formRequestDocBlock->getTags(), $classTags); + + if (count($parametersFromFormRequest)) { + return $parametersFromFormRequest; + } + } + + $methodDocBlock = RouteDocBlocker::getDocBlocksFromRoute($route)['method']; + + return $this->getFromTags($methodDocBlock->getTags(), $classTags); + } + + /** + * @param Tag[] $tagsOnMethod + * @param Tag[] $tagsOnClass + */ + abstract public function getFromTags(array $tagsOnMethod, array $tagsOnClass = []): array; +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromLaravelAPI.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromLaravelAPI.php new file mode 100644 index 00000000..32a36773 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromLaravelAPI.php @@ -0,0 +1,225 @@ +uri; + preg_match_all('/\{(.*?)\}/', $path, $matches); + + foreach ($matches[1] as $match) { + $isOptional = Str::endsWith($match, '?'); + $name = mb_rtrim($match, '?'); + + $parameters[$name] = [ + 'name' => $name, + 'description' => $this->inferUrlParamDescription($endpointData->uri, $name), + 'required' => ! $isOptional, + ]; + } + + $parameters = $this->inferBetterTypesAndExamplesForEloquentUrlParameters($parameters, $endpointData); + $parameters = $this->inferBetterTypesAndExamplesForEnumUrlParameters($parameters, $endpointData); + + return $this->setTypesAndExamplesForOthers($parameters, $endpointData); + } + + protected function inferUrlParamDescription(string $url, string $paramName): string + { + // If $url is sth like /users/{id}, return "The ID of the user." + // If $url is sth like /anything/{user_id}, return "The ID of the user." + + $strategies = collect(['id', 'slug'])->map(function ($name) { + $friendlyName = $name === 'id' ? 'ID' : $name; + + return function ($url, $paramName) use ($name, $friendlyName) { + if ($paramName === $name) { + $thing = $this->getNameOfUrlThing($url, $paramName); + + return "The {$friendlyName} of the {$thing}."; + } + if (Str::is("*_{$name}", $paramName)) { + $thing = str_replace(['_', '-'], ' ', str_replace("_{$name}", '', $paramName)); + + return "The {$friendlyName} of the {$thing}."; + } + }; + })->toArray(); + + // If $url is sth like /categories/{category}, return "The category." + $strategies[] = function ($url, $paramName) { + $thing = $this->getNameOfUrlThing($url, $paramName); + if ($thing === $paramName) { + return "The {$thing}."; + } + }; + + foreach ($strategies as $strategy) { + if ($inferred = $strategy($url, $paramName)) { + return $inferred; + } + } + + return ''; + } + + protected function inferBetterTypesAndExamplesForEloquentUrlParameters(array $parameters, ExtractedEndpointData $endpointData): array + { + // We'll gather Eloquent model instances that can be linked to a URl parameter + $modelInstances = []; + + // First, any bound models + // Eg if route is /users/{id}, and (User $user) model is typehinted on method + // If User model has `id` as an integer, then {id} param should be an integer + $typeHintedEloquentModels = UrlParamsNormalizer::getTypeHintedEloquentModels($endpointData->method); + foreach ($typeHintedEloquentModels as $argumentName => $modelInstance) { + $routeKey = $modelInstance->getRouteKeyName(); + + // Find the param name. In our normalized URL, argument $user might be param {user}, or {user_id}, or {id}, + if (isset($parameters[$argumentName])) { + $paramName = $argumentName; + } elseif (isset($parameters["{$argumentName}_{$routeKey}"])) { + $paramName = "{$argumentName}_{$routeKey}"; + } elseif (isset($parameters[$routeKey])) { + $paramName = $routeKey; + } else { + continue; + } + + $modelInstances[$paramName] = $modelInstance; + } + + // Next, non-Eloquent-bound parameters. They might still be Eloquent models, but model binding wasn't used. + foreach ($parameters as $name => $data) { + if (isset($data['type'])) { + continue; + } + + // If the url is /things/{id}, try to find a Thing model + $urlThing = $this->getNameOfUrlThing($endpointData->uri, $name); + if ($urlThing && ($modelInstance = $this->findModelFromUrlThing($urlThing))) { + $modelInstances[$name] = $modelInstance; + } + } + + // Now infer. + foreach ($modelInstances as $paramName => $modelInstance) { + // If the routeKey is the same as the primary key in the database, use the PK's type. + $routeKey = $modelInstance->getRouteKeyName(); + $type = $modelInstance->getKeyName() === $routeKey + ? static::normalizeTypeName($modelInstance->getKeyType()) : 'string'; + + $parameters[$paramName]['type'] = $type; + + try { + $parameters[$paramName]['example'] = $modelInstance::first()->{$routeKey} ?? null; + } catch (\Throwable) { + $parameters[$paramName]['example'] = null; + } + } + + return $parameters; + } + + protected function inferBetterTypesAndExamplesForEnumUrlParameters(array $parameters, ExtractedEndpointData $endpointData): array + { + $typeHintedEnums = UrlParamsNormalizer::getTypeHintedEnums($endpointData->method); + foreach ($typeHintedEnums as $argumentName => $enum) { + $parameters[$argumentName]['type'] = static::normalizeTypeName($enum->getBackingType()); + + try { + $parameters[$argumentName]['example'] = $enum->getCases()[0]->getBackingValue(); + } catch (\Throwable) { + $parameters[$argumentName]['example'] = null; + } + } + + return $parameters; + } + + protected function setTypesAndExamplesForOthers(array $parameters, ExtractedEndpointData $endpointData): array + { + foreach ($parameters as $name => $parameter) { + if (empty($parameter['type'])) { + $parameters[$name]['type'] = 'string'; + } + + if (($parameter['example'] ?? null) === null) { + // If the user explicitly set a `where()` constraint, use that to refine examples + $parameterRegex = $endpointData->route->wheres[$name] ?? null; + $parameters[$name]['example'] = $parameterRegex + ? $this->castToType($this->getFaker()->regexify($parameterRegex), $parameters[$name]['type']) + : $this->generateDummyValue($parameters[$name]['type'], hints: ['name' => $name]); + } + } + + return $parameters; + } + + /** + * Given a URL parameter $paramName, extract the "thing" that comes before it. eg:: + * - //things/{paramName} -> "thing" + * - animals/cats/{id} -> "cat" + * - users/{user_id}/contracts -> "user" + * + * @param null|string $alternateParamName a second paramName to try, if the original paramName isn't in the URL + */ + protected function getNameOfUrlThing(string $url, string $paramName, ?string $alternateParamName = null): ?string + { + $parts = explode('/', $url); + if (count($parts) === 1) { + return null; + } // URL was "/{thing}" + + $paramIndex = array_search("{{$paramName}}", $parts); + + if ($paramIndex === false) { + $paramIndex = array_search("{{$alternateParamName}}", $parts); + } + + if ($paramIndex === false || $paramIndex === 0) { + return null; + } + + $things = $parts[$paramIndex - 1]; + + // Replace underscores/hyphens, so "side_projects" becomes "side project" + return str_replace(['_', '-'], ' ', Str::singular($things)); + } + + /** + * Given a URL "thing", like the "cat" in /cats/{id}, try to locate a Cat model. + */ + protected function findModelFromUrlThing(string $urlThing): ?Model + { + $className = str_replace(['-', '_', ' '], '', Str::title($urlThing)); + $rootNamespace = app()->getNamespace(); + + if (class_exists($class = "{$rootNamespace}Models\\".$className, autoload: false) + // For the heathens that don't use a Models\ directory + || class_exists($class = $rootNamespace.$className, autoload: false)) { + try { + $instance = new $class; + } catch (\Error) { // It might be an enum or some other non-instantiable class + return null; + } + + return $instance instanceof Model ? $instance : null; + } + + return null; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamAttribute.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamAttribute.php new file mode 100644 index 00000000..bdc685d1 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamAttribute.php @@ -0,0 +1,14 @@ + + */ +class GetFromUrlParamAttribute extends GetParamsFromAttributeStrategy +{ + protected static array $attributeNames = [UrlParam::class]; +} diff --git a/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php new file mode 100644 index 00000000..f334360a --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Extracting/Strategies/UrlParameters/GetFromUrlParamTag.php @@ -0,0 +1,54 @@ + <"required" (optional)> + // Examples: + // @urlParam id string required The id of the post. + // @urlParam user_id The ID of the user. + + // We match on all the possible types for URL parameters. It's a limited range, so no biggie. + preg_match('/(\w+?)\s+((int|integer|string|float|double|number)\s+)?(required\s+)?([\s\S]*)/', $tagContent, $content); + if (empty($content)) { + // This means only name was supplied + $name = $tagContent; + $required = false; + $description = ''; + $type = 'string'; + } else { + [$_, $name, $__, $type, $required, $description] = $content; + $description = mb_trim(str_replace(['No-example.', 'No-example'], '', $description)); + if ($description === 'required') { + $required = true; + $description = ''; + } else { + $required = mb_trim($required) === 'required'; + } + + if (empty($type) && $this->isSupportedTypeInDocBlocks($description)) { + // Only type was supplied + $type = $description; + $description = ''; + } + + $type = empty($type) + ? (Str::contains($description, ['number', 'count', 'page']) ? 'integer' : 'string') + : static::normalizeTypeName($type); + } + + [$description, $example, $enumValues, $exampleWasSpecified] + = $this->getDescriptionAndExample($description, $type, $tagContent, $name); + + return compact('name', 'description', 'required', 'example', 'type', 'enumValues', 'exampleWasSpecified'); + } +} diff --git a/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsContract.php b/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsContract.php new file mode 100644 index 00000000..dd4609a5 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsContract.php @@ -0,0 +1,10 @@ +isForcing()) { + return static::fromApp( + command: $command, + routeMatcher: $routeMatcher, + preserveUserChanges: false, + paths: $paths + ); + } + + if ($command->shouldExtract()) { + return static::fromApp( + command: $command, + routeMatcher: $routeMatcher, + preserveUserChanges: true, + paths: $paths + ); + } + + return static::fromCamelDir($paths); + } + + public static function fromApp( + GenerateDocumentation $command, + RouteMatcherInterface $routeMatcher, + bool $preserveUserChanges, + PathConfig $paths, + ): GroupedEndpointsFromApp { + return new GroupedEndpointsFromApp($command, $routeMatcher, $paths, $preserveUserChanges); + } + + public static function fromCamelDir(PathConfig $paths): GroupedEndpointsFromCamelDir + { + return new GroupedEndpointsFromCamelDir($paths); + } +} diff --git a/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromApp.php b/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromApp.php new file mode 100644 index 00000000..19fda342 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromApp.php @@ -0,0 +1,309 @@ +docConfig = $command->getDocConfig(); + + static::$camelDir = Camel::camelDir($this->paths); + static::$cacheDir = Camel::cacheDir($this->paths); + } + + public function get(): array + { + $groupedEndpoints = $this->extractEndpointsInfoAndWriteToDisk($this->routeMatcher, $this->preserveUserChanges); + $this->extractAndWriteApiDetailsToDisk(); + + return $groupedEndpoints; + } + + public function hasEncounteredErrors(): bool + { + return $this->encounteredErrors; + } + + protected function extractEndpointsInfoAndWriteToDisk(RouteMatcherInterface $routeMatcher, bool $preserveUserChanges): array + { + $latestEndpointsData = []; + $cachedEndpoints = []; + + if ($preserveUserChanges && is_dir(static::$camelDir) && is_dir(static::$cacheDir)) { + $latestEndpointsData = Camel::loadEndpointsToFlatPrimitivesArray(static::$camelDir); + $cachedEndpoints = Camel::loadEndpointsToFlatPrimitivesArray(static::$cacheDir); + } + + $routes = $routeMatcher->getRoutes($this->docConfig->get('routes', [])); + $endpoints = $this->extractEndpointsInfoFromLaravelApp($routes, $cachedEndpoints, $latestEndpointsData); + + $groupedEndpoints = collect($endpoints)->groupBy('metadata.groupName')->map(function (Collection $endpointsInGroup) { + return [ + 'name' => $endpointsInGroup->first(function (ExtractedEndpointData $endpointData) { + return ! empty($endpointData->metadata->groupName); + })->metadata->groupName ?? '', + 'description' => $endpointsInGroup->first(function (ExtractedEndpointData $endpointData) { + return ! empty($endpointData->metadata->groupDescription); + })->metadata->groupDescription ?? '', + 'endpoints' => $endpointsInGroup->toArray(), + ]; + })->all(); + $this->writeEndpointsToDisk($groupedEndpoints); + + return $groupedEndpoints; + } + + protected function writeEndpointsToDisk(array $grouped): void + { + Utils::deleteFilesMatching(static::$camelDir, function ($file) { + /** @var array|\League\Flysystem\StorageAttributes $file */ + return ! Str::startsWith(basename($file['path']), 'custom.'); + }); + Utils::deleteDirectoryAndContents(static::$cacheDir); + + if (! is_dir(static::$camelDir)) { + mkdir(static::$camelDir, 0o777, true); + } + + if (! is_dir(static::$cacheDir)) { + mkdir(static::$cacheDir, 0o777, true); + } + + $fileNameIndex = 0; + foreach ($grouped as $group) { + $yaml = Yaml::dump( + $group, + 20, + 2, + Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK + ); + + // Format numbers as two digits so they are sorted properly when retrieving later + // (ie "10.yaml" comes after "9.yaml", not after "1.yaml") + $fileName = sprintf('%02d.yaml', $fileNameIndex); + $fileNameIndex++; + + file_put_contents(static::$camelDir."/{$fileName}", $yaml); + file_put_contents(static::$cacheDir."/{$fileName}", "## Autogenerated by Scribe. DO NOT MODIFY.\n\n".$yaml); + } + } + + protected function extractAndWriteApiDetailsToDisk(): void + { + $apiDetails = $this->makeApiDetails(); + + $apiDetails->writeMarkdownFiles(); + } + + protected function makeApiDetails(): ApiDetails + { + return new ApiDetails($this->paths, $this->docConfig, ! $this->command->option('force')); + } + + /** + * Make a new extractor. + */ + protected function makeExtractor(): Extractor + { + return new Extractor($this->docConfig); + } + + /** + * @param MatchedRoute[] $matches + * + * @throws \Exception + */ + private function extractEndpointsInfoFromLaravelApp(array $matches, array $cachedEndpoints, array $latestEndpointsData): array + { + $extractor = $this->makeExtractor(); + + $parsedEndpoints = []; + + foreach ($matches as $routeItem) { + $route = $routeItem->getRoute(); + + $routeControllerAndMethod = u::getRouteClassAndMethodNames($route); + if (! $this->isValidRoute($routeControllerAndMethod)) { + c::warn('Skipping invalid route: '.c::getRouteRepresentation($route)); + + continue; + } + + if (! $this->doesControllerMethodExist($routeControllerAndMethod)) { + c::warn('Skipping route: '.c::getRouteRepresentation($route).' - Controller method does not exist.'); + + continue; + } + + if ($this->isRouteHiddenFromDocumentation($routeControllerAndMethod)) { + c::warn('Skipping route: '.c::getRouteRepresentation($route).': @hideFromAPIDocumentation was specified.'); + + continue; + } + + try { + // Start buffering warnings so they don't break the task output. + c::startWarningBuffer(); + + $this->command->outputComponents()->task( + c::getRouteRepresentation($route), + function () use ($extractor, $route, $routeItem, $cachedEndpoints, $latestEndpointsData, &$parsedEndpoints) { + $currentEndpointData = $extractor->processRoute($route, $routeItem->getRules()); + // If latest data is different from cached data, merge latest into current. + $currentEndpointData = $this->mergeAnyEndpointDataUpdates($currentEndpointData, $cachedEndpoints, $latestEndpointsData); + $parsedEndpoints[] = $currentEndpointData; + + return true; + } + ); + + // Flush buffered warnings after a task completes. + c::flushWarningBuffer(); + } catch (\Exception $exception) { + $this->encounteredErrors = true; + c::flushWarningBuffer(); // Flush warnings even in error. + e::dumpExceptionIfVerbose($exception); + } + } + + return $parsedEndpoints; + } + + /** + * @param array[] $cachedEndpoints + * @param array[] $latestEndpointsData + * @return ExtractedEndpointData The extracted endpoint data + */ + private function mergeAnyEndpointDataUpdates(ExtractedEndpointData $endpointData, array $cachedEndpoints, array $latestEndpointsData): ExtractedEndpointData + { + // First, find the corresponding endpoint in cached and latest + $thisEndpointCached = Arr::first($cachedEndpoints, function (array $endpoint) use ($endpointData) { + return $endpoint['uri'] === $endpointData->uri && $endpoint['httpMethods'] === $endpointData->httpMethods; + }); + if (! $thisEndpointCached) { + return $endpointData; + } + + $thisEndpointLatest = Arr::first($latestEndpointsData, function (array $endpoint) use ($endpointData) { + return $endpoint['uri'] === $endpointData->uri && $endpoint['httpMethods'] === $endpointData->httpMethods; + }); + if (! $thisEndpointLatest) { + return $endpointData; + } + + // Then compare cached and latest to see what sections changed. + $properties = [ + 'metadata', + 'headers', + 'urlParameters', + 'queryParameters', + 'bodyParameters', + 'responses', + 'responseFields', + ]; + + $changed = []; + foreach ($properties as $property) { + if ($thisEndpointCached[$property] !== $thisEndpointLatest[$property]) { + $changed[] = $property; + } + } + + // Finally, merge any changed sections. + $thisEndpointLatest = OutputEndpointData::create($thisEndpointLatest); + foreach ($changed as $property) { + $endpointData->{$property} = $thisEndpointLatest->{$property}; + } + + return $endpointData; + } + + private function isValidRoute(?array $routeControllerAndMethod): bool + { + if (is_array($routeControllerAndMethod)) { + if (count($routeControllerAndMethod) < 2) { + throw CouldntGetRouteDetails::new(); + } + [$classOrObject, $method] = $routeControllerAndMethod; + if (u::isInvokableObject($classOrObject)) { + return true; + } + $routeControllerAndMethod = $classOrObject.'@'.$method; + } + + return ! is_callable($routeControllerAndMethod) && ! is_null($routeControllerAndMethod); + } + + private function doesControllerMethodExist(array $routeControllerAndMethod): bool + { + if (count($routeControllerAndMethod) < 2) { + throw CouldntGetRouteDetails::new(); + } + [$class, $method] = $routeControllerAndMethod; + $reflection = new \ReflectionClass($class); + + if ($reflection->hasMethod($method)) { + return true; + } + + return false; + } + + private function isRouteHiddenFromDocumentation(array $routeControllerAndMethod): bool + { + if (! ($class = $routeControllerAndMethod[0]) instanceof \Closure) { + $classDocBlock = new DocBlock((new \ReflectionClass($class))->getDocComment() ?: ''); + $shouldIgnoreClass = collect($classDocBlock->getTags()) + ->filter(function (Tag $tag) { + return Str::lower($tag->getName()) === 'hidefromapidocumentation'; + })->isNotEmpty(); + + if ($shouldIgnoreClass) { + return true; + } + } + + $methodDocBlock = new DocBlock(u::getReflectedRouteMethod($routeControllerAndMethod)->getDocComment() ?: ''); + $shouldIgnoreMethod = collect($methodDocBlock->getTags()) + ->filter(function (Tag $tag) { + return Str::lower($tag->getName()) === 'hidefromapidocumentation'; + })->isNotEmpty(); + + return $shouldIgnoreMethod; + } +} diff --git a/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php b/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php new file mode 100644 index 00000000..989bfe9b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/GroupedEndpoints/GroupedEndpointsFromCamelDir.php @@ -0,0 +1,27 @@ +paths))) { + throw new \InvalidArgumentException( + "Can't use --no-extraction because there are no endpoints in the ".Camel::camelDir($this->paths).' directory.' + ); + } + + return Camel::loadEndpointsIntoGroups(Camel::camelDir($this->paths)); + } + + public function hasEncounteredErrors(): bool + { + return false; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Matching/MatchedRoute.php b/vendor/knuckleswtf/scribe/src/Matching/MatchedRoute.php new file mode 100644 index 00000000..aecf77af --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Matching/MatchedRoute.php @@ -0,0 +1,50 @@ +route = $route; + $this->rules = $applyRules; + } + + public function getRoute(): Route + { + return $this->route; + } + + /** @deprecated Use the strategy config instead */ + public function getRules(): array + { + return $this->rules; + } + + public function offsetExists($offset): bool + { + return is_callable([$this, 'get'.ucfirst($offset)]); + } + + public function offsetGet($offset): mixed + { + return call_user_func([$this, 'get'.ucfirst($offset)]); + } + + public function offsetSet($offset, $value): void + { + $this->{$offset} = $value; + } + + public function offsetUnset($offset): void + { + $this->{$offset} = null; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Matching/RouteMatcher.php b/vendor/knuckleswtf/scribe/src/Matching/RouteMatcher.php new file mode 100644 index 00000000..77cdc87f --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Matching/RouteMatcher.php @@ -0,0 +1,72 @@ +getRoutesToBeDocumented($routeRules); + } + + private function getRoutesToBeDocumented(array $routeRules): array + { + $allRoutes = $this->getAllRoutes(); + + $matchedRoutes = []; + + foreach ($routeRules as $routeRule) { + $includes = $routeRule['include'] ?? []; + + foreach ($allRoutes as $route) { + if ($this->shouldExcludeRoute($route, $routeRule)) { + continue; + } + + if ($this->shouldIncludeRoute($route, $routeRule, $includes)) { + $matchedRoutes[] = new MatchedRoute($route, $routeRule['apply'] ?? []); + } + } + } + + return $matchedRoutes; + } + + private function getAllRoutes() + { + return RouteFacade::getRoutes(); + } + + private function shouldIncludeRoute(Route $route, array $routeRule, array $mustIncludes): bool + { + if (RoutePatternMatcher::matches($route, $mustIncludes)) { + return true; + } + + $domainsToMatch = $routeRule['match']['domains'] ?? []; + $pathsToMatch = $routeRule['match']['prefixes'] ?? []; + + return Str::is($domainsToMatch, $route->getDomain()) && Str::is($pathsToMatch, $route->uri()); + } + + private function shouldExcludeRoute(Route $route, array $routeRule): bool + { + $excludes = $routeRule['exclude'] ?? []; + + // Exclude this package's routes + $excludes[] = 'scribe'; + $excludes[] = 'scribe.*'; + + // Exclude Laravel Telescope routes + if (class_exists('Laravel\Telescope\Telescope')) { + $excludes[] = 'telescope/*'; + } + + return RoutePatternMatcher::matches($route, $excludes); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Matching/RouteMatcherInterface.php b/vendor/knuckleswtf/scribe/src/Matching/RouteMatcherInterface.php new file mode 100644 index 00000000..f653a93e --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Matching/RouteMatcherInterface.php @@ -0,0 +1,14 @@ + '/absolute/path/to/postman/collection', + * 'openapi' => '/absolute/path/to/openapi/spec', + * // If you're using `laravel` type, `html` will be null, and vice versa for `blade`. + * 'html' => '/absolute/path/to/index.html/', + * 'blade' => '/absolute/path/to/blade/view', + * // These are paths to asset folders + * 'assets' => [ + * 'js' => '/path/to/js/assets/folder', + * 'css' => '/path/to/css/assets/folder', + * 'images' => '/path/to/images/assets/folder', + * ] + * ]. + * + * If you disabled `postman` or `openapi`, their values will be null. + * + * @param callable(array): mixed $callable + */ + public static function afterGenerating(callable $callable) + { + Globals::$__afterGenerating = $callable; + } + + /** + * Specify a callback that will be used by all FormRequest strategies + * to instantiate Form Requests. his callback takes the name of the form request class, + * the current Laravel route being processed, and the controller method. + * + * @param ?callable(string,Route,\ReflectionFunctionAbstract): mixed $callable + */ + public static function instantiateFormRequestUsing(?callable $callable) + { + Globals::$__instantiateFormRequestUsing = $callable; + } + + /** + * Specify a callback that will be called when instantiating an `ExtractedEndpointData` object + * in order to normalize the URL. The default normalization tries to convert URL parameters from + * Laravel resource-style (`users/{user}/projects/{project}`) + * to a general style (`users/{user_id}/projects/{id}`). + * The callback will be passed the default Laravel URL, the route object, the controller method and class. + * + * @param ?callable(string,Route,\ReflectionFunctionAbstract,?\ReflectionClass): string $callable + */ + public static function normalizeEndpointUrlUsing(?callable $callable) + { + Globals::$__normalizeEndpointUrlUsing = $callable; + } +} diff --git a/vendor/knuckleswtf/scribe/src/ScribeServiceProvider.php b/vendor/knuckleswtf/scribe/src/ScribeServiceProvider.php new file mode 100644 index 00000000..7c397439 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/ScribeServiceProvider.php @@ -0,0 +1,119 @@ +registerViews(); + + $this->registerConfig(); + + $this->bootRoutes(); + + $this->registerCommands(); + + $this->configureTranslations(); + + // Bind the route matcher implementation + $this->app->bind(RouteMatcherInterface::class, config('scribe.routeMatcher', RouteMatcher::class)); + + if (! class_exists('Str')) { + // We don't want to have to use the FQN in our blade files. + class_alias(Str::class, 'Str'); + } + } + + // Allows our custom translation layer to be loaded on demand, + // so we minimize issues with interference from framework/package/environment. + // ALso, Laravel's `app->runningInConsole()` isn't reliable enough. See issue #676 + public function loadCustomTranslationLayer(): void + { + $this->app->extend('translation.loader', function ($defaultFileLoader) { + return app(CustomTranslationsLoader::class, ['loader' => $defaultFileLoader]); + }); + $this->app->forgetInstance('translator'); + self::$customTranslationLayerLoaded = true; + } + + /** + * Add docs routes for users that want their docs to pass through their Laravel app. + */ + protected function bootRoutes() + { + $docsType = config('scribe.type', 'laravel'); + if (Str::endsWith($docsType, 'laravel') && config('scribe.laravel.add_routes', true)) { + $routesPath = __DIR__.'/../routes/laravel.php'; + $this->loadRoutesFrom($routesPath); + } + } + + protected function configureTranslations(): void + { + $this->publishes([ + __DIR__.'/../lang/' => $this->app->langPath(), + ], 'scribe-translations'); + + $this->loadTranslationsFrom($this->app->langPath('scribe.php'), 'scribe'); + $this->loadTranslationsFrom(realpath(__DIR__.'/../lang'), 'scribe'); + } + + protected function registerViews(): void + { + // Register custom Markdown Blade compiler so we can automatically have MD views converted to HTML + $this->app->view->getEngineResolver() + ->register('blademd', fn () => new BladeMarkdownEngine($this->app['blade.compiler'])); + $this->app->view->addExtension('md.blade.php', 'blademd'); + + $this->loadViewsFrom(__DIR__.'/../resources/views/', 'scribe'); + + // Publish views in separate, smaller groups for ease of end-user modifications + $viewGroups = [ + 'views' => '', + 'examples' => 'partials/example-requests', + 'themes' => 'themes', + 'markdown' => 'markdown', + 'external' => 'external', + ]; + foreach ($viewGroups as $group => $path) { + $this->publishes([ + __DIR__."/../resources/views/{$path}" => $this->app->basePath("resources/views/vendor/scribe/{$path}"), + ], "scribe-{$group}"); + } + } + + protected function registerConfig(): void + { + $this->publishes([ + __DIR__.'/../config/scribe.php' => $this->app->configPath('scribe.php'), + ], 'scribe-config'); + + $this->mergeConfigFrom(__DIR__.'/../config/scribe.php', 'scribe'); + } + + protected function registerCommands(): void + { + if ($this->app->runningInConsole()) { + $this->commands([ + GenerateDocumentation::class, + MakeStrategy::class, + // Retired for the same reasons as the upgrade check + // Upgrade::class, + DiffConfig::class, + ]); + } + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/AnnotationParser.php b/vendor/knuckleswtf/scribe/src/Tools/AnnotationParser.php new file mode 100644 index 00000000..67de85b2 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/AnnotationParser.php @@ -0,0 +1,57 @@ + mb_trim($annotationContent), + 'fields' => $parsedFields, + ]; + } + + /** + * Parse an annotation like 'title=This message="everything good"' into a key-value array. + * All non key-value fields will be ignored. Useful for `@apiResourceAdditional`, + * where users may specify arbitrary fields. + */ + public static function parseIntoFields(string $annotationContent): array + { + $fields = $matches = []; + + preg_match_all( + '/([^\s\'"]+|".+?"|\'.+?\')=([^\s\'"]+|".+?"|\'.+?\')/', + $annotationContent, + $matches, + PREG_SET_ORDER, + ); + + foreach ($matches as $match) { + $fields[mb_trim($match[1], '"\' ')] = mb_trim($match[2], '"\' '); + } + + return $fields; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/BladeMarkdownEngine.php b/vendor/knuckleswtf/scribe/src/Tools/BladeMarkdownEngine.php new file mode 100644 index 00000000..adcfceb5 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/BladeMarkdownEngine.php @@ -0,0 +1,30 @@ +markdown = \Parsedown::instance(); + } + + /** + * Get the evaluated contents of the view. + * + * @param mixed $path + */ + public function get($path, array $data = []) + { + $contents = parent::get($path, $data); + + return $this->markdown->text($contents); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/ConfigDiffer.php b/vendor/knuckleswtf/scribe/src/Tools/ConfigDiffer.php new file mode 100644 index 00000000..81782b1d --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/ConfigDiffer.php @@ -0,0 +1,99 @@ +recursiveItemDiff($this->original, $this->changed); + } + + protected function recursiveItemDiff($old, $new, $prefix = '') + { + $diff = []; + + foreach ($new as $key => $value) { + $fullKey = $prefix.$key; + if (Str::is($this->ignorePaths, $fullKey)) { + continue; + } + + $oldValue = data_get($old, $key); + + if (is_array($value)) { + if (Str::is($this->asList, $fullKey)) { + $listDiff = $this->diffList($oldValue, $value); + if (! empty($listDiff)) { + $diff[$fullKey] = $listDiff; + } + } else { + $diff = array_merge( + $diff, + $this->recursiveItemDiff($oldValue, $value, "{$fullKey}.") + ); + } + } else { + if ($oldValue !== $value) { + $printedValue = json_encode($value, JSON_UNESCAPED_SLASHES); + $diff[$prefix.$key] = $printedValue; + } + } + } + + return $diff; + } + + protected function diffList(mixed $oldValue, array $value) + { + if (! is_array($oldValue)) { + return 'changed to a list'; + } + + $added = array_map(fn ($v) => "{$v}", $this->subtractArraysFlat($value, $oldValue)); + $removed = array_map(fn ($v) => "{$v}", $this->subtractArraysFlat($oldValue, $value)); + + $diff = []; + if (! empty($added)) { + $diff[] = 'added '.implode(', ', $added); + } + if (! empty($removed)) { + $diff[] = 'removed '.implode(', ', $removed); + } + + return empty($diff) ? '' : implode(': ', $diff); + } + + /** + * Basically array_diff, but handling items which may also be arrays. + */ + protected function subtractArraysFlat(array $a, array $b) + { + $mapped_a = array_map(function ($item) { + if (is_array($item)) { + return VarExporter::export($item); + } + + return $item; + }, $a); + $mapped_b = array_map(function ($item) { + if (is_array($item)) { + return VarExporter::export($item); + } + + return $item; + }, $b); + + return array_diff($mapped_a, $mapped_b); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/ConsoleOutputUtils.php b/vendor/knuckleswtf/scribe/src/Tools/ConsoleOutputUtils.php new file mode 100644 index 00000000..4aa49dc4 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/ConsoleOutputUtils.php @@ -0,0 +1,174 @@ +outputComponents()->task($description, function () use (&$result, $task) { + $result = $task(); + }); + + return $result; + } + + // Fallback for contexts without command + self::info($description); + $result = $task(); + self::success($description); + + return $result; + } + + public static function deprecated(string $feature, string $inVersion, ?string $should = null): void + { + $message = "You're using {$feature}. This is deprecated and will be removed in the next major version."; + if ($should) { + $message .= "\nYou should {$should} instead."; + } + $message .= " See the changelog for details (v{$inVersion})."; + + self::warn($message); + } + + public static function warn(string $message): void + { + if (self::$isBufferingWarnings) { + self::$warningBuffer[] = $message; + + return; + } + + if (self::$command) { + self::$command->outputComponents()->warn($message); + + return; + } + + if (! self::$output) { + self::bootstrapOutput(new ConsoleOutput); + } + self::$output->writeln(" ⚠ {$message}"); + } + + public static function info(string $message): void + { + if (self::$command) { + self::$command->outputComponents()->info($message); + + return; + } + + if (! self::$output) { + self::bootstrapOutput(new ConsoleOutput); + } + self::$output->writeln(" ℹ {$message}"); + } + + public static function debug(string $message): void + { + if (! Globals::$shouldBeVerbose) { + return; + } + + if (! self::$output) { + self::bootstrapOutput(new ConsoleOutput); + } + self::$output->writeln(" 🐛 {$message}"); + } + + public static function success(string $message): void + { + if (! self::$output) { + self::bootstrapOutput(new ConsoleOutput); + } + self::$output->writeln(" ✔ {$message}"); + } + + public static function error(string $message): void + { + if (self::$command) { + self::$command->outputComponents()->error($message); + + return; + } + + if (! self::$output) { + self::bootstrapOutput(new ConsoleOutput); + } + self::$output->writeln(" ✖ {$message}"); + } + + /** + * Return a string representation of a route to output to the console eg [GET] /api/users. + */ + public static function getRouteRepresentation(Route $route): string + { + $methods = $route->methods(); + if (count($methods) > 1) { + $methods = array_diff($route->methods(), ['HEAD']); + } + + $routeMethods = implode('|', $methods); + $routePath = $route->uri(); + + return "[{$routeMethods}] {$routePath}"; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/DocumentationConfig.php b/vendor/knuckleswtf/scribe/src/Tools/DocumentationConfig.php new file mode 100644 index 00000000..9cdbd136 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/DocumentationConfig.php @@ -0,0 +1,42 @@ +data = $config; + } + + /** + * Get a config item with dot notation. + * If the key does not exist, $default (or null) will be returned. + * + * @param mixed $default + * @return array|mixed + */ + public function get(string $key, $default = null) + { + return data_get($this->data, $key, $default); + } + + public function outputIsStatic(): bool + { + return ! $this->outputRoutedThroughLaravel(); + } + + public function outputRoutedThroughLaravel(): bool + { + return Str::is(['laravel', 'external_laravel'], $this->get('type')); + } + + public function outputIsExternal(): bool + { + return Str::is(['external_static', 'external_laravel'], $this->get('type')); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/ErrorHandlingUtils.php b/vendor/knuckleswtf/scribe/src/Tools/ErrorHandlingUtils.php new file mode 100644 index 00000000..b7252067 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/ErrorHandlingUtils.php @@ -0,0 +1,42 @@ +getTrace(); + + try { + ['file' => $file, 'line' => $line] = $firstFrame; + } catch (\Exception $_) { + ['file' => $file, 'line' => $line] = $secondFrame; + } + $exceptionType = get_class($e); + $message = $e->getMessage(); + $message = "{$exceptionType} in {$file} at line {$line}: {$message}"; + ConsoleOutputUtils::error($message); + ConsoleOutputUtils::error('Run this again with the --verbose flag to see the full stack trace.'); + } + + public static function dumpException(\Throwable $e): void + { + $output = new ConsoleOutput(OutputInterface::VERBOSITY_VERBOSE); + $handler = new Handler(new Writer(null, $output)); + $handler->setInspector(new Inspector($e)); + $handler->setException($e); + $handler->handle(); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/Globals.php b/vendor/knuckleswtf/scribe/src/Tools/Globals.php new file mode 100644 index 00000000..d655a426 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/Globals.php @@ -0,0 +1,22 @@ +headings[] = [ + 'text' => $text, + 'level' => $level, + 'slug' => $slug, + ]; + } + + return $block; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/PathConfig.php b/vendor/knuckleswtf/scribe/src/Tools/PathConfig.php new file mode 100644 index 00000000..3e1c831a --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/PathConfig.php @@ -0,0 +1,46 @@ +scribeDir)) { + $this->scribeDir = ".{$this->configName}"; + } + } + + public function outputPath(?string $resolvePath = null, string $separator = '/'): string + { + if (is_null($resolvePath)) { + return $this->configName; + } + + return "{$this->configName}{$separator}{$resolvePath}"; + } + + public function configFileName(): string + { + return "{$this->configName}.php"; + } + + /** + * The directory where Scribe writes its intermediate output (default is . ie .scribe). + */ + public function intermediateOutputPath(?string $resolvePath = null, string $separator = '/'): string + { + if (is_null($resolvePath)) { + return $this->scribeDir; + } + + return "{$this->scribeDir}{$separator}{$resolvePath}"; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/RoutePatternMatcher.php b/vendor/knuckleswtf/scribe/src/Tools/RoutePatternMatcher.php new file mode 100644 index 00000000..b0e7b5da --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/RoutePatternMatcher.php @@ -0,0 +1,31 @@ +getName(); + $routePathWithoutInitialSlash = $route->uri(); + $routePathWithInitialSlash = "/{$routePathWithoutInitialSlash}"; + $routeMethods = $route->methods(); + if (Str::is($patterns, $routeName) + || Str::is($patterns, $routePathWithoutInitialSlash) + || Str::is($patterns, $routePathWithInitialSlash)) { + return true; + } + + foreach ($routeMethods as $httpMethod) { + if (Str::is($patterns, "{$httpMethod} {$routePathWithoutInitialSlash}") + || Str::is($patterns, "{$httpMethod} {$routePathWithInitialSlash}")) { + return true; + } + } + + return false; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/Utils.php b/vendor/knuckleswtf/scribe/src/Tools/Utils.php new file mode 100644 index 00000000..d39b1fcb --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/Utils.php @@ -0,0 +1,341 @@ + [ (options for b) ] + * ] + * + * This method extracts the top-level options (['a', 'b']) + */ + public static function getTopLevelItemsFromMixedConfigList(array $mixedList): array + { + $topLevels = []; + foreach ($mixedList as $item => $value) { + $topLevels[] = is_int($item) ? $value : $item; + } + + return $topLevels; + } + + public static function getUrlWithBoundParameters(string $uri, array $urlParameters = []): string + { + return self::replaceUrlParameterPlaceholdersWithValues($uri, $urlParameters); + } + + /** + * Transform parameters in URLs into real values (/users/{user} -> /users/2). + * Uses @urlParam values specified by caller, otherwise just uses '1'. + * + * @param array $urlParameters Dictionary of url params and example values + */ + public static function replaceUrlParameterPlaceholdersWithValues(string $uri, array $urlParameters): string + { + if (empty($urlParameters)) { + return $uri; + } + + foreach ($urlParameters as $parameterName => $example) { + $uri = preg_replace('#\{'.$parameterName.'\??}#', $example, $uri); + } + + // Remove unbound optional parameters with nothing + $uri = preg_replace('#{([^/]+\?)}#', '', $uri); + + // Replace any unbound non-optional parameters with '1' + return preg_replace('#{([^/]+)}#', '1', $uri); + } + + public static function getRouteClassAndMethodNames(Route $route): array + { + $action = $route->getAction(); + + $uses = $action['uses']; + + if ($uses !== null) { + if (is_array($uses)) { + return $uses; + } + if (is_string($uses)) { + $usesArray = explode('@', $uses); + if (count($usesArray) < 2) { + throw CouldntGetRouteDetails::new(); + } + [$class, $method] = $usesArray; + + // Support for the Laravel Actions package, docblock should be put on the asController method + if ($method === '__invoke' && method_exists($class, 'asController')) { + return [$class, 'asController']; + } + + return [$class, $method]; + } + if (static::isInvokableObject($uses)) { + return [$uses, '__invoke']; + } + } + if (array_key_exists(0, $action) && array_key_exists(1, $action)) { + return [ + 0 => $action[0], + 1 => $action[1], + ]; + } + + throw new \Exception("Couldn't get class and method names for route ".c::getRouteRepresentation($route).'.'); + } + + public static function deleteDirectoryAndContents(string $dir, ?string $workingDir = null): void + { + $workingDir ??= getcwd(); + $adapter = new LocalFilesystemAdapter($workingDir); + $fs = new Filesystem($adapter); + $dir = str_replace($workingDir, '', $dir); + $fs->deleteDirectory($dir); + } + + /** + * @return DirectoryListing + * + * @throws FilesystemException + */ + public static function listDirectoryContents(string $dir) + { + $adapter = new LocalFilesystemAdapter(getcwd()); + $fs = new Filesystem($adapter); + + return $fs->listContents($dir); + } + + public static function copyDirectory(string $src, string $dest): void + { + if (! is_dir($src)) { + return; + } + + // If the destination directory does not exist create it + if (! is_dir($dest)) { + if (! mkdir($dest, 0o777, true)) { + // If the destination directory could not be created stop processing + throw new \Exception("Failed to create target directory: {$dest}"); + } + } + + // Open the source directory to read in files + $i = new \DirectoryIterator($src); + foreach ($i as $f) { + if ($f->isFile()) { + copy($f->getRealPath(), "{$dest}/".$f->getFilename()); + } elseif (! $f->isDot() && $f->isDir()) { + self::copyDirectory($f->getRealPath(), "{$dest}/{$f}"); + } + } + } + + public static function makeDirectoryRecursive(string $dir): void + { + File::isDirectory($dir) || File::makeDirectory($dir, 0o777, true, true); + } + + public static function deleteFilesMatching(string $dir, callable $condition): void + { + if (class_exists(LocalFilesystemAdapter::class)) { + // Flysystem 2+ + $adapter = new LocalFilesystemAdapter(getcwd()); + $fs = new Filesystem($adapter); + $contents = $fs->listContents(mb_ltrim($dir, '/')); + } else { + // v1 + $adapter = new Local(getcwd()); // @phpstan-ignore-line + $fs = new Filesystem($adapter); // @phpstan-ignore-line + $dir = str_replace($adapter->getPathPrefix(), '', $dir); // @phpstan-ignore-line + $contents = $fs->listContents(mb_ltrim($dir, '/')); + } + foreach ($contents as $file) { + // Flysystem v1 had items as arrays; v2 has objects. + // v2 allows ArrayAccess, but when we drop v1 support (Laravel <9), we should switch to methods + if ($file['type'] === 'file' && $condition($file) === true) { + $fs->delete($file['path']); + } + } + } + + /** + * @param mixed $value + */ + public static function isInvokableObject($value): bool + { + return is_object($value) && method_exists($value, '__invoke'); + } + + /** + * Returns the route method or closure as an instance of ReflectionMethod or ReflectionFunction. + * + * @throws \ReflectionException + */ + public static function getReflectedRouteMethod(array $routeControllerAndMethod): \ReflectionFunctionAbstract + { + if (count($routeControllerAndMethod) < 2) { + throw CouldntGetRouteDetails::new(); + } + [$class, $method] = $routeControllerAndMethod; + + if ($class instanceof Closure) { + return new ReflectionFunction($class); + } + + return (new \ReflectionClass($class))->getMethod($method); + } + + public static function isArrayType(string $typeName) + { + return Str::endsWith($typeName, '[]'); + } + + public static function getBaseTypeFromArrayType(string $typeName) + { + return mb_substr($typeName, 0, -2); + } + + /** + * @param string[] $states + * @param string[] $relations + * @return Factory + * + * @throws \Throwable + */ + public static function getModelFactory(string $modelName, array $states = [], array $relations = []) + { + // Factories are usually defined without the leading \ in the class name, + // but the user might write it that way in a comment. Let's be safe. + $modelName = mb_ltrim($modelName, '\\'); + + if (method_exists($modelName, 'factory')) { // Laravel 8 type factory + /** @var Factory $factory */ + $factory = call_user_func_array([$modelName, 'factory'], []); + foreach ($states as $state) { + if (method_exists(get_class($factory), $state)) { + $factory = $factory->{$state}(); + } + } + + foreach ($relations as $relation) { + // Support nested relations; see https://github.com/knuckleswtf/scribe/pull/364 for a detailed example + // Example: App\Models\Author with=posts.categories + $relationChain = explode('.', $relation); + $relationVector = array_shift($relationChain); + + $relation = (new $modelName)->{$relationVector}(); + $relationType = get_class($relation); + $relationModel = get_class($relation->getModel()); + + $factoryChain = empty($relationChain) + ? call_user_func_array([$relationModel, 'factory'], []) + : self::getModelFactory($relationModel, $states, [implode('.', $relationChain)]); + + if ($relation instanceof BelongsToMany) { + $pivot = method_exists($factory, 'pivot'.$relationVector) + ? $factory->{'pivot'.$relationVector}() + : []; + + $factory = $factory->hasAttached($factoryChain, $pivot, $relationVector); + } elseif ($relationType === BelongsTo::class) { + $factory = $factory->for($factoryChain, $relationVector); + } else { + $factory = $factory->has($factoryChain, $relationVector); + } + } + } else { + try { + $factory = factory($modelName); + } catch (\Throwable $e) { + if (Str::contains($e->getMessage(), 'Call to undefined function Knuckles\Scribe\Tools\factory()')) { + throw CouldntFindFactory::forModel($modelName); + } + + throw $e; + } + if (count($states)) { + $factory = $factory->states($states); + } + } + + return $factory; + } + + /** + * Filter a list of docblock tags to those matching the specified ones (case-insensitive). + * + * @param Tag[] $tags + * @return Tag[] + */ + public static function filterDocBlockTags(array $tags, string ...$names): array + { + // Avoid "holes" in the keys of the filtered array by using array_values + return array_values( + array_filter($tags, fn ($tag) => in_array(mb_strtolower($tag->getName()), $names)) + ); + } + + /** + * Like Laravel's trans/__ function, but will fallback to using the default translation if translation fails. + * For instance, if the user's locale is DE, but they have no DE strings defined, + * Laravel simply renders the translation key. + * Instead, we render the EN version. + */ + public static function trans(string $key, array $replace = []) + { + // We only load our custom translation layer if we really need it + if (! ScribeServiceProvider::$customTranslationLayerLoaded) { + app(ScribeServiceProvider::class, ['app' => app()])->loadCustomTranslationLayer(); + } + + $translation = trans($key, $replace); + + // @phpstan-ignore-next-line + if ($translation === $key || $translation === null) { + $translation = trans($key, $replace, 'en'); + } + + if ($translation === $key) { + throw new \Exception("Translation not found for {$key}. You can add a translation for this in your `lang/scribe.php`, but this is likely a problem with the package. Please open an issue."); + } + + return $translation; + } +} + +function getTopLevelItemsFromMixedOrderList(array $mixedList): array +{ + $topLevels = []; + foreach ($mixedList as $item => $value) { + $topLevels[] = is_int($item) ? $value : $item; + } + + return $topLevels; +} diff --git a/vendor/knuckleswtf/scribe/src/Tools/WritingUtils.php b/vendor/knuckleswtf/scribe/src/Tools/WritingUtils.php new file mode 100644 index 00000000..fc123c6a --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Tools/WritingUtils.php @@ -0,0 +1,267 @@ + 'green', + 'HEAD' => 'darkgreen', + 'POST' => 'black', + 'PUT' => 'darkblue', + 'PATCH' => 'purple', + 'DELETE' => 'red', + 'OPTIONS' => 'grey', + ]; + + /** + * Print a value as valid PHP, handling arrays and proper indentation. + * + * @param mixed $value + * + * @throws ExceptionInterface + */ + public static function printPhpValue($value, int $indentationLevel = 0): string + { + $output = VarExporter::export($value); + // Padding with x spaces so they align + $split = explode("\n", $output); + $result = ''; + $padWith = str_repeat(' ', $indentationLevel); + foreach ($split as $index => $line) { + $result .= ($index === 0 ? '' : "\n{$padWith}").$line; + } + + return $result; + } + + public static function printQueryParamsAsString(array $cleanQueryParams): string + { + $qs = ''; + foreach ($cleanQueryParams as $paramName => $value) { + $qs .= static::printSingleQueryParamsAsString('', $paramName, $value, true); + } + + return mb_rtrim($qs, '&'); + } + + public static function printSingleQueryParamsAsString(string $prefix, int|string $key, mixed $parameters, bool $firstLevel): string + { + if (! is_array($parameters)) { + if ($firstLevel) { + return sprintf('%s=%s&', urlencode($key), urlencode($parameters)); + } + if (is_string($key)) { + return sprintf('%s[%s]=%s&', $prefix, urlencode($key), urlencode($parameters)); + } + + return sprintf('%s[]=%s&', $prefix, urlencode($parameters)); + } + if ($firstLevel) { + $newPrefix = urlencode($key); + } else { + $newPrefix = sprintf('%s[%s]', $prefix, urlencode($key)); + } + $query = ''; + foreach ($parameters as $item => $itemValue) { + $query .= static::printSingleQueryParamsAsString($newPrefix, $item, $itemValue, false); + } + + return $query; + } + + /** + * Print key-value query params as a hash { "key1": "value1", "key2": "value2" } + * Supports custom delimiters (eg "=>", default: ":"), + * custom braces (eg "[]", default: "{}"), + * custom quotes (eg ', default: "), + * custom indentation, line endings etc. + * Expands/simplifies arrays {key: [1, 2,]} becomes {"key[0]": "1","key[1]": "2"} + * Expands hashes {key: {a: 1, b: 2, c: {e: 3}}} becomes {"key[a]": "1", "key[b]": "2", "key[c][e]": "3"}. + */ + public static function printQueryParamsAsKeyValue( + array $cleanQueryParams, + string $quote = '"', + string $delimiter = ':', + int $spacesIndentation = 4, + string $braces = '{}', + int $closingBraceIndentation = 0, + string $startLinesWith = '', + string $endLinesWith = ',', + ): string { + $output = isset($braces[0]) ? "{$braces[0]}\n" : ''; + foreach ($cleanQueryParams as $parameter => $value) { + $output .= self::printSingleQueryParamAsKeyValue( + $value, + $spacesIndentation, + $startLinesWith, + $quote, + $parameter, + $delimiter, + $endLinesWith + ); + } + + $closing = isset($braces[1]) ? str_repeat(' ', $closingBraceIndentation)."{$braces[1]}" : ''; + + return $output.$closing; + } + + /** + * Expand a request parameter into one or more parameters to be used when sending as form-data. + * A primitive value like ("name", "John") is returned as ["name" => "John"] + * Lists like ("filter", ["haha"]) becomes ["filter[]" => "haha"] + * Maps like ("filter", ["name" => "john", "age" => "12"]) become ["filter[name]" => "john", "filter[age]" => 12]. + * + * @param string $parameter The name of the parameter + * @param mixed $value Value of the parameter + */ + public static function getParameterNamesAndValuesForFormData(string $parameter, $value): array + { + if (! is_array($value)) { + return [$parameter => $value]; + } + + // We assume it's a list if its first key is 0 + $keys = array_keys($value); + if (count($keys) && $keys[0] === 0) { + if (is_array($value[0])) { + // handle nested arrays/objects + $params = []; + $expanded = self::getParameterNamesAndValuesForFormData('', $value[0]); + foreach ($expanded as $fieldName => $itemValue) { + $paramName = $parameter.'[]'.$fieldName; + $params[$paramName] = $itemValue; + } + + return $params; + } + + return [$parameter.'[]' => $value[0]]; + } + + // Transform hashes + $params = []; + foreach ($value as $item => $itemValue) { + if (is_array($itemValue)) { + $expanded = self::getParameterNamesAndValuesForFormData('', $itemValue); + foreach ($expanded as $fieldName => $subItemValue) { + $paramName = $parameter."[{$item}]".$fieldName; + $params[$paramName] = $subItemValue; + } + } else { + $params[$parameter."[{$item}]"] = $itemValue; + } + } + + return $params; + } + + public static function getSampleBody(array $nestedBodyParameters) + { + if (! empty($nestedBodyParameters['[]'])) { + return [self::getSampleBody($nestedBodyParameters['[]']['__fields'])]; + } + + return array_map(function ($param) { + if (! empty($param['__fields'])) { + if ($param['type'] === 'object[]') { + return [self::getSampleBody($param['__fields'])]; + } + + return self::getSampleBody($param['__fields']); + } + + return $param['example']; + }, $nestedBodyParameters); + } + + /** + * Convert a list of possible values to a friendly string: + * [1, 2, 3] -> "1, 2, or 3" + * [1, 2] -> "1 or 2" + * [1] -> "1" + * Each value is wrapped in HTML tags, so you actually get "1, 2, or + * 3" + */ + public static function getListOfValuesAsFriendlyHtmlString(array $list = [], string $conjunction = 'or'): string + { + return match (count($list)) { + 1 => "{$list[0]}", + 2 => "{$list[0]} {$conjunction} {$list[1]}", + default => '' + .implode(', ', array_slice($list, 0, -1)) + .", {$conjunction} ".end($list).'', + }; + } + + /** + * Convert a path like 'js/tryitout.js' to include the current Scribe version ('js/tryitout-3.0.1.js'). + */ + public static function getVersionedAsset(string $assetPath): string + { + $index = mb_strrpos($assetPath, '.'); + + return substr_replace($assetPath, '-'.Scribe::VERSION, $index, 0); + } + + protected static function printSingleQueryParamAsKeyValue( + mixed $value, + int $spacesIndentation, + string $startLinesWith, + string $quote, + string $parameter, + string $delimiter, + string $endLinesWith, + ): string { + if (! is_array($value)) { + $output = str_repeat(' ', $spacesIndentation); + // Example: -----"param_name": "value"---- + $formattedValue = is_bool($value) ? ($value ? 1 : 0) : $value; + $output .= "{$startLinesWith}{$quote}{$parameter}{$quote}{$delimiter} {$quote}{$formattedValue}{$quote}{$endLinesWith}\n"; + } else { + $output = ''; + if (count($value) === 0) { + return $output; + } + + // List query param (eg filter[]=haha should become "filter[0]": "haha") + // Hash query param (eg filter[name]=john should become "filter[name]": "john") + // Hash query param (eg filter[info][name]=john should become "filter[info][name]": "john") + foreach ($value as $item => $itemValue) { + $parameterString = sprintf('%s[%s]', $parameter, $item); + if (is_array($itemValue)) { + $output .= static::printSingleQueryParamAsKeyValue( + $itemValue, + $spacesIndentation, + $startLinesWith, + $quote, + $parameterString, + $delimiter, + $endLinesWith + ); + } else { + $output .= str_repeat(' ', $spacesIndentation); + $output .= sprintf( + "%s%s%s%s%s %s%s%s%s\n", + $startLinesWith, + $quote, + $parameterString, + $quote, + $delimiter, + $quote, + $itemValue, + $quote, + $endLinesWith + ); + } + } + } + + return $output; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/CustomTranslationsLoader.php b/vendor/knuckleswtf/scribe/src/Writing/CustomTranslationsLoader.php new file mode 100644 index 00000000..ca261b1c --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/CustomTranslationsLoader.php @@ -0,0 +1,63 @@ +defaultLoader = $loader; + $this->files = app('files'); + $this->langPath = app('path.lang'); + } + + public function load($locale, $group, $namespace = null) + { + // Laravel expects translation strings to be broken up into groups (files): + // `lang/scribe/en/auth.php`, `lang/scribe/en/links.php` + // We want to trick it into accepting a simple `lang/scribe.php`. + + if ($namespace === 'scribe') { + if (isset($this->scribeTranslationsCache)) { + $lines = $this->scribeTranslationsCache[$group] ?? []; + } elseif ($this->files->exists($full = "{$this->hints[$namespace]}/scribe.php")) { + $this->scribeTranslationsCache = $this->files->getRequire($full); + // getRequire() requires the Scribe file, which will return an array + $lines = $this->scribeTranslationsCache[$group] ?? []; + } else { + return []; + } + + return $this->loadScribeNamespaceOverrides($lines, $locale, $group, $namespace); + } + + return $this->defaultLoader->load($locale, $group, $namespace); + } + + protected function loadScribeNamespaceOverrides(array $lines, $locale, $group, $namespace) + { + $userTranslationsFile = "{$this->langPath}/scribe.php"; + + if ($this->files->exists($userTranslationsFile)) { + if (! isset($this->userTranslationsCache)) { + $this->userTranslationsCache = $this->files->getRequire($userTranslationsFile); + } + $userTranslations = $this->userTranslationsCache[$group] ?? []; + + return array_replace_recursive($lines, $userTranslations); + } + + return $lines; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/ExternalHtmlWriter.php b/vendor/knuckleswtf/scribe/src/Writing/ExternalHtmlWriter.php new file mode 100644 index 00000000..b61f3b14 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/ExternalHtmlWriter.php @@ -0,0 +1,49 @@ +config->get('theme'); + $output = View::make("scribe::external.{$template}", [ + 'metadata' => $this->getMetadata(), + 'baseUrl' => $this->baseUrl, + 'tryItOut' => $this->config->get('try_it_out'), + 'htmlAttributes' => $this->config->get('external.html_attributes', []), + ])->render(); + + if (! is_dir($destinationFolder)) { + mkdir($destinationFolder, 0o777, true); + } + + file_put_contents($destinationFolder.'/index.html', $output); + } + + public function getMetadata(): array + { + // NB:These paths are wrong for laravel type but will be set correctly by the Writer class + if ($this->config->get('postman.enabled', true)) { + $postmanCollectionUrl = "{$this->assetPathPrefix}collection.json"; + } + if ($this->config->get('openapi.enabled', false)) { + $openApiSpecUrl = "{$this->assetPathPrefix}openapi.yaml"; + } + + return [ + 'title' => $this->config->get('title') ?: config('app.name', '').' Documentation', + 'example_languages' => $this->config->get('example_languages'), // may be useful + 'logo' => $this->config->get('logo') ?? false, + 'last_updated' => $this->getLastUpdated(), // may be useful + 'try_it_out' => $this->config->get('try_it_out'), // may be useful + 'postman_collection_url' => $postmanCollectionUrl ?? null, + 'openapi_spec_url' => $openApiSpecUrl ?? null, + ]; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/HtmlWriter.php b/vendor/knuckleswtf/scribe/src/Writing/HtmlWriter.php new file mode 100644 index 00000000..79e9309f --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/HtmlWriter.php @@ -0,0 +1,234 @@ +config = $config ?: new DocumentationConfig(config('scribe', [])); + $this->markdownParser = new MarkdownParser; + $this->baseUrl = $this->config->get('base_url') ?? config('app.url'); + // If they're using the default static path, + // then use '../docs/{asset}', so assets can work via Laravel app or via index.html + $this->assetPathPrefix = '../docs/'; + if (in_array($this->config->get('type'), ['static', 'external_static']) + && mb_rtrim($this->config->get('static.output_path', ''), '/') !== 'public/docs' + ) { + $this->assetPathPrefix = './'; + } + } + + public function generate(array $groupedEndpoints, string $sourceFolder, string $destinationFolder) + { + $intro = $this->transformMarkdownFileToHTML($sourceFolder.'/intro.md'); + $auth = $this->transformMarkdownFileToHTML($sourceFolder.'/auth.md'); + $headingsBeforeEndpoints = $this->markdownParser->headings; + + $this->markdownParser->headings = []; + $appendFile = mb_rtrim($sourceFolder, '/').'/append.md'; + $append = file_exists($appendFile) ? $this->transformMarkdownFileToHTML($appendFile) : ''; + $headingsAfterEndpoints = $this->markdownParser->headings; + + foreach ($groupedEndpoints as &$group) { + $group['subgroups'] = collect($group['endpoints'])->groupBy('metadata.subgroup')->all(); + } + $theme = $this->config->get('theme') ?? 'default'; + $output = View::make("scribe::themes.{$theme}.index", [ + 'metadata' => $this->getMetadata(), + 'baseUrl' => $this->baseUrl, + 'tryItOut' => $this->config->get('try_it_out'), + 'intro' => $intro, + 'auth' => $auth, + 'groupedEndpoints' => $groupedEndpoints, + 'headings' => $this->getHeadings($headingsBeforeEndpoints, $groupedEndpoints, $headingsAfterEndpoints), + 'append' => $append, + 'assetPathPrefix' => $this->assetPathPrefix, + ])->render(); + + if (! is_dir($destinationFolder)) { + mkdir($destinationFolder, 0o777, true); + } + + file_put_contents($destinationFolder.'/index.html', $output); + + // Copy assets + $assetsFolder = __DIR__.'/../../resources'; + // Prune older versioned assets + if (is_dir($destinationFolder.'/css')) { + Utils::deleteDirectoryAndContents($destinationFolder.'/css'); + } + if (is_dir($destinationFolder.'/js')) { + Utils::deleteDirectoryAndContents($destinationFolder.'/js'); + } + Utils::copyDirectory("{$assetsFolder}/images/", "{$destinationFolder}/images"); + + $assets = [ + "{$assetsFolder}/css/theme-{$theme}.style.css" => ["{$destinationFolder}/css/", "theme-{$theme}.style.css"], + "{$assetsFolder}/css/theme-{$theme}.print.css" => ["{$destinationFolder}/css/", "theme-{$theme}.print.css"], + "{$assetsFolder}/js/theme-{$theme}.js" => ["{$destinationFolder}/js/", WritingUtils::getVersionedAsset("theme-{$theme}.js")], + ]; + + if ($this->config->get('try_it_out.enabled', true)) { + $assets["{$assetsFolder}/js/tryitout.js"] = ["{$destinationFolder}/js/", WritingUtils::getVersionedAsset('tryitout.js')]; + } + + foreach ($assets as $path => [$destination, $fileName]) { + if (file_exists($path)) { + if (! is_dir($destination)) { + mkdir($destination, 0o777, true); + } + copy($path, $destination.$fileName); + } + } + } + + public function getMetadata(): array + { + // NB:These paths are wrong for laravel type but will be set correctly by the Writer class + if ($this->config->get('postman.enabled', true)) { + $postmanCollectionUrl = "{$this->assetPathPrefix}collection.json"; + } + if ($this->config->get('openapi.enabled', false)) { + $openApiSpecUrl = "{$this->assetPathPrefix}openapi.yaml"; + } + + $auth = $this->config->get('auth'); + if ($auth) { + if ($auth['in'] === 'bearer' || $auth['in'] === 'basic') { + $auth['name'] = 'Authorization'; + $auth['location'] = 'header'; + $auth['prefix'] = ucfirst($auth['in']).' '; + } else { + $auth['location'] = $auth['in']; + $auth['prefix'] = ''; + } + } + + return [ + 'title' => $this->config->get('title') ?: config('app.name', '').' Documentation', + 'example_languages' => $this->config->get('example_languages'), + 'logo' => $this->config->get('logo') ?? false, + 'last_updated' => $this->getLastUpdated(), + 'auth' => $auth, + 'try_it_out' => $this->config->get('try_it_out'), + 'postman_collection_url' => $postmanCollectionUrl ?? null, + 'openapi_spec_url' => $openApiSpecUrl ?? null, + ]; + } + + protected function transformMarkdownFileToHTML(string $markdownFilePath): string + { + return $this->markdownParser->text(file_get_contents($markdownFilePath)); + } + + protected function getLastUpdated() + { + $lastUpdated = $this->config->get('last_updated', 'Last updated: {date:F j, Y}'); + + $tokens = [ + 'date' => fn ($format) => date($format), + 'git' => fn ($format) => match ($format) { + 'short' => mb_trim(shell_exec('git rev-parse --short HEAD')), + 'long' => mb_trim(shell_exec('git rev-parse HEAD')), + default => throw new InvalidArgumentException("The `git` token only supports formats 'short' and 'long', but you specified {$format}"), + }, + ]; + + foreach ($tokens as $token => $resolver) { + $matches = []; + if (preg_match('#(\{'.$token.':(.+?)})#', $lastUpdated, $matches)) { + $lastUpdated = str_replace($matches[1], $resolver($matches[2]), $lastUpdated); + } + } + + return $lastUpdated; + } + + protected function getHeadings(array $headingsBeforeEndpoints, array $endpointsByGroupAndSubgroup, array $headingsAfterEndpoints) + { + $headings = []; + + $lastL1ElementIndex = null; + foreach ($headingsBeforeEndpoints as $heading) { + $element = [ + 'slug' => $heading['slug'], + 'name' => $heading['text'], + 'subheadings' => [], + ]; + if ($heading['level'] === 1) { + $headings[] = $element; + $lastL1ElementIndex = count($headings) - 1; + } elseif ($heading['level'] === 2 && ! is_null($lastL1ElementIndex)) { + $headings[$lastL1ElementIndex]['subheadings'][] = $element; + } + } + + $headings = array_merge($headings, array_values(array_map(function ($group) { + $groupSlug = Str::slug($group['name']); + + return [ + 'slug' => $groupSlug, + 'name' => $group['name'], + 'subheadings' => collect($group['subgroups'])->flatMap(function ($endpoints, $subgroupName) use ($groupSlug) { + if ($subgroupName === '') { + return $endpoints->map(fn (OutputEndpointData $endpoint) => [ + 'slug' => $endpoint->fullSlug(), + 'name' => $endpoint->name(), + 'subheadings' => [], + ])->values(); + } + + return [ + [ + 'slug' => "{$groupSlug}-".Str::slug($subgroupName), + 'name' => $subgroupName, + 'subheadings' => $endpoints->map(fn ($endpoint) => [ + 'slug' => $endpoint->fullSlug(), + 'name' => $endpoint->name(), + 'subheadings' => [], + ])->values(), + ], + ]; + })->values(), + ]; + }, $endpointsByGroupAndSubgroup))); + + $lastL1ElementIndex = null; + foreach ($headingsAfterEndpoints as $heading) { + $element = [ + 'slug' => $heading['slug'], + 'name' => $heading['text'], + 'subheadings' => [], + ]; + if ($heading['level'] === 1) { + $headings[] = $element; + $lastL1ElementIndex = count($headings) - 1; + } elseif ($heading['level'] === 2 && ! is_null($lastL1ElementIndex)) { + $headings[$lastL1ElementIndex]['subheadings'][] = $element; + } + } + + return $headings; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php b/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php new file mode 100644 index 00000000..a9018f18 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/OpenAPISpecWriter.php @@ -0,0 +1,118 @@ + + */ + private Collection $generators; + + public function __construct(?DocumentationConfig $config = null) + { + $this->config = $config ?: new DocumentationConfig(config('scribe', [])); + $generators = [ + $this->isOpenApi31OrLater() ? Base31Generator::class : BaseGenerator::class, + SecurityGenerator::class, + OverridesGenerator::class, + ]; + $this->generators = collect($generators) + ->merge($this->config->get('openapi.generators', [])) + ->map(fn ($generatorClass) => app()->makeWith($generatorClass, ['config' => $this->config])); + } + + /** + * Get the OpenAPI spec version to use from config, defaulting to 3.0.3. + * Supported versions: '3.0.3', '3.1.0'. + * + * @return string The OpenAPI version + */ + public function getSpecVersion(): string + { + return $this->config->get('openapi.version', self::SPEC_VERSION); + } + + /** + * See https://swagger.io/specification/. + * + * @param array $groupedEndpoints + */ + public function generateSpecContent(array $groupedEndpoints): array + { + $paths = ['paths' => $this->generatePathsSpec($groupedEndpoints)]; + + $content = []; + foreach ($this->generators as $generator) { + $content = $generator->root($content, $groupedEndpoints); + } + + return array_replace_recursive($content, $paths); + } + + /** + * @param array $groupedEndpoints + */ + protected function generatePathsSpec(array $groupedEndpoints): array + { + $allEndpoints = collect($groupedEndpoints)->map->endpoints->flatten(1); + // OpenAPI groups endpoints by path, then method + $groupedByPath = $allEndpoints->groupBy(function ($endpoint) { + $path = str_replace('?}', '}', $endpoint->uri); // Remove optional parameters indicator in path + + return '/'.mb_ltrim($path, '/'); + }); + + return $groupedByPath->mapWithKeys(function (Collection $endpoints, $path) use ($groupedEndpoints) { + $operations = $endpoints->mapWithKeys(function (OutputEndpointData $endpoint) use ($groupedEndpoints) { + $spec = []; + + foreach ($this->generators as $generator) { + $spec = $generator->pathItem($spec, $groupedEndpoints, $endpoint); + } + + return [mb_strtolower($endpoint->httpMethods[0]) => $spec]; + }); + + $pathItem = $operations; + + // Placing all URL parameters at the path level, since it's the same path anyway + /** @var OutputEndpointData $urlParameterEndpoint */ + $urlParameterEndpoint = $endpoints[0]; + + $parameters = []; + + foreach ($this->generators as $generator) { + $parameters = $generator->pathParameters($parameters, $endpoints->all(), $urlParameterEndpoint->urlParameters); + } + if (! empty($parameters)) { + $pathItem['parameters'] = array_values($parameters); + } + + return [$path => $pathItem]; + })->toArray(); + } + + protected function isOpenApi31OrLater(): bool + { + $version = $this->config->get('openapi.version', self::SPEC_VERSION); + + return version_compare($version, '3.1.0', '>='); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/Base31Generator.php b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/Base31Generator.php new file mode 100644 index 00000000..16cbe394 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/Base31Generator.php @@ -0,0 +1,155 @@ +convertExampleInSchemaToExamples($fieldData); + + return $fieldData; + } + + /** + * Override parent's generateSchemaForResponseValue to convert 'example' to 'examples' for OpenAPI 3.1. + */ + public function generateSchemaForResponseValue(mixed $value, \Knuckles\Camel\Output\OutputEndpointData $endpoint, string $path): array + { + $schema = parent::generateSchemaForResponseValue($value, $endpoint, $path); + $this->convertExampleInSchemaToExamples($schema); + + return $schema; + } + + /** + * Override parent's generateResponseContentSpec to convert 'example' to 'examples' for OpenAPI 3.1. + */ + protected function generateResponseContentSpec(?string $responseContent, \Knuckles\Camel\Output\OutputEndpointData $endpoint): array + { + $contentSpec = parent::generateResponseContentSpec($responseContent, $endpoint); + + // Convert example to examples in all schemas within the content spec + foreach ($contentSpec as $contentType => &$content) { + if (isset($content['schema'])) { + $this->convertExampleInSchemaToExamples($content['schema']); + } + } + + return $contentSpec; + } + + /** + * Convert 'example' to 'examples' for OpenAPI 3.1 compatibility. + * OpenAPI 3.1 uses JSON Schema, which prefers 'examples' (plural, as an array). + */ + protected function convertExampleInSchemaToExamples(array &$schema): void + { + // Only convert if 'example' exists and 'examples' doesn't already exist + // If both exist, prioritize 'examples' and remove 'example' to avoid conflicts + if (array_key_exists('example', $schema)) { + if (! array_key_exists('examples', $schema)) { + $schema['examples'] = [$schema['example']]; + } + // Remove 'example' to ensure only 'examples' is present in OpenAPI 3.1 + unset($schema['example']); + } + + // Recursively handle nested properties + if (isset($schema['properties']) && is_array($schema['properties'])) { + foreach ($schema['properties'] as &$property) { + $this->convertExampleInSchemaToExamples($property); + } + } + + // Handle items in arrays + if (isset($schema['items']) && is_array($schema['items'])) { + $this->convertExampleInSchemaToExamples($schema['items']); + } + } + + protected function convertExampleOutsideSchemaToExamples(array &$data): void + { + // Only convert if 'example' exists and 'examples' doesn't already exist + // If both exist, prioritize 'examples' and remove 'example' to avoid conflicts + if (isset($data['example'])) { + if (! isset($data['schema']['examples'])) { + $data['schema']['examples'] = [$data['example']]; + } + // Remove 'example' to ensure only 'examples' is present in OpenAPI 3.1 + unset($data['example']); + } + + // Recursively handle nested properties + if (isset($data['schema']['properties']) && is_array($data['schema']['properties'])) { + foreach ($data['schema']['properties'] as &$property) { + $this->convertExampleInSchemaToExamples($property); + } + } + + // Handle items in arrays + if (isset($data['schema']['items']) && is_array($data['schema']['items'])) { + $this->convertExampleInSchemaToExamples($data['schema']['items']); + } + } + + protected function headerToOpenApiParameterObject(string $name, string $value): array + { + $data = parent::headerToOpenApiParameterObject($name, $value); + $this->convertExampleOutsideSchemaToExamples($data); + + return $data; + } + + protected function queryParamToOpenApiParameterObject(string $name, Parameter $details): array + { + $data = parent::queryParamToOpenApiParameterObject($name, $details); + $this->convertExampleOutsideSchemaToExamples($data); + + return $data; + } + + protected function urlParamToOpenApiParameterObject(string $name, Parameter $details): array + { + $data = parent::urlParamToOpenApiParameterObject($name, $details); + if (isset($data['examples'])) { + // This is NOT the JSON Schema 'examples' array, but the OpenAPI Parameter Object 'examples' Map, outside the schema. Leave untouched + return $data; + } + + $this->convertExampleOutsideSchemaToExamples($data); + + return $data; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/BaseGenerator.php b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/BaseGenerator.php new file mode 100644 index 00000000..8f492ab8 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/BaseGenerator.php @@ -0,0 +1,706 @@ + $this->config->get('openapi.version', OpenAPISpecWriter::SPEC_VERSION), + 'info' => [ + 'title' => $this->config->get('title') ?: config('app.name', ''), + 'description' => $this->config->get('description', ''), + 'version' => '1.0.0', + ], + 'servers' => [ + [ + 'url' => mb_rtrim($this->config->get('base_url') ?? config('app.url'), '/'), + ], + ], + 'tags' => array_values(array_map(function (array $group) { + return [ + 'name' => $group['name'], + 'description' => $group['description'], + ]; + }, $groupedEndpoints)), + ]); + } + + public function pathItem(array $pathItem, array $groupedEndpoints, OutputEndpointData $endpoint): array + { + $spec = [ + 'summary' => $endpoint->metadata->title, + 'operationId' => $this->operationId($endpoint), + 'description' => $endpoint->metadata->description, + 'parameters' => $this->generateEndpointParametersSpec($endpoint), + 'responses' => $this->generateEndpointResponsesSpec($endpoint), + 'tags' => [Arr::first($groupedEndpoints, function ($group) use ($endpoint) { + return Camel::doesGroupContainEndpoint($group, $endpoint); + })['name']], + ]; + + if ($endpoint->metadata->deprecated) { + $spec['deprecated'] = true; + } + + if (count($endpoint->bodyParameters)) { + $spec['requestBody'] = $this->generateEndpointRequestBodySpec($endpoint); + } + + return array_merge($pathItem, $spec); + } + + public function pathParameters(array $parameters, array $endpoints, array $urlParameters): array + { + foreach ($urlParameters as $name => $details) { + $parameterData = $this->urlParamToOpenApiParameterObject($name, $details); + $parameters[$name] = $parameterData; + } + + return $parameters; + } + + /** + * @param array|Parameter $field + */ + public function generateFieldData($field): array + { + if (is_array($field)) { + $field = new Parameter($field); + } + + if ($field->type === 'file') { + // See https://swagger.io/docs/specification/describing-request-body/file-upload/ + $fieldData = [ + 'type' => 'string', + 'format' => 'binary', + 'description' => $field->description ?: '', + ]; + $this->applyNullable($fieldData, $field->nullable); + + return $fieldData; + } + if (Utils::isArrayType($field->type)) { + $baseType = Utils::getBaseTypeFromArrayType($field->type); + $baseItem = ($baseType === 'file') ? [ + 'type' => 'string', + 'format' => 'binary', + ] : ['type' => $baseType]; + + if (! empty($field->enumValues)) { + $baseItem['enum'] = $field->enumValues; + } + + $this->applyNullable($baseItem, $field->nullable); + + $fieldData = [ + 'type' => 'array', + 'description' => $field->description ?: '', + 'example' => $field->example, + 'items' => Utils::isArrayType($baseType) + ? $this->generateFieldData([ + 'name' => '', + 'type' => $baseType, + 'example' => ($field->example ?: [null])[0], + 'nullable' => $field->nullable, + ]) + : $baseItem, + ]; + if (str_replace('[]', '', $field->type) === 'file') { + // Don't include example for file params in OAS; it's hard to translate it correctly + unset($fieldData['example']); + } + + if ($baseType === 'object' && ! empty($field->__fields)) { + if ($fieldData['items']['type'] === 'object') { + $fieldData['items']['properties'] = []; + } + foreach ($field->__fields as $fieldSimpleName => $subfield) { + $fieldData['items']['properties'][$fieldSimpleName] = $this->generateFieldData($subfield); + if ($subfield['required']) { + $fieldData['items']['required'][] = $fieldSimpleName; + } + } + } + + return $fieldData; + } + if ($field->type === 'object') { + $data = [ + 'type' => 'object', + 'description' => $field->description ?: '', + 'example' => $field->example, + 'properties' => $this->objectIfEmpty(collect($field->__fields)->mapWithKeys(function ($subfield, $subfieldName) { + return [$subfieldName => $this->generateFieldData($subfield)]; + })->all()), + 'required' => collect($field->__fields)->filter(fn ($f) => $f['required'])->keys()->toArray(), + ]; + $this->applyNullable($data, $field->nullable); + // The spec doesn't allow for an empty `required` array. Must have something there. + if (empty($data['required'])) { + unset($data['required']); + } + + return $data; + } + $schema = [ + 'type' => static::normalizeTypeName($field->type), + 'description' => $field->description ?: '', + 'example' => $field->example, + ]; + if (! empty($field->enumValues)) { + $schema['enum'] = $field->enumValues; + } + $this->applyNullable($schema, $field->nullable); + + return $schema; + } + + /** + * Given a value, generate the schema for it. The schema consists of: {type:, example:, properties: (if value is an + * object)}, and possibly a description for each property. The $endpoint and $path are used for looking up response + * field descriptions. + */ + public function generateSchemaForResponseValue(mixed $value, OutputEndpointData $endpoint, string $path): array + { + if ($value instanceof \stdClass) { + $value = (array) $value; + $properties = []; + // Recurse into the object + foreach ($value as $subField => $subValue) { + $subFieldPath = sprintf('%s.%s', $path, $subField); + $properties[$subField] = $this->generateSchemaForResponseValue($subValue, $endpoint, $subFieldPath); + } + $required = $this->filterRequiredResponseFields($endpoint, array_keys($properties), $path); + + $schema = [ + 'type' => 'object', + 'properties' => $this->objectIfEmpty($properties), + ]; + if ($required) { + $schema['required'] = $required; + } + $this->setDescription($schema, $endpoint, $path); + $this->setNullable($schema, $endpoint, $path, $value); + + return $schema; + } + + $schema = [ + 'type' => $this->convertScribeOrPHPTypeToOpenAPIType(gettype($value)), + 'example' => $value, + ]; + $this->setDescription($schema, $endpoint, $path); + $this->setNullable($schema, $endpoint, $path, $value); + + // Set enum values for the property if they exist + if (! empty($endpoint->responseFields[$path]->enumValues)) { + $schema['enum'] = $endpoint->responseFields[$path]->enumValues; + } + + if ($schema['type'] === 'array' && ! empty($value)) { + $schema['example'] = json_decode(json_encode($schema['example']), true); // Convert stdClass to array + + $sample = $value[0]; + $typeOfEachItem = $this->convertScribeOrPHPTypeToOpenAPIType(gettype($sample)); + $schema['items']['type'] = $typeOfEachItem; + + if ($typeOfEachItem === 'object') { + $schema['items']['properties'] = collect($sample)->mapWithKeys(function ($v, $k) use ($endpoint, $path) { + return [$k => $this->generateSchemaForResponseValue($v, $endpoint, "{$path}.{$k}")]; + })->toArray(); + + $required = $this->filterRequiredResponseFields( + $endpoint, + array_keys($schema['items']['properties']), + $path + ); + if ($required) { + $schema['required'] = $required; + } + } + } + + return $schema; + } + + /** + * Given an enpoint and a set of object keys at a path, return the properties that are specified as required. + */ + public function filterRequiredResponseFields(OutputEndpointData $endpoint, array $properties, string $path = ''): array + { + $required = []; + foreach ($properties as $property) { + $responseField = $endpoint->responseFields["{$path}.{$property}"] ?? $endpoint->responseFields[$property] ?? null; + if ($responseField && $responseField->required) { + $required[] = $property; + } + } + + return $required; + } + + protected function operationId(OutputEndpointData $endpoint): string + { + if ($endpoint->metadata->title) { + return preg_replace('/[^\w+]/', '', Str::camel($endpoint->metadata->title)); + } + + $parts = preg_split('/[^\w+]/', $endpoint->uri, -1, PREG_SPLIT_NO_EMPTY); + + return Str::lower($endpoint->httpMethods[0]).implode('', array_map(fn ($part) => ucfirst($part), $parts)); + } + + /** + * Add query parameters and headers. + * + * @return array> + */ + protected function generateEndpointParametersSpec(OutputEndpointData $endpoint): array + { + $parameters = []; + + $parameters = $this->generateQueryParams($endpoint, $parameters); + + $parameters = $this->generateHeaders($endpoint, $parameters); + + return $parameters; + } + + protected function generateEndpointRequestBodySpec(OutputEndpointData $endpoint): array|\stdClass + { + $body = []; + + if (count($endpoint->bodyParameters)) { + $schema = [ + 'type' => 'object', + 'properties' => [], + ]; + + $hasRequiredParameter = false; + $hasFileParameter = false; + + foreach ($endpoint->nestedBodyParameters as $name => $details) { + if ($name === '[]') { // Request body is an array + $hasRequiredParameter = true; + $schema = $this->generateFieldData($details); + + break; + } + + if ($details['required']) { + $hasRequiredParameter = true; + // Don't declare this earlier. + // The spec doesn't allow for an empty `required` array. Must have something there. + $schema['required'][] = $name; + } + + if ($details['type'] === 'file') { + $hasFileParameter = true; + } + + $fieldData = $this->generateFieldData($details); + if ($details['deprecated']) { + $fieldData['deprecated'] = true; + } + + $schema['properties'][$name] = $fieldData; + } + + // We remove 'properties' if the request body is an array, so we need to check if it's still there + if (array_key_exists('properties', $schema)) { + $schema['properties'] = $this->objectIfEmpty($schema['properties']); + } + $body['required'] = $hasRequiredParameter; + + if ($hasFileParameter) { + // If there are file parameters, content type changes to multipart + $contentType = 'multipart/form-data'; + } elseif (isset($endpoint->headers['Content-Type'])) { + $contentType = $endpoint->headers['Content-Type']; + } else { + $contentType = 'application/json'; + } + + $body['content'][$contentType]['schema'] = $schema; + } + + // return object rather than empty array, so can get properly serialised as object + return $this->objectIfEmpty($body); + } + + protected function generateEndpointResponsesSpec(OutputEndpointData $endpoint) + { + // See https://swagger.io/docs/specification/describing-responses/ + $responses = []; + + foreach ($endpoint->responses as $response) { + $code = $response->status; // OpenAPI spec requires status codes to be integers + // OpenAPI groups responses by status code + // Only one response type per status code, so only the last one will be used + if ($code === '204') { + // Must not add content for 204 + $responses[$code] = [ + 'description' => $this->getResponseDescription($response), + ]; + } elseif (isset($responses[$code])) { + // If we already have a response for this status code and content type, + // we change to a `oneOf` which includes all the responses + $content = $this->generateResponseContentSpec($response->content, $endpoint); + $contentType = array_keys($content)[0]; + if (isset($responses[$code]['content'][$contentType])) { + $newResponseExample = array_replace([ + 'description' => $this->getResponseDescription($response), + ], $content[$contentType]['schema']); + + // If we've already created the oneOf object, add this response + if (isset($responses[$code]['content'][$contentType]['schema']['oneOf'])) { + $responses[$code]['content'][$contentType]['schema']['oneOf'][] = $newResponseExample; + } else { + // Create the oneOf object + $existingResponseExample = array_replace([ + 'description' => $responses[$code]['description'], + ], $responses[$code]['content'][$contentType]['schema']); + + $responses[$code]['description'] = ''; + $responses[$code]['content'][$contentType]['schema'] = [ + 'oneOf' => [$existingResponseExample, $newResponseExample], + ]; + } + } + } else { + // Store as the response for this status + $responses[$code] = [ + 'description' => $this->getResponseDescription($response), + 'content' => $this->generateResponseContentSpec($response->content, $endpoint), + ]; + } + } + + // return object rather than empty array, so can get properly serialised as object + return $this->objectIfEmpty($responses); + } + + protected function getResponseDescription(Response $response): string + { + if ($response->isBinary()) { + return mb_trim(str_replace('<>', '', $response->content)); + } + + $description = (string) ($response->description); + // Don't include the status code in description; see https://github.com/knuckleswtf/scribe/issues/271 + if (preg_match('/\d{3},\s+(.+)/', $description, $matches)) { + $description = $matches[1]; + } elseif ($description === (string) ($response->status)) { + $description = ''; + } + + return $description; + } + + protected function generateResponseContentSpec(?string $responseContent, OutputEndpointData $endpoint) + { + if (Str::startsWith($responseContent, '<>')) { + return [ + 'application/octet-stream' => [ + 'schema' => [ + 'type' => 'string', + 'format' => 'binary', + ], + ], + ]; + } + + if ($responseContent === null) { + $schema = [ + 'type' => 'object', + ]; + $this->applyNullable($schema, true); + + return [ + 'application/json' => [ + 'schema' => $schema, + ], + ]; + } + + $decoded = json_decode($responseContent); + if ($decoded === null) { // Decoding failed, so we return the content string as is + return [ + 'text/plain' => [ + 'schema' => [ + 'type' => 'string', + 'example' => $responseContent, + ], + ], + ]; + } + + $response = $endpoint->responses->where('content', $responseContent)->first(); + $contentType = $response->headers['content-type'] ?? $response->headers['Content-Type'] ?? 'application/json'; + + switch ($type = gettype($decoded)) { + case 'string': + case 'boolean': + case 'integer': + case 'double': + return [ + $contentType => [ + 'schema' => [ + 'type' => $type === 'double' ? 'number' : $type, + 'example' => $decoded, + ], + ], + ]; + + case 'array': + if (! count($decoded)) { + // empty array + return [ + $contentType => [ + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', // No better idea what to put here + ], + 'example' => $decoded, + ], + ], + ]; + } + + // Non-empty array + if (is_object($decoded[0])) { + // If the first item is an object, we assume it's an array of objects' + $properties = collect($decoded[0])->mapWithKeys(function ($value, $key) use ($endpoint) { + return [$key => $this->generateSchemaForResponseValue($value, $endpoint, $key)]; + })->toArray(); + + return [ + $contentType => [ + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => $this->convertScribeOrPHPTypeToOpenAPIType(gettype($decoded[0])), + 'properties' => $this->objectIfEmpty($properties), + ], + 'example' => $decoded, + ], + ], + ]; + } + + return [ + $contentType => [ + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => $this->convertScribeOrPHPTypeToOpenAPIType(gettype($decoded[0])), + ], + 'example' => $decoded, + ], + ], + ]; + + case 'object': + $properties = collect($decoded)->mapWithKeys(function ($value, $key) use ($endpoint) { + return [$key => $this->generateSchemaForResponseValue($value, $endpoint, $key)]; + })->toArray(); + $required = $this->filterRequiredResponseFields($endpoint, array_keys($properties)); + + $data = [ + $contentType => [ + 'schema' => [ + 'type' => 'object', + 'example' => $decoded, + 'properties' => $this->objectIfEmpty($properties), + ], + ], + ]; + if ($required) { + $data[$contentType]['schema']['required'] = $required; + } + + return $data; + + default: + return []; + } + } + + /** + * Given an array, return an object if the array is empty. To be used with fields that are + * required by OpenAPI spec to be objects, since empty arrays get serialised as []. + */ + protected function objectIfEmpty(array $field): array|\stdClass + { + return count($field) > 0 ? $field : new \stdClass; + } + + protected function convertScribeOrPHPTypeToOpenAPIType($type) + { + return match ($type) { + 'float', 'double' => 'number', + 'NULL' => 'string', + default => $type, + }; + } + + /** + * Handle nullable fields based on OpenAPI version. + * In OpenAPI 3.0, use 'nullable: true'. + * In OpenAPI 3.1, use JSON Schema's type array syntax: 'type: ["string", "null"]'. + */ + protected function applyNullable(array &$schema, bool $nullable): void + { + if (! $nullable) { + return; + } + + $schema['nullable'] = true; + } + + // Set the description for the schema. If the field has a description, it is set in the schema. + private function setDescription(array &$schema, OutputEndpointData $endpoint, string $path): void + { + if (! empty($endpoint->responseFields[$path]->description)) { + $schema['description'] = $endpoint->responseFields[$path]->description; + } + } + + // Set the nullable for the schema. If the field is nullable, it is set in the schema. + private function setNullable(array &$schema, OutputEndpointData $endpoint, string $path, mixed $value): void + { + /** @var null|ResponseField $field */ + $field = $endpoint->responseFields[$path] ?? null; + + // prefer explicit values + if ($field !== null && $field->nullable !== null) { + if ($field->nullable) { + $this->applyNullable($schema, true); + } + + // false => do not set and do not use example + return; + } + + // example is null + if ($value === null) { + $this->applyNullable($schema, true); + } + } + + protected function generateQueryParams(OutputEndpointData $endpoint, array $parameters): array + { + if (count($endpoint->queryParameters)) { + /** + * @var string $name + * @var Parameter $details + */ + foreach ($endpoint->queryParameters as $name => $details) { + $parameterData = $this->queryParamToOpenApiParameterObject($name, $details); + $parameters[] = $parameterData; + } + } + + return $parameters; + } + + protected function generateHeaders(OutputEndpointData $endpoint, mixed $parameters): mixed + { + if (count($endpoint->headers)) { + foreach ($endpoint->headers as $name => $value) { + if (in_array(mb_strtolower($name), ['content-type', 'accept', 'authorization'])) { + // These headers are not allowed in the spec. + // https://swagger.io/docs/specification/describing-parameters/#header-parameters + continue; + } + + $parameters[] = $this->headerToOpenApiParameterObject($name, $value); + } + } + + return $parameters; + } + + protected function headerToOpenApiParameterObject(string $name, string $value): array + { + return [ + 'in' => 'header', + 'name' => $name, + 'description' => '', + 'example' => $value, + 'schema' => [ + 'type' => 'string', + ], + ]; + } + + protected function queryParamToOpenApiParameterObject(string $name, Parameter $details): array + { + $parameterData = [ + 'in' => 'query', + 'name' => $name, + 'description' => $details->description, + 'example' => $details->example, + 'required' => $details->required, + 'schema' => $this->generateFieldData($details), + ]; + if ($details->deprecated) { + $parameterData['deprecated'] = true; + } + + return $parameterData; + } + + protected function urlParamToOpenApiParameterObject(string $name, Parameter $details): array + { + $parameterData = [ + 'in' => 'path', + 'name' => $name, + 'description' => $details->description, + 'example' => $details->example, + // Currently, OAS requires path parameters to be required + 'required' => true, + 'schema' => [ + 'type' => $details->type, + ], + ]; + // Workaround for optional parameters + if (empty($details->required)) { + $parameterData['description'] = rtrim('Optional parameter. '.$parameterData['description']); + $parameterData['examples'] = [ + 'omitted' => [ + 'summary' => 'When the value is omitted', + 'value' => '', + ], + ]; + + if ($parameterData['example'] !== null) { + $parameterData['examples']['present'] = [ + 'summary' => 'When the value is present', + 'value' => $parameterData['example'], + ]; + } + + // Can't have `example` and `examples` + unset($parameterData['example']); + } + + return $parameterData; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OpenApiGenerator.php b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OpenApiGenerator.php new file mode 100644 index 00000000..63e4602d --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OpenApiGenerator.php @@ -0,0 +1,63 @@ + $groupedEndpoints + * + * @see https://spec.openapis.org/oas/v3.1.1.html#openapi-object + */ + public function root(array $root, array $groupedEndpoints): array + { + return $root; + } + + /** + * This section is the individual path item object in an OpenApi document. It contains the details of the specific + * endpoint. This will be called for each individual endpoint, e.g. post, get, put, delete, etc. + * + * @param array $groupedEndpoints + * + * @see https://spec.openapis.org/oas/v3.1.1.html#path-item-object + */ + public function pathItem(array $pathItem, array $groupedEndpoints, OutputEndpointData $endpoint): array + { + return $pathItem; + } + + /** + * This section of the spec is the parameters object inside the path item object. It contains the details of all the + * parameters for the endpoints matching the path. This will be called for each individual path, e.g. /users, /posts + * it will not be called if a path has multiple endpoints, e.g. get and post. + * + * @param OutputEndpointData[] $endpoints + * @param Parameter[] $urlParameters + * + * @see https://spec.openapis.org/oas/v3.1.1.html#parameter-object + */ + public function pathParameters(array $parameters, array $endpoints, array $urlParameters): array + { + return $parameters; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OverridesGenerator.php b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OverridesGenerator.php new file mode 100644 index 00000000..f30d0d2f --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/OverridesGenerator.php @@ -0,0 +1,15 @@ +config->get('openapi.overrides', []); + + return array_replace_recursive($root, Arr::undot($overrides)); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/SecurityGenerator.php b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/SecurityGenerator.php new file mode 100644 index 00000000..cc0ce46b --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/OpenApiSpecGenerators/SecurityGenerator.php @@ -0,0 +1,60 @@ +config->get('auth.enabled', false); + if (! $isApiAuthed) { + return $root; + } + + $location = $this->config->get('auth.in'); + $parameterName = $this->config->get('auth.name'); + $description = $this->config->get('auth.extra_info'); + $scheme = match ($location) { + 'query', 'header' => [ + 'type' => 'apiKey', + 'name' => $parameterName, + 'in' => $location, + 'description' => $description, + ], + 'bearer', 'basic' => [ + 'type' => 'http', + 'scheme' => $location, + 'description' => $description, + ], + default => [], + }; + + return array_merge_recursive($root, [ + // All security schemes must be registered in `components.securitySchemes`... + 'components' => [ + 'securitySchemes' => [ + // 'default' is an arbitrary name for the auth scheme. Can be anything, really. + 'default' => $scheme, + ], + ], + // ...and then can be applied in `security` + 'security' => [ + [ + 'default' => [], + ], + ], + ]); + } + + public function pathItem(array $pathItem, array $groupedEndpoints, OutputEndpointData $endpoint): array + { + if (! $endpoint->metadata->authenticated) { + // Make sure to exclude non-auth endpoints from auth + $pathItem['security'] = []; + } + + return $pathItem; + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/PostmanCollectionWriter.php b/vendor/knuckleswtf/scribe/src/Writing/PostmanCollectionWriter.php new file mode 100644 index 00000000..ea386e0f --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/PostmanCollectionWriter.php @@ -0,0 +1,404 @@ +config = $config ?: new DocumentationConfig(config('scribe', [])); + $this->baseUrl = $this->config->get('base_url') ?: config('app.url'); + } + + /** + * @param array[] $groupedEndpoints + */ + public function generatePostmanCollection(array $groupedEndpoints): array + { + $collection = [ + 'variable' => [ + [ + 'id' => 'baseUrl', + 'key' => 'baseUrl', + 'type' => 'string', + 'name' => 'string', + 'value' => $this->baseUrl, + ], + ], + 'info' => [ + 'name' => $this->config->get('title') ?: config('app.name'), + '_postman_id' => Uuid::uuid4()->toString(), + 'description' => $this->config->get('description', ''), + 'schema' => 'https://schema.getpostman.com/json/collection/v'.self::SPEC_VERSION.'/collection.json', + ], + 'item' => array_values(array_map(function (array $group) { + return [ + 'name' => $group['name'], + 'description' => $group['description'], + 'item' => $this->generateSubItem($group), + ]; + }, $groupedEndpoints)), + 'auth' => $this->generateAuthObject(), + ]; + + return $collection; + } + + protected function generateAuthObject(): array + { + if (! $this->config->get('auth.enabled')) { + return [ + 'type' => 'noauth', + ]; + } + + return match ($this->config->get('auth.in')) { + 'basic' => [ + 'type' => 'basic', + ], + 'bearer' => [ + 'type' => 'bearer', + 'bearer' => [ + [ + 'key' => $this->config->get('auth.name'), + 'type' => 'string', + ], + ], + ], + default => [ + 'type' => 'apikey', + 'apikey' => [ + [ + 'key' => 'in', + 'value' => $this->config->get('auth.in'), + 'type' => 'string', + ], + [ + 'key' => 'key', + 'value' => $this->config->get('auth.name'), + 'type' => 'string', + ], + ], + ], + }; + } + + protected function generateSubItem(array $group): array + { + $seenSubgroups = []; + $items = []; + + /** @var OutputEndpointData $endpoint */ + foreach ($group['endpoints'] as $endpoint) { + if (! $endpoint->metadata->subgroup) { + $items[] = $this->generateEndpointItem($endpoint); + } else { + if (isset($seenSubgroups[$endpoint->metadata->subgroup])) { + $subgroupIndex = $seenSubgroups[$endpoint->metadata->subgroup]; + $items[$subgroupIndex]['description'] = $items[$subgroupIndex]['description'] ?: $endpoint->metadata->subgroupDescription; + $items[$subgroupIndex]['item'] = [...$items[$subgroupIndex]['item'], $this->generateEndpointItem($endpoint)]; + } else { + $items[] = [ + 'name' => $endpoint->metadata->subgroup, + 'description' => $endpoint->metadata->subgroupDescription, + 'item' => [$this->generateEndpointItem($endpoint)], + ]; + $seenSubgroups[$endpoint->metadata->subgroup] = count($items) - 1; + } + } + } + + return $items; + } + + protected function generateEndpointItem(OutputEndpointData $endpoint): array + { + $method = $endpoint->httpMethods[0]; + + $bodyParameters = empty($endpoint->bodyParameters) ? null : $this->getBodyData($endpoint); + + if ((in_array('PUT', $endpoint->httpMethods) || in_array('PATCH', $endpoint->httpMethods)) + && isset($bodyParameters['formdata'])) { + $method = 'POST'; + $bodyParameters['formdata'][] = [ + 'key' => '_method', + 'value' => $endpoint->httpMethods[0], + 'type' => 'text', + ]; + } + + $endpointItem = [ + 'name' => ($endpoint->metadata->title !== '' ? $endpoint->metadata->title : ($endpoint->httpMethods[0].' '.$endpoint->uri)) + .($endpoint->metadata->deprecated ? ' [DEPRECATED]' : ''), + 'request' => [ + 'url' => $this->generateUrlObject($endpoint), + 'method' => $method, + 'header' => $this->resolveHeadersForEndpoint($endpoint), + 'body' => $bodyParameters, + 'description' => $endpoint->metadata->description, + ], + 'response' => $this->getResponses($endpoint), + ]; + + if ($endpoint->metadata->authenticated === false) { + $endpointItem['request']['auth'] = ['type' => 'noauth']; + } + + return $endpointItem; + } + + protected function getBodyData(OutputEndpointData $endpoint): array + { + $body = []; + $contentType = $endpoint->headers['Content-Type'] ?? null; + $inputMode = match ($contentType) { + 'multipart/form-data' => 'formdata', + 'application/x-www-form-urlencoded' => 'urlencoded', + default => 'raw', + }; + $body['mode'] = $inputMode; + $body[$inputMode] = []; + + switch ($inputMode) { + case 'formdata': + case 'urlencoded': + $body[$inputMode] = $this->getFormDataParams( + $endpoint->cleanBodyParameters, + null, + $endpoint->bodyParameters + ); + foreach ($endpoint->fileParameters as $key => $value) { + while (is_array($value)) { + $keys = array_keys($value); + if ($keys[0] === 0) { + // List of files + $key .= '[]'; + $value = $value[0]; + } else { + $key .= '['.$keys[0].']'; + $value = $value[$keys[0]]; + } + } + $params = [ + 'key' => $key, + 'src' => [], + 'type' => 'file', + ]; + $body[$inputMode][] = $params; + } + + break; + + case 'raw': + default: + $body[$inputMode] = json_encode($endpoint->cleanBodyParameters, JSON_UNESCAPED_UNICODE); + } + + return $body; + } + + /** + * Format form-data parameters correctly for arrays eg. data[item][index] = value. + */ + protected function getFormDataParams(array $paramsKeyValue, ?string $key = null, array $paramsFullDetails = []): array + { + $body = []; + + foreach ($paramsKeyValue as $index => $value) { + $index = $key ? ($key.'['.$index.']') : $index; + + if (! is_array($value)) { + $body[] = [ + 'key' => $index, + 'value' => (string) $value, + 'type' => 'text', + 'description' => $paramsFullDetails[$index]->description ?? '', + ]; + + continue; + } + + $body = array_merge($body, $this->getFormDataParams($value, $index)); + } + + return $body; + } + + protected function resolveHeadersForEndpoint(OutputEndpointData $endpointData): array + { + [$where, $authParam] = $this->getAuthParamToExclude(); + + $headers = collect($endpointData->headers); + if ($where === 'header') { + unset($headers[$authParam]); + } + + $headers = $headers + ->union([ + 'Accept' => 'application/json', + ]) + ->map(function ($value, $header) { + // Allow users to write ['header' => '@{{value}}'] in config + // and have it rendered properly as {{value}} in the Postman collection. + $value = str_replace('@{{', '{{', $value); + + return [ + 'key' => $header, + 'value' => $value, + ]; + }) + ->values() + ->all(); + + return $headers; + } + + protected function generateUrlObject(OutputEndpointData $endpointData): array + { + $base = [ + 'host' => '{{baseUrl}}', + // Change laravel/symfony URL params ({example}) to Postman style, prefixed with a colon + 'path' => preg_replace_callback('/\{(\w+)\??}/', function ($matches) { + return ':'.$matches[1]; + }, $endpointData->uri), + ]; + + $query = []; + [$where, $authParam] = $this->getAuthParamToExclude(); + + /** + * @var string $name + * @var Parameter $parameterData + */ + foreach ($endpointData->queryParameters as $name => $parameterData) { + if ($where === 'query' && $authParam === $name) { + continue; + } + + if (Str::endsWith($parameterData->type, '[]') || $parameterData->type === 'object') { + $values = empty($parameterData->example) ? [] : $parameterData->example; + foreach ($values as $index => $value) { + // PHP's parse_str supports array query parameters as filters[0]=name&filters[1]=age OR filters[]=name&filters[]=age + // Going with the first to also support object query parameters + // See https://www.php.net/manual/en/function.parse-str.php + $query[] = [ + 'key' => "{$name}[{$index}]", + 'value' => is_string($value) ? $value : (string) $value, + 'description' => strip_tags($parameterData->description), + // Default query params to disabled if they aren't required and have empty values + 'disabled' => ! $parameterData->required && empty($parameterData->example), + ]; + } + // If there are no values, add one entry so the parameter shows up in the Postman UI. + if (empty($values)) { + $query[] = [ + 'key' => "{$name}[]", + 'value' => '', + 'description' => strip_tags($parameterData->description), + // Default query params to disabled if they aren't required and have empty values + 'disabled' => true, + ]; + } + } else { + $query[] = [ + 'key' => urlencode($name), + 'value' => $parameterData->example !== null ? urlencode($parameterData->example) : '', + 'description' => strip_tags($parameterData->description), + // Default query params to disabled if they aren't required and have empty values + 'disabled' => ! $parameterData->required && empty($parameterData->example), + ]; + } + } + + $base['query'] = $query; + + // Create raw url-parameter (Insomnia uses this on import) + $queryString = collect($base['query'])->map(function ($queryParamData) { + return $queryParamData['key'].'='.$queryParamData['value']; + })->implode('&'); + $base['raw'] = sprintf('%s/%s%s', $base['host'], $base['path'], $queryString ? "?{$queryString}" : null); + + $urlParams = collect($endpointData->urlParameters); + if ($urlParams->isEmpty()) { + return $base; + } + + $base['variable'] = $urlParams->map(function (Parameter $parameter, $name) { + return [ + 'id' => $name, + 'key' => $name, + 'value' => urlencode($parameter->example), + 'description' => $parameter->description, + ]; + })->values()->toArray(); + + return $base; + } + + protected function getResponseDescription(Response $response): string + { + if ($response->isBinary()) { + return mb_trim(str_replace('<>', '', $response->content)); + } + + $description = (string) ($response->description); + // Don't include the status code in description; see https://github.com/knuckleswtf/scribe/issues/271 + if (preg_match('/\d{3},\s+(.+)/', $description, $matches)) { + $description = $matches[1]; + } elseif ($description === (string) ($response->status)) { + $description = ''; + } + + return $description; + } + + private function getAuthParamToExclude(): array + { + if (! $this->config->get('auth.enabled')) { + return [null, null]; + } + + if (in_array($this->config->get('auth.in'), ['bearer', 'basic'])) { + return ['header', 'Authorization']; + } + + return [$this->config->get('auth.in'), $this->config->get('auth.name')]; + } + + private function getResponses(OutputEndpointData $endpoint): array + { + return collect($endpoint->responses)->map(function (Response $response) { + $headers = []; + foreach ($response->headers as $header => $value) { + $headers[] = [ + 'key' => $header, + 'value' => $value, + ]; + } + + return [ + 'header' => $headers, + 'code' => $response->status, + 'body' => $response->content, + 'name' => $this->getResponseDescription($response), + ]; + })->toArray(); + } +} diff --git a/vendor/knuckleswtf/scribe/src/Writing/Writer.php b/vendor/knuckleswtf/scribe/src/Writing/Writer.php new file mode 100644 index 00000000..d042e386 --- /dev/null +++ b/vendor/knuckleswtf/scribe/src/Writing/Writer.php @@ -0,0 +1,288 @@ + null, + 'openapi' => null, + 'html' => null, + 'blade' => null, + 'assets' => [ + 'js' => null, + 'css' => null, + 'images' => null, + ], + ]; + + protected string $laravelAssetsPath; + + public function __construct(protected DocumentationConfig $config, public PathConfig $paths) + { + $this->isStatic = $this->config->outputIsStatic(); + $this->isExternal = $this->config->outputIsExternal(); + + $this->laravelTypeOutputPath = $this->getLaravelTypeOutputPath(); + $this->staticTypeOutputPath = mb_rtrim($this->config->get('static.output_path', 'public/docs'), '/'); + + $this->laravelAssetsPath = $this->config->get('laravel.assets_directory') + ? '/'.$this->config->get('laravel.assets_directory') + : '/vendor/'.$this->paths->outputPath(); + } + + /** + * @param array $groupedEndpoints + */ + public function writeDocs(array $groupedEndpoints): void + { + // The static assets (js/, css/, and images/) always go in public/docs/. + // For 'static' docs, the output files (index.html, collection.json) go in public/docs/. + // For 'laravel' docs, the output files (index.blade.php, collection.json) + // go in resources/views/scribe/ and storage/app/scribe/ respectively. + + if ($this->isExternal) { + $this->writeOpenAPISpec($groupedEndpoints); + $this->writePostmanCollection($groupedEndpoints); + $this->writeExternalHtmlDocs(); + } else { + $this->writeHtmlDocs($groupedEndpoints); + $this->writePostmanCollection($groupedEndpoints); + $this->writeOpenAPISpec($groupedEndpoints); + } + + $this->runAfterGeneratingHook(); + } + + /** + * Generate Postman collection JSON file. + * + * @param array[] $groupedEndpoints + */ + public function generatePostmanCollection(array $groupedEndpoints): string + { + /** @var PostmanCollectionWriter $writer */ + $writer = app()->makeWith(PostmanCollectionWriter::class, ['config' => $this->config]); + + $collection = $writer->generatePostmanCollection($groupedEndpoints); + $overrides = $this->config->get('postman.overrides', []); + if (count($overrides)) { + foreach ($overrides as $key => $value) { + data_set($collection, $key, $value); + } + } + + return json_encode($collection, JSON_PRETTY_PRINT | JSON_INVALID_UTF8_SUBSTITUTE); + } + + /** + * @param array[] $groupedEndpoints + */ + public function generateOpenAPISpec(array $groupedEndpoints): string + { + /** @var OpenAPISpecWriter $writer */ + $writer = app()->makeWith(OpenAPISpecWriter::class, ['config' => $this->config]); + $spec = $writer->generateSpecContent($groupedEndpoints); + + return Yaml::dump($spec, 20, 2, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE | Yaml::DUMP_OBJECT_AS_MAP); + } + + /** + * @param array[] $groupedEndpoints + */ + public function writeHtmlDocs(array $groupedEndpoints): void + { + if ($this->isStatic) { + $outputPath = mb_rtrim($this->staticTypeOutputPath, '/').'/'; + $assetsOutputPath = $outputPath; + } else { + $outputPath = mb_rtrim($this->laravelTypeOutputPath, '/').'/'; + $assetsOutputPath = public_path().$this->laravelAssetsPath.'/'; + } + c::task( + 'Writing '.($this->isStatic ? 'HTML' : 'Blade').' docs to '.$this->makePathFriendly($outputPath).' and assets to '.$this->makePathFriendly($assetsOutputPath), + function () use ($assetsOutputPath, $outputPath, $groupedEndpoints) { + // Then we convert them to HTML, and throw in the endpoints as well. + /** @var HtmlWriter $writer */ + $writer = app()->makeWith(HtmlWriter::class, ['config' => $this->config]); + $writer->generate($groupedEndpoints, $this->paths->intermediateOutputPath(), $this->staticTypeOutputPath); + + if (! $this->isStatic) { + $this->performFinalTasksForLaravelType(); + } + + if ($this->isStatic) { + $this->generatedFiles['html'] = realpath("{$outputPath}index.html"); + } else { + $this->generatedFiles['blade'] = realpath("{$outputPath}index.blade.php"); + } + $this->generatedFiles['assets']['js'] = realpath("{$assetsOutputPath}js"); + $this->generatedFiles['assets']['css'] = realpath("{$assetsOutputPath}css"); + $this->generatedFiles['assets']['images'] = realpath("{$assetsOutputPath}images"); + + return true; + } + ); + } + + public function writeExternalHtmlDocs(): void + { + if ($this->isStatic) { + $outputPath = mb_rtrim($this->staticTypeOutputPath, '/').'/'; + $assetsOutputPath = $outputPath; + } else { + $outputPath = mb_rtrim($this->laravelTypeOutputPath, '/').'/'; + $assetsOutputPath = public_path().$this->laravelAssetsPath.'/'; + } + c::task( + 'Writing client-side HTML docs to '.$this->makePathFriendly($outputPath).' and assets to '.$this->makePathFriendly($assetsOutputPath), + function () use ($outputPath) { + /** @var ExternalHtmlWriter $writer */ + $writer = app()->makeWith(ExternalHtmlWriter::class, ['config' => $this->config]); + $writer->generate([], $this->paths->intermediateOutputPath(), $this->staticTypeOutputPath); + + if (! $this->isStatic) { + $this->performFinalTasksForLaravelType(); + } + + if ($this->isStatic) { + $this->generatedFiles['html'] = realpath("{$outputPath}index.html"); + } else { + $this->generatedFiles['blade'] = realpath("{$outputPath}index.blade.php"); + } + + return true; + } + ); + } + + protected function writePostmanCollection(array $groups): void + { + if ($this->config->get('postman.enabled', true)) { + $outputPath = $this->isStatic ? $this->staticTypeOutputPath : Storage::disk('local')->path($this->paths->outputPath()); + c::task( + 'Generating Postman collection in '.mb_rtrim($this->makePathFriendly($outputPath), '/').'/', + function () use ($groups) { + $collection = $this->generatePostmanCollection($groups); + if ($this->isStatic) { + $collectionPath = "{$this->staticTypeOutputPath}/collection.json"; + file_put_contents($collectionPath, $collection); + } else { + $outputPath = $this->paths->outputPath('collection.json'); + Storage::disk('local')->put($outputPath, $collection); + $collectionPath = Storage::disk('local')->path($outputPath); + } + + $this->generatedFiles['postman'] = realpath($collectionPath); + + return true; + } + ); + } + } + + protected function writeOpenAPISpec(array $parsedRoutes): void + { + if ($this->config->get('openapi.enabled', false) || $this->isExternal) { + $outputPath = $this->isStatic ? $this->staticTypeOutputPath : Storage::disk('local')->path($this->paths->outputPath()); + c::task( + 'Generating OpenAPI specification in '.mb_rtrim($this->makePathFriendly($outputPath), '/').'/', + function () use ($parsedRoutes) { + $spec = $this->generateOpenAPISpec($parsedRoutes); + if ($this->isStatic) { + Utils::makeDirectoryRecursive($this->staticTypeOutputPath); + $specPath = "{$this->staticTypeOutputPath}/openapi.yaml"; + file_put_contents($specPath, $spec); + } else { + $outputPath = $this->paths->outputPath('openapi.yaml'); + Storage::disk('local')->put($outputPath, $spec); + $specPath = Storage::disk('local')->path($outputPath); + } + + $this->generatedFiles['openapi'] = realpath($specPath); + + return true; + } + ); + } + } + + protected function performFinalTasksForLaravelType(): void + { + if (! is_dir($this->laravelTypeOutputPath)) { + mkdir($this->laravelTypeOutputPath, 0o777, true); + } + $publicDirectory = public_path(); + if (! is_dir($publicDirectory.$this->laravelAssetsPath)) { + mkdir($publicDirectory.$this->laravelAssetsPath, 0o777, true); + } + + // Transform output HTML to a Blade view + rename("{$this->staticTypeOutputPath}/index.html", "{$this->laravelTypeOutputPath}/index.blade.php"); + + // Move assets from public/docs to public/vendor/scribe or config('laravel.assets_directory') + // We need to do this delete first, otherwise move won't work if folder exists + Utils::deleteDirectoryAndContents($publicDirectory.$this->laravelAssetsPath); + rename("{$this->staticTypeOutputPath}/", $publicDirectory.$this->laravelAssetsPath); + + $contents = file_get_contents("{$this->laravelTypeOutputPath}/index.blade.php"); + + // Rewrite asset links to go through Laravel + $contents = preg_replace('#href="\.\./docs/css/(.+?)"#', 'href="{{ asset("'.$this->laravelAssetsPath.'/css/$1") }}"', $contents); + $contents = preg_replace('#src="\.\./docs/(js|images)/(.+?)"#', 'src="{{ asset("'.$this->laravelAssetsPath.'/$1/$2") }}"', $contents); + $contents = str_replace('href="../docs/collection.json"', 'href="{{ route("'.$this->paths->outputPath('postman', '.').'") }}"', $contents); + $contents = str_replace('href="../docs/openapi.yaml"', 'href="{{ route("'.$this->paths->outputPath('openapi', '.').'") }}"', $contents); + $contents = str_replace('url="../docs/openapi.yaml"', 'url="{{ route("'.$this->paths->outputPath('openapi', '.').'") }}"', $contents); + // With Elements theme, we'd have paths->outputPath('openapi', '.').'") }}"', $contents); + + file_put_contents("{$this->laravelTypeOutputPath}/index.blade.php", $contents); + } + + protected function runAfterGeneratingHook() + { + if (is_callable(Globals::$__afterGenerating)) { + c::info('Running `afterGenerating()` hook...'); + call_user_func_array(Globals::$__afterGenerating, [$this->generatedFiles]); + } + } + + protected function getLaravelTypeOutputPath(): ?string + { + if ($this->isStatic) { + return null; + } + + return config( + 'view.paths.0', + function_exists('base_path') ? base_path('resources/views') : 'resources/views' + ).'/'.$this->paths->outputPath(); + } + + /** + * Turn a path from (possibly) C:\projects\myapp\resources\views + * or /projects/myapp/resources/views to resources/views ie: + * - make it relative to PWD + * - normalise all slashes to forward slashes. + */ + protected function makePathFriendly(string $path): string + { + return str_replace('\\', '/', str_replace(getcwd().DIRECTORY_SEPARATOR, '', $path)); + } +} diff --git a/vendor/mpociot/reflection-docblock/.gitignore b/vendor/mpociot/reflection-docblock/.gitignore new file mode 100644 index 00000000..3ce5adbb --- /dev/null +++ b/vendor/mpociot/reflection-docblock/.gitignore @@ -0,0 +1,2 @@ +.idea +vendor diff --git a/vendor/mpociot/reflection-docblock/.travis.yml b/vendor/mpociot/reflection-docblock/.travis.yml new file mode 100644 index 00000000..eef782c4 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/.travis.yml @@ -0,0 +1,32 @@ +language: php +php: + - 5.3.3 + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + - hhvm-nightly + +matrix: + allow_failures: + - php: hhvm + - php: hhvm-nightly + +script: + - vendor/bin/phpunit + +before_script: + - sudo apt-get -qq update > /dev/null + - phpenv rehash > /dev/null + - composer selfupdate --quiet + - composer install --no-interaction --prefer-source --dev + - vendor/bin/phpunit + - composer update --no-interaction --prefer-source --dev + +notifications: + irc: "irc.freenode.org#phpdocumentor" + email: + - mike.vanriel@naenius.com + - ashnazg@php.net + - boen.robot@gmail.com diff --git a/vendor/mpociot/reflection-docblock/LICENSE b/vendor/mpociot/reflection-docblock/LICENSE new file mode 100644 index 00000000..792e4040 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2010 Mike van Riel + +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/vendor/mpociot/reflection-docblock/README.md b/vendor/mpociot/reflection-docblock/README.md new file mode 100644 index 00000000..6405d1a1 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/README.md @@ -0,0 +1,57 @@ +The ReflectionDocBlock Component [![Build Status](https://secure.travis-ci.org/phpDocumentor/ReflectionDocBlock.png)](https://travis-ci.org/phpDocumentor/ReflectionDocBlock) +================================ + +Introduction +------------ + +The ReflectionDocBlock component of phpDocumentor provides a DocBlock parser +that is 100% compatible with the [PHPDoc standard](http://phpdoc.org/docs/latest). + +With this component, a library can provide support for annotations via DocBlocks +or otherwise retrieve information that is embedded in a DocBlock. + +> **Note**: *this is a core component of phpDocumentor and is constantly being +> optimized for performance.* + +Installation +------------ + +You can install the component in the following ways: + +* Use the official Github repository (https://github.com/phpDocumentor/ReflectionDocBlock) +* Via Composer (http://packagist.org/packages/phpdocumentor/reflection-docblock) + +Usage +----- + +The ReflectionDocBlock component is designed to work in an identical fashion to +PHP's own Reflection extension (http://php.net/manual/en/book.reflection.php). + +Parsing can be initiated by instantiating the +`\phpDocumentor\Reflection\DocBlock()` class and passing it a string containing +a DocBlock (including asterisks) or by passing an object supporting the +`getDocComment()` method. + +> *Examples of objects having the `getDocComment()` method are the +> `ReflectionClass` and the `ReflectionMethod` classes of the PHP +> Reflection extension* + +Example: + + $class = new ReflectionClass('MyClass'); + $phpdoc = new \phpDocumentor\Reflection\DocBlock($class); + +or + + $docblock = <<=5.3.3" + }, + "autoload": { + "psr-0": {"Mpociot": ["src/"]} + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/mpociot/reflection-docblock/composer.lock b/vendor/mpociot/reflection-docblock/composer.lock new file mode 100644 index 00000000..f52e6eb3 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/composer.lock @@ -0,0 +1,1124 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "f2757f6bd2c8e74e1c0545708adddce5", + "content-hash": "ec3176c2d86c406b19eebe444adf9311", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-05-17 03:09:28" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-05-17 03:18:57" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/yaml", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "c5a7e7fc273c758b92b85dcb9c46149ccda89623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c5a7e7fc273c758b92b85dcb9c46149ccda89623", + "reference": "c5a7e7fc273c758b92b85dcb9c46149ccda89623", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-06-14 11:18:07" + }, + { + "name": "webmozart/assert", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.3" + }, + "platform-dev": [] +} diff --git a/vendor/mpociot/reflection-docblock/phpunit.xml.dist b/vendor/mpociot/reflection-docblock/phpunit.xml.dist new file mode 100644 index 00000000..f67ad2a2 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + + ./tests/ + + + + + ./src/ + + + diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock.php new file mode 100644 index 00000000..19ca4f50 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock.php @@ -0,0 +1,468 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection; + +use Mpociot\Reflection\DocBlock\Tag; +use Mpociot\Reflection\DocBlock\Context; +use Mpociot\Reflection\DocBlock\Location; + +/** + * Parses the DocBlock for any structure. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class DocBlock implements \Reflector +{ + /** @var string The opening line for this docblock. */ + protected $short_description = ''; + + /** + * @var DocBlock\Description The actual + * description for this docblock. + */ + protected $long_description = null; + + /** + * @var Tag[] An array containing all + * the tags in this docblock; except inline. + */ + protected $tags = array(); + + /** @var Context Information about the context of this DocBlock. */ + protected $context = null; + + /** @var Location Information about the location of this DocBlock. */ + protected $location = null; + + /** @var bool Is this DocBlock (the start of) a template? */ + protected $isTemplateStart = false; + + /** @var bool Does this DocBlock signify the end of a DocBlock template? */ + protected $isTemplateEnd = false; + + /** + * Parses the given docblock and populates the member fields. + * + * The constructor may also receive namespace information such as the + * current namespace and aliases. This information is used by some tags + * (e.g. @return, @param, etc.) to turn a relative Type into a FQCN. + * + * @param \Reflector|string $docblock A docblock comment (including + * asterisks) or reflector supporting the getDocComment method. + * @param Context $context The context in which the DocBlock + * occurs. + * @param Location $location The location within the file that this + * DocBlock occurs in. + * + * @throws \InvalidArgumentException if the given argument does not have the + * getDocComment method. + */ + public function __construct( + $docblock, + Context $context = null, + Location $location = null + ) { + if (is_object($docblock)) { + if (!method_exists($docblock, 'getDocComment')) { + throw new \InvalidArgumentException( + 'Invalid object passed; the given reflector must support ' + . 'the getDocComment method' + ); + } + + $docblock = $docblock->getDocComment(); + } + + $docblock = $this->cleanInput($docblock); + + list($templateMarker, $short, $long, $tags) = $this->splitDocBlock($docblock); + $this->isTemplateStart = $templateMarker === '#@+'; + $this->isTemplateEnd = $templateMarker === '#@-'; + $this->short_description = $short; + $this->long_description = new DocBlock\Description($long, $this); + $this->parseTags($tags); + + $this->context = $context; + $this->location = $location; + } + + /** + * Strips the asterisks from the DocBlock comment. + * + * @param string $comment String containing the comment text. + * + * @return string + */ + protected function cleanInput($comment) + { + $comment = trim( + preg_replace( + '#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]{0,1}(.*)?#u', + '$1', + $comment + ) + ); + + // reg ex above is not able to remove */ from a single line docblock + if (substr($comment, -2) == '*/') { + $comment = trim(substr($comment, 0, -2)); + } + + // normalize strings + $comment = str_replace(array("\r\n", "\r"), "\n", $comment); + + return $comment; + } + + /** + * Splits the DocBlock into a template marker, summary, description and block of tags. + * + * @param string $comment Comment to split into the sub-parts. + * + * @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split. + * @author Mike van Riel for extending the regex with template marker support. + * + * @return string[] containing the template marker (if any), summary, description and a string containing the tags. + */ + protected function splitDocBlock($comment) + { + // Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This + // method does not split tags so we return this verbatim as the fourth result (tags). This saves us the + // performance impact of running a regular expression + if (strpos($comment, '@') === 0) { + return array('', '', '', $comment); + } + + // clears all extra horizontal whitespace from the line endings to prevent parsing issues + $comment = preg_replace('/\h*$/Sum', '', $comment); + + /* + * Splits the docblock into a template marker, short description, long description and tags section + * + * - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may + * occur after it and will be stripped). + * - The short description is started from the first character until a dot is encountered followed by a + * newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing + * errors). This is optional. + * - The long description, any character until a new line is encountered followed by an @ and word + * characters (a tag). This is optional. + * - Tags; the remaining characters + * + * Big thanks to RichardJ for contributing this Regular Expression + */ + preg_match( + '/ + \A + # 1. Extract the template marker + (?:(\#\@\+|\#\@\-)\n?)? + + # 2. Extract the summary + (?: + (?! @\pL ) # The summary may not start with an @ + ( + [^\n.]+ + (?: + (?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines + [\n.] (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line + [^\n.]+ # Include anything else + )* + \.? + )? + ) + + # 3. Extract the description + (?: + \s* # Some form of whitespace _must_ precede a description because a summary must be there + (?! @\pL ) # The description may not start with an @ + ( + [^\n]+ + (?: \n+ + (?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line + [^\n]+ # Include anything else + )* + ) + )? + + # 4. Extract the tags (anything that follows) + (\s+ [\s\S]*)? # everything that follows + /ux', + $comment, + $matches + ); + array_shift($matches); + + while (count($matches) < 4) { + $matches[] = ''; + } + + return $matches; + } + + /** + * Creates the tag objects. + * + * @param string $tags Tag block to parse. + * + * @return void + */ + protected function parseTags($tags) + { + $result = array(); + $tags = trim($tags); + if ('' !== $tags) { + if ('@' !== $tags[0]) { + throw new \LogicException( + 'A tag block started with text instead of an actual tag,' + . ' this makes the tag block invalid: ' . $tags + ); + } + foreach (explode("\n", $tags) as $tag_line) { + if (isset($tag_line[0]) && ($tag_line[0] === '@')) { + $result[] = $tag_line; + } else { + $result[count($result) - 1] .= "\n" . $tag_line; + } + } + + // create proper Tag objects + foreach ($result as $key => $tag_line) { + $result[$key] = Tag::createInstance(trim($tag_line), $this); + } + } + + $this->tags = $result; + } + + /** + * Gets the text portion of the doc block. + * + * Gets the text portion (short and long description combined) of the doc + * block. + * + * @return string The text portion of the doc block. + */ + public function getText() + { + $short = $this->getShortDescription(); + $long = $this->getLongDescription()->getContents(); + + if ($long) { + return "{$short}\n\n{$long}"; + } else { + return $short; + } + } + + /** + * Set the text portion of the doc block. + * + * Sets the text portion (short and long description combined) of the doc + * block. + * + * @param string $docblock The new text portion of the doc block. + * + * @return $this This doc block. + */ + public function setText($comment) + { + list(,$short, $long) = $this->splitDocBlock($comment); + $this->short_description = $short; + $this->long_description = new DocBlock\Description($long, $this); + return $this; + } + /** + * Returns the opening line or also known as short description. + * + * @return string + */ + public function getShortDescription() + { + return $this->short_description; + } + + /** + * Returns the full description or also known as long description. + * + * @return DocBlock\Description + */ + public function getLongDescription() + { + return $this->long_description; + } + + /** + * Returns whether this DocBlock is the start of a Template section. + * + * A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker + * (`#@+`) that is appended directly after the opening `/**` of a DocBlock. + * + * An example of such an opening is: + * + * ``` + * /**#@+ + * * My DocBlock + * * / + * ``` + * + * The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all + * elements that follow until another DocBlock is found that contains the closing marker (`#@-`). + * + * @see self::isTemplateEnd() for the check whether a closing marker was provided. + * + * @return boolean + */ + public function isTemplateStart() + { + return $this->isTemplateStart; + } + + /** + * Returns whether this DocBlock is the end of a Template section. + * + * @see self::isTemplateStart() for a more complete description of the Docblock Template functionality. + * + * @return boolean + */ + public function isTemplateEnd() + { + return $this->isTemplateEnd; + } + + /** + * Returns the current context. + * + * @return Context + */ + public function getContext() + { + return $this->context; + } + + /** + * Returns the current location. + * + * @return Location + */ + public function getLocation() + { + return $this->location; + } + + /** + * Returns the tags for this DocBlock. + * + * @return Tag[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * Returns an array of tags matching the given name. If no tags are found + * an empty array is returned. + * + * @param string $name String to search by. + * + * @return Tag[] + */ + public function getTagsByName($name) + { + $result = array(); + + /** @var Tag $tag */ + foreach ($this->getTags() as $tag) { + if ($tag->getName() != $name) { + continue; + } + + $result[] = $tag; + } + + return $result; + } + + /** + * Checks if a tag of a certain type is present in this DocBlock. + * + * @param string $name Tag name to check for. + * + * @return bool + */ + public function hasTag($name) + { + /** @var Tag $tag */ + foreach ($this->getTags() as $tag) { + if ($tag->getName() == $name) { + return true; + } + } + + return false; + } + + /** + * Appends a tag at the end of the list of tags. + * + * @param Tag $tag The tag to add. + * + * @return Tag The newly added tag. + * + * @throws \LogicException When the tag belongs to a different DocBlock. + */ + public function appendTag(Tag $tag) + { + if (null === $tag->getDocBlock()) { + $tag->setDocBlock($this); + } + + if ($tag->getDocBlock() === $this) { + $this->tags[] = $tag; + } else { + throw new \LogicException( + 'This tag belongs to a different DocBlock object.' + ); + } + + return $tag; + } + + + /** + * Builds a string representation of this object. + * + * @todo determine the exact format as used by PHP Reflection and + * implement it. + * + * @return string + * @codeCoverageIgnore Not yet implemented + */ + public static function export() + { + throw new \Exception('Not yet implemented'); + } + + /** + * Returns the exported information (we should use the export static method + * BUT this throws an exception at this point). + * + * @return string + * @codeCoverageIgnore Not yet implemented + */ + public function __toString() + { + return 'Not yet implemented'; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Context.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Context.php new file mode 100644 index 00000000..176b80a3 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Context.php @@ -0,0 +1,154 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +/** + * The context in which a DocBlock occurs. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class Context +{ + /** @var string The current namespace. */ + protected $namespace = ''; + + /** @var array List of namespace aliases => Fully Qualified Namespace. */ + protected $namespace_aliases = array(); + + /** @var string Name of the structural element, within the namespace. */ + protected $lsen = ''; + + /** + * Cteates a new context. + * @param string $namespace The namespace where this DocBlock + * resides in. + * @param array $namespace_aliases List of namespace aliases => Fully + * Qualified Namespace. + * @param string $lsen Name of the structural element, within + * the namespace. + */ + public function __construct( + $namespace = '', + array $namespace_aliases = array(), + $lsen = '' + ) { + if (!empty($namespace)) { + $this->setNamespace($namespace); + } + $this->setNamespaceAliases($namespace_aliases); + $this->setLSEN($lsen); + } + + /** + * @return string The namespace where this DocBlock resides in. + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return array List of namespace aliases => Fully Qualified Namespace. + */ + public function getNamespaceAliases() + { + return $this->namespace_aliases; + } + + /** + * Returns the Local Structural Element Name. + * + * @return string Name of the structural element, within the namespace. + */ + public function getLSEN() + { + return $this->lsen; + } + + /** + * Sets a new namespace. + * + * Sets a new namespace for the context. Leading and trailing slashes are + * trimmed, and the keywords "global" and "default" are treated as aliases + * to no namespace. + * + * @param string $namespace The new namespace to set. + * + * @return $this + */ + public function setNamespace($namespace) + { + if ('global' !== $namespace + && 'default' !== $namespace + ) { + // Srip leading and trailing slash + $this->namespace = trim((string)$namespace, '\\'); + } else { + $this->namespace = ''; + } + return $this; + } + + /** + * Sets the namespace aliases, replacing all previous ones. + * + * @param array $namespace_aliases List of namespace aliases => Fully + * Qualified Namespace. + * + * @return $this + */ + public function setNamespaceAliases(array $namespace_aliases) + { + $this->namespace_aliases = array(); + foreach ($namespace_aliases as $alias => $fqnn) { + $this->setNamespaceAlias($alias, $fqnn); + } + return $this; + } + + /** + * Adds a namespace alias to the context. + * + * @param string $alias The alias name (the part after "as", or the last + * part of the Fully Qualified Namespace Name) to add. + * @param string $fqnn The Fully Qualified Namespace Name for this alias. + * Any form of leading/trailing slashes are accepted, but what will be + * stored is a name, prefixed with a slash, and no trailing slash. + * + * @return $this + */ + public function setNamespaceAlias($alias, $fqnn) + { + $this->namespace_aliases[$alias] = '\\' . trim((string)$fqnn, '\\'); + return $this; + } + + /** + * Sets a new Local Structural Element Name. + * + * Sets a new Local Structural Element Name. A local name also contains + * punctuation determining the kind of structural element (e.g. trailing "(" + * and ")" for functions and methods). + * + * @param string $lsen The new local name of a structural element. + * + * @return $this + */ + public function setLSEN($lsen) + { + $this->lsen = (string)$lsen; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Description.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Description.php new file mode 100644 index 00000000..54d64d9f --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Description.php @@ -0,0 +1,223 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +use Mpociot\Reflection\DocBlock; + +/** + * Parses a Description of a DocBlock or tag. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class Description implements \Reflector +{ + /** @var string */ + protected $contents = ''; + + /** @var array The contents, as an array of strings and Tag objects. */ + protected $parsedContents = null; + + /** @var DocBlock The DocBlock which this description belongs to. */ + protected $docblock = null; + + /** + * Populates the fields of a description. + * + * @param string $content The description's conetnts. + * @param DocBlock $docblock The DocBlock which this description belongs to. + */ + public function __construct($content, DocBlock $docblock = null) + { + $this->setContent($content)->setDocBlock($docblock); + } + + /** + * Gets the text of this description. + * + * @return string + */ + public function getContents() + { + return $this->contents; + } + + /** + * Sets the text of this description. + * + * @param string $content The new text of this description. + * + * @return $this + */ + public function setContent($content) + { + $this->contents = trim($content); + + $this->parsedContents = null; + return $this; + } + + /** + * Returns the parsed text of this description. + * + * @return array An array of strings and tag objects, in the order they + * occur within the description. + */ + public function getParsedContents() + { + if (null === $this->parsedContents) { + $this->parsedContents = preg_split( + '/\{ + # "{@}" is not a valid inline tag. This ensures that + # we do not treat it as one, but treat it literally. + (?!@\}) + # We want to capture the whole tag line, but without the + # inline tag delimiters. + (\@ + # Match everything up to the next delimiter. + [^{}]* + # Nested inline tag content should not be captured, or + # it will appear in the result separately. + (?: + # Match nested inline tags. + (?: + # Because we did not catch the tag delimiters + # earlier, we must be explicit with them here. + # Notice that this also matches "{}", as a way + # to later introduce it as an escape sequence. + \{(?1)?\} + | + # Make sure we match hanging "{". + \{ + ) + # Match content after the nested inline tag. + [^{}]* + )* # If there are more inline tags, match them as well. + # We use "*" since there may not be any nested inline + # tags. + ) + \}/Sux', + $this->contents, + null, + PREG_SPLIT_DELIM_CAPTURE + ); + + $count = count($this->parsedContents); + for ($i=1; $i<$count; $i += 2) { + $this->parsedContents[$i] = Tag::createInstance( + $this->parsedContents[$i], + $this->docblock + ); + } + + //In order to allow "literal" inline tags, the otherwise invalid + //sequence "{@}" is changed to "@", and "{}" is changed to "}". + //See unit tests for examples. + for ($i=0; $i<$count; $i += 2) { + $this->parsedContents[$i] = str_replace( + array('{@}', '{}'), + array('@', '}'), + $this->parsedContents[$i] + ); + } + } + return $this->parsedContents; + } + + /** + * Return a formatted variant of the Long Description using MarkDown. + * + * @todo this should become a more intelligent piece of code where the + * configuration contains a setting what format long descriptions are. + * + * @codeCoverageIgnore Will be removed soon, in favor of adapters at + * PhpDocumentor itself that will process text in various formats. + * + * @return string + */ + public function getFormattedContents() + { + $result = $this->contents; + + // if the long description contains a plain HTML element, surround + // it with a pre element. Please note that we explicitly used str_replace + // and not preg_replace to gain performance + if (strpos($result, '') !== false) { + $result = str_replace( + array('', "\r\n", "\n", "\r", ''), + array('
', '', '', '', '
'), + $result + ); + } + + if (class_exists('Parsedown')) { + $markdown = \Parsedown::instance(); + $result = $markdown->parse($result); + } elseif (class_exists('dflydev\markdown\MarkdownExtraParser')) { + $markdown = new \dflydev\markdown\MarkdownExtraParser(); + $result = $markdown->transformMarkdown($result); + } + + return trim($result); + } + + /** + * Gets the docblock this tag belongs to. + * + * @return DocBlock The docblock this description belongs to. + */ + public function getDocBlock() + { + return $this->docblock; + } + + /** + * Sets the docblock this tag belongs to. + * + * @param DocBlock $docblock The new docblock this description belongs to. + * Setting NULL removes any association. + * + * @return $this + */ + public function setDocBlock(DocBlock $docblock = null) + { + $this->docblock = $docblock; + + return $this; + } + + /** + * Builds a string representation of this object. + * + * @todo determine the exact format as used by PHP Reflection + * and implement it. + * + * @return void + * @codeCoverageIgnore Not yet implemented + */ + public static function export() + { + throw new \Exception('Not yet implemented'); + } + + /** + * Returns the long description as a string. + * + * @return string + */ + public function __toString() + { + return $this->getContents(); + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Location.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Location.php new file mode 100644 index 00000000..998516b3 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Location.php @@ -0,0 +1,76 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +/** + * The location a DocBlock occurs within a file. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class Location +{ + /** @var int Line where the DocBlock text starts. */ + protected $lineNumber = 0; + + /** @var int Column where the DocBlock text starts. */ + protected $columnNumber = 0; + + public function __construct( + $lineNumber = 0, + $columnNumber = 0 + ) { + $this->setLineNumber($lineNumber)->setColumnNumber($columnNumber); + } + + /** + * @return int Line where the DocBlock text starts. + */ + public function getLineNumber() + { + return $this->lineNumber; + } + + /** + * + * @param type $lineNumber + * @return $this + */ + public function setLineNumber($lineNumber) + { + $this->lineNumber = (int)$lineNumber; + + return $this; + } + + /** + * @return int Column where the DocBlock text starts. + */ + public function getColumnNumber() + { + return $this->columnNumber; + } + + /** + * + * @param int $columnNumber + * @return $this + */ + public function setColumnNumber($columnNumber) + { + $this->columnNumber = (int)$columnNumber; + + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Serializer.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Serializer.php new file mode 100644 index 00000000..2b7aaf59 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Serializer.php @@ -0,0 +1,198 @@ + + * @copyright 2013 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +use Mpociot\Reflection\DocBlock; + +/** + * Serializes a DocBlock instance. + * + * @author Barry vd. Heuvel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class Serializer +{ + + /** @var string The string to indent the comment with. */ + protected $indentString = ' '; + + /** @var int The number of times the indent string is repeated. */ + protected $indent = 0; + + /** @var bool Whether to indent the first line. */ + protected $isFirstLineIndented = true; + + /** @var int|null The max length of a line. */ + protected $lineLength = null; + + /** + * Create a Serializer instance. + * + * @param int $indent The number of times the indent string is + * repeated. + * @param string $indentString The string to indent the comment with. + * @param bool $indentFirstLine Whether to indent the first line. + * @param int|null $lineLength The max length of a line or NULL to + * disable line wrapping. + */ + public function __construct( + $indent = 0, + $indentString = ' ', + $indentFirstLine = true, + $lineLength = null + ) { + $this->setIndentationString($indentString); + $this->setIndent($indent); + $this->setIsFirstLineIndented($indentFirstLine); + $this->setLineLength($lineLength); + } + + /** + * Sets the string to indent comments with. + * + * @param string $indentationString The string to indent comments with. + * + * @return $this This serializer object. + */ + public function setIndentationString($indentString) + { + $this->indentString = (string)$indentString; + return $this; + } + + /** + * Gets the string to indent comments with. + * + * @return string The indent string. + */ + public function getIndentationString() + { + return $this->indentString; + } + + /** + * Sets the number of indents. + * + * @param int $indent The number of times the indent string is repeated. + * + * @return $this This serializer object. + */ + public function setIndent($indent) + { + $this->indent = (int)$indent; + return $this; + } + + /** + * Gets the number of indents. + * + * @return int The number of times the indent string is repeated. + */ + public function getIndent() + { + return $this->indent; + } + + /** + * Sets whether or not the first line should be indented. + * + * Sets whether or not the first line (the one with the "/**") should be + * indented. + * + * @param bool $indentFirstLine The new value for this setting. + * + * @return $this This serializer object. + */ + public function setIsFirstLineIndented($indentFirstLine) + { + $this->isFirstLineIndented = (bool)$indentFirstLine; + return $this; + } + + /** + * Gets whether or not the first line should be indented. + * + * @return bool Whether or not the first line should be indented. + */ + public function isFirstLineIndented() + { + return $this->isFirstLineIndented; + } + + /** + * Sets the line length. + * + * Sets the length of each line in the serialization. Content will be + * wrapped within this limit. + * + * @param int|null $lineLength The length of each line. NULL to disable line + * wrapping altogether. + * + * @return $this This serializer object. + */ + public function setLineLength($lineLength) + { + $this->lineLength = null === $lineLength ? null : (int)$lineLength; + return $this; + } + + /** + * Gets the line length. + * + * @return int|null The length of each line or NULL if line wrapping is + * disabled. + */ + public function getLineLength() + { + return $this->lineLength; + } + + /** + * Generate a DocBlock comment. + * + * @param DocBlock The DocBlock to serialize. + * + * @return string The serialized doc block. + */ + public function getDocComment(DocBlock $docblock) + { + $indent = str_repeat($this->indentString, $this->indent); + $firstIndent = $this->isFirstLineIndented ? $indent : ''; + + $text = $docblock->getText(); + if ($this->lineLength) { + //3 === strlen(' * ') + $wrapLength = $this->lineLength - strlen($indent) - 3; + $text = wordwrap($text, $wrapLength); + } + $text = str_replace("\n", "\n{$indent} * ", $text); + + $comment = "{$firstIndent}/**\n{$indent} * {$text}\n{$indent} *\n"; + + /** @var Tag $tag */ + foreach ($docblock->getTags() as $tag) { + $tagText = (string) $tag; + if ($this->lineLength) { + $tagText = wordwrap($tagText, $wrapLength); + } + $tagText = str_replace("\n", "\n{$indent} * ", $tagText); + + $comment .= "{$indent} * {$tagText}\n"; + } + + $comment .= $indent . ' */'; + + return $comment; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag.php new file mode 100644 index 00000000..b60df560 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag.php @@ -0,0 +1,377 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +use Mpociot\Reflection\DocBlock; + +/** + * Parses a tag definition for a DocBlock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class Tag implements \Reflector +{ + /** + * PCRE regular expression matching a tag name. + */ + const REGEX_TAGNAME = '[\w\-\_\\\\]+'; + + /** @var string Name of the tag */ + protected $tag = ''; + + /** + * @var string|null Content of the tag. + * When set to NULL, it means it needs to be regenerated. + */ + protected $content = ''; + + /** @var string Description of the content of this tag */ + protected $description = ''; + + /** + * @var array|null The description, as an array of strings and Tag objects. + * When set to NULL, it means it needs to be regenerated. + */ + protected $parsedDescription = null; + + /** @var Location Location of the tag. */ + protected $location = null; + + /** @var DocBlock The DocBlock which this tag belongs to. */ + protected $docblock = null; + + /** + * @var array An array with a tag as a key, and an FQCN to a class that + * handles it as an array value. The class is expected to inherit this + * class. + */ + private static $tagHandlerMappings = array( + 'author' + => '\Mpociot\Reflection\DocBlock\Tag\AuthorTag', + 'covers' + => '\Mpociot\Reflection\DocBlock\Tag\CoversTag', + 'deprecated' + => '\Mpociot\Reflection\DocBlock\Tag\DeprecatedTag', + 'example' + => '\Mpociot\Reflection\DocBlock\Tag\ExampleTag', + 'link' + => '\Mpociot\Reflection\DocBlock\Tag\LinkTag', + 'method' + => '\Mpociot\Reflection\DocBlock\Tag\MethodTag', + 'param' + => '\Mpociot\Reflection\DocBlock\Tag\ParamTag', + 'property-read' + => '\Mpociot\Reflection\DocBlock\Tag\PropertyReadTag', + 'property' + => '\Mpociot\Reflection\DocBlock\Tag\PropertyTag', + 'property-write' + => '\Mpociot\Reflection\DocBlock\Tag\PropertyWriteTag', + 'return' + => '\Mpociot\Reflection\DocBlock\Tag\ReturnTag', + 'see' + => '\Mpociot\Reflection\DocBlock\Tag\SeeTag', + 'since' + => '\Mpociot\Reflection\DocBlock\Tag\SinceTag', + 'source' + => '\Mpociot\Reflection\DocBlock\Tag\SourceTag', + 'throw' + => '\Mpociot\Reflection\DocBlock\Tag\ThrowsTag', + 'throws' + => '\Mpociot\Reflection\DocBlock\Tag\ThrowsTag', + 'uses' + => '\Mpociot\Reflection\DocBlock\Tag\UsesTag', + 'var' + => '\Mpociot\Reflection\DocBlock\Tag\VarTag', + 'version' + => '\Mpociot\Reflection\DocBlock\Tag\VersionTag' + ); + + /** + * Factory method responsible for instantiating the correct sub type. + * + * @param string $tag_line The text for this tag, including description. + * @param DocBlock $docblock The DocBlock which this tag belongs to. + * @param Location $location Location of the tag. + * + * @throws \InvalidArgumentException if an invalid tag line was presented. + * + * @return static A new tag object. + */ + final public static function createInstance( + $tag_line, + DocBlock $docblock = null, + Location $location = null + ) { + if (!preg_match( + '/^@(' . self::REGEX_TAGNAME . ')(?:\s*([^\s].*)|$)?/us', + $tag_line, + $matches + )) { + throw new \InvalidArgumentException( + 'Invalid tag_line detected: ' . $tag_line + ); + } + + $handler = __CLASS__; + if (isset(self::$tagHandlerMappings[$matches[1]])) { + $handler = self::$tagHandlerMappings[$matches[1]]; + } elseif (isset($docblock)) { + $tagName = (string)new Type\Collection( + array($matches[1]), + $docblock->getContext() + ); + + if (isset(self::$tagHandlerMappings[$tagName])) { + $handler = self::$tagHandlerMappings[$tagName]; + } + } + + return new $handler( + $matches[1], + isset($matches[2]) ? $matches[2] : '', + $docblock, + $location + ); + } + + /** + * Registers a handler for tags. + * + * Registers a handler for tags. The class specified is autoloaded if it's + * not available. It must inherit from this class. + * + * @param string $tag Name of tag to regiser a handler for. When + * registering a namespaced tag, the full name, along with a prefixing + * slash MUST be provided. + * @param string|null $handler FQCN of handler. Specifing NULL removes the + * handler for the specified tag, if any. + * + * @return bool TRUE on success, FALSE on failure. + */ + final public static function registerTagHandler($tag, $handler) + { + $tag = trim((string)$tag); + + if (null === $handler) { + unset(self::$tagHandlerMappings[$tag]); + return true; + } + + if ('' !== $tag + && class_exists($handler, true) + && is_subclass_of($handler, __CLASS__) + && !strpos($tag, '\\') //Accept no slash, and 1st slash at offset 0. + ) { + self::$tagHandlerMappings[$tag] = $handler; + return true; + } + + return false; + } + + /** + * Parses a tag and populates the member variables. + * + * @param string $name Name of the tag. + * @param string $content The contents of the given tag. + * @param DocBlock $docblock The DocBlock which this tag belongs to. + * @param Location $location Location of the tag. + */ + public function __construct( + $name, + $content, + DocBlock $docblock = null, + Location $location = null + ) { + $this + ->setName($name) + ->setContent($content) + ->setDocBlock($docblock) + ->setLocation($location); + } + + /** + * Gets the name of this tag. + * + * @return string The name of this tag. + */ + public function getName() + { + return $this->tag; + } + + /** + * Sets the name of this tag. + * + * @param string $name The new name of this tag. + * + * @return $this + * @throws \InvalidArgumentException When an invalid tag name is provided. + */ + public function setName($name) + { + if (!preg_match('/^' . self::REGEX_TAGNAME . '$/u', $name)) { + throw new \InvalidArgumentException( + 'Invalid tag name supplied: ' . $name + ); + } + + $this->tag = $name; + + return $this; + } + + /** + * Gets the content of this tag. + * + * @return string + */ + public function getContent() + { + if (null === $this->content) { + $this->content = $this->description; + } + + return $this->content; + } + + /** + * Sets the content of this tag. + * + * @param string $content The new content of this tag. + * + * @return $this + */ + public function setContent($content) + { + $this->setDescription($content); + $this->content = $content; + + return $this; + } + + /** + * Gets the description component of this tag. + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the description component of this tag. + * + * @param string $description The new description component of this tag. + * + * @return $this + */ + public function setDescription($description) + { + $this->content = null; + $this->parsedDescription = null; + $this->description = trim($description); + + return $this; + } + + /** + * Gets the parsed text of this description. + * + * @return array An array of strings and tag objects, in the order they + * occur within the description. + */ + public function getParsedDescription() + { + if (null === $this->parsedDescription) { + $description = new Description($this->description, $this->docblock); + $this->parsedDescription = $description->getParsedContents(); + } + return $this->parsedDescription; + } + + /** + * Gets the docblock this tag belongs to. + * + * @return DocBlock The docblock this tag belongs to. + */ + public function getDocBlock() + { + return $this->docblock; + } + + /** + * Sets the docblock this tag belongs to. + * + * @param DocBlock $docblock The new docblock this tag belongs to. Setting + * NULL removes any association. + * + * @return $this + */ + public function setDocBlock(DocBlock $docblock = null) + { + $this->docblock = $docblock; + + return $this; + } + + /** + * Gets the location of the tag. + * + * @return Location The tag's location. + */ + public function getLocation() + { + return $this->location; + } + + /** + * Sets the location of the tag. + * + * @param Location $location The new location of the tag. + * + * @return $this + */ + public function setLocation(Location $location = null) + { + $this->location = $location; + + return $this; + } + + /** + * Builds a string representation of this object. + * + * @todo determine the exact format as used by PHP Reflection and implement it. + * + * @return void + * @codeCoverageIgnore Not yet implemented + */ + public static function export() + { + throw new \Exception('Not yet implemented'); + } + + /** + * Returns the tag as a serialized string + * + * @return string + */ + public function __toString() + { + return "@{$this->getName()} {$this->getContent()}"; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/AuthorTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/AuthorTag.php new file mode 100644 index 00000000..ebfd589e --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/AuthorTag.php @@ -0,0 +1,131 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for an @author tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class AuthorTag extends Tag +{ + /** + * PCRE regular expression matching any valid value for the name component. + */ + const REGEX_AUTHOR_NAME = '[^\<]*'; + + /** + * PCRE regular expression matching any valid value for the email component. + */ + const REGEX_AUTHOR_EMAIL = '[^\>]*'; + + /** @var string The name of the author */ + protected $authorName = ''; + + /** @var string The email of the author */ + protected $authorEmail = ''; + + public function getContent() + { + if (null === $this->content) { + $this->content = $this->authorName; + if ('' != $this->authorEmail) { + $this->content .= "<{$this->authorEmail}>"; + } + } + + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + parent::setContent($content); + if (preg_match( + '/^(' . self::REGEX_AUTHOR_NAME . + ')(\<(' . self::REGEX_AUTHOR_EMAIL . + ')\>)?$/u', + $this->description, + $matches + )) { + $this->authorName = trim($matches[1]); + if (isset($matches[3])) { + $this->authorEmail = trim($matches[3]); + } + } + + return $this; + } + + /** + * Gets the author's name. + * + * @return string The author's name. + */ + public function getAuthorName() + { + return $this->authorName; + } + + /** + * Sets the author's name. + * + * @param string $authorName The new author name. + * An invalid value will set an empty string. + * + * @return $this + */ + public function setAuthorName($authorName) + { + $this->content = null; + $this->authorName + = preg_match('/^' . self::REGEX_AUTHOR_NAME . '$/u', $authorName) + ? $authorName : ''; + + return $this; + } + + /** + * Gets the author's email. + * + * @return string The author's email. + */ + public function getAuthorEmail() + { + return $this->authorEmail; + } + + /** + * Sets the author's email. + * + * @param string $authorEmail The new author email. + * An invalid value will set an empty string. + * + * @return $this + */ + public function setAuthorEmail($authorEmail) + { + $this->authorEmail + = preg_match('/^' . self::REGEX_AUTHOR_EMAIL . '$/u', $authorEmail) + ? $authorEmail : ''; + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/CoversTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/CoversTag.php new file mode 100644 index 00000000..9ae09fe0 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/CoversTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @covers tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class CoversTag extends SeeTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/DeprecatedTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/DeprecatedTag.php new file mode 100644 index 00000000..8a6e55eb --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/DeprecatedTag.php @@ -0,0 +1,26 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag\VersionTag; + +/** + * Reflection class for a @deprecated tag in a Docblock. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class DeprecatedTag extends VersionTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ExampleTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ExampleTag.php new file mode 100644 index 00000000..1a0fa737 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ExampleTag.php @@ -0,0 +1,156 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @example tag in a Docblock. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ExampleTag extends SourceTag +{ + /** + * @var string Path to a file to use as an example. + * May also be an absolute URI. + */ + protected $filePath = ''; + + /** + * @var bool Whether the file path component represents an URI. + * This determines how the file portion appears at {@link getContent()}. + */ + protected $isURI = false; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $filePath = ''; + if ($this->isURI) { + if (false === strpos($this->filePath, ':')) { + $filePath = str_replace( + '%2F', + '/', + rawurlencode($this->filePath) + ); + } else { + $filePath = $this->filePath; + } + } else { + $filePath = '"' . $this->filePath . '"'; + } + + $this->content = $filePath . ' ' . parent::getContent(); + } + + return $this->content; + } + /** + * {@inheritdoc} + */ + public function setContent($content) + { + Tag::setContent($content); + if (preg_match( + '/^ + # File component + (?: + # File path in quotes + \"([^\"]+)\" + | + # File URI + (\S+) + ) + # Remaining content (parsed by SourceTag) + (?:\s+(.*))? + $/sux', + $this->description, + $matches + )) { + if ('' !== $matches[1]) { + $this->setFilePath($matches[1]); + } else { + $this->setFileURI($matches[2]); + } + + if (isset($matches[3])) { + parent::setContent($matches[3]); + } else { + $this->setDescription(''); + } + $this->content = $content; + } + + return $this; + } + + /** + * Returns the file path. + * + * @return string Path to a file to use as an example. + * May also be an absolute URI. + */ + public function getFilePath() + { + return $this->filePath; + } + + /** + * Sets the file path. + * + * @param string $filePath The new file path to use for the example. + * + * @return $this + */ + public function setFilePath($filePath) + { + $this->isURI = false; + $this->filePath = trim($filePath); + + $this->content = null; + return $this; + } + + /** + * Sets the file path as an URI. + * + * This function is equivalent to {@link setFilePath()}, except that it + * convers an URI to a file path before that. + * + * There is no getFileURI(), as {@link getFilePath()} is compatible. + * + * @param type $uri The new file URI to use as an example. + */ + public function setFileURI($uri) + { + $this->isURI = true; + if (false === strpos($uri, ':')) { + //Relative URL + $this->filePath = rawurldecode( + str_replace(array('/', '\\'), '%2F', $uri) + ); + } else { + //Absolute URL or URI. + $this->filePath = $uri; + } + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/LinkTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/LinkTag.php new file mode 100644 index 00000000..df3fd12b --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/LinkTag.php @@ -0,0 +1,81 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @link tag in a Docblock. + * + * @author Ben Selby + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class LinkTag extends Tag +{ + /** @var string */ + protected $link = ''; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $this->content = "{$this->link} {$this->description}"; + } + + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + parent::setContent($content); + $parts = preg_split('/\s+/Su', $this->description, 2); + + $this->link = $parts[0]; + + $this->setDescription(isset($parts[1]) ? $parts[1] : $parts[0]); + + $this->content = $content; + return $this; + } + + /** + * Gets the link + * + * @return string + */ + public function getLink() + { + return $this->link; + } + + /** + * Sets the link + * + * @param string $link The link + * + * @return $this + */ + public function setLink($link) + { + $this->link = $link; + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/MethodTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/MethodTag.php new file mode 100644 index 00000000..8a64195b --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/MethodTag.php @@ -0,0 +1,209 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @method in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class MethodTag extends ReturnTag +{ + + /** @var string */ + protected $method_name = ''; + + /** @var string */ + protected $arguments = ''; + + /** @var bool */ + protected $isStatic = false; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $this->content = ''; + if ($this->isStatic) { + $this->content .= 'static '; + } + $this->content .= $this->type . + " {$this->method_name}({$this->arguments}) " . + $this->description; + } + + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + Tag::setContent($content); + // 1. none or more whitespace + // 2. optionally the keyword "static" followed by whitespace + // 3. optionally a word with underscores followed by whitespace : as + // type for the return value + // 4. then optionally a word with underscores followed by () and + // whitespace : as method name as used by phpDocumentor + // 5. then a word with underscores, followed by ( and any character + // until a ) and whitespace : as method name with signature + // 6. any remaining text : as description + if (preg_match( + '/^ + # Static keyword + # Declates a static method ONLY if type is also present + (?: + (static) + \s+ + )? + # Return type + (?: + ([\w\|_\\\\]+) + \s+ + )? + # Legacy method name (not captured) + (?: + [\w_]+\(\)\s+ + )? + # Method name + ([\w\|_\\\\]+) + # Arguments + \(([^\)]*)\) + \s* + # Description + (.*) + $/sux', + $this->description, + $matches + )) { + list( + , + $static, + $this->type, + $this->method_name, + $this->arguments, + $this->description + ) = $matches; + if ($static) { + if (!$this->type) { + $this->type = 'static'; + } else { + $this->isStatic = true; + } + } else { + if (!$this->type) { + $this->type = 'void'; + } + } + $this->parsedDescription = null; + } + + return $this; + } + + /** + * Sets the name of this method. + * + * @param string $method_name The name of the method. + * + * @return $this + */ + public function setMethodName($method_name) + { + $this->method_name = $method_name; + + $this->content = null; + return $this; + } + + /** + * Retrieves the method name. + * + * @return string + */ + public function getMethodName() + { + return $this->method_name; + } + + /** + * Sets the arguments for this method. + * + * @param string $arguments A comma-separated arguments line. + * + * @return void + */ + public function setArguments($arguments) + { + $this->arguments = $arguments; + + $this->content = null; + return $this; + } + + /** + * Returns an array containing each argument as array of type and name. + * + * Please note that the argument sub-array may only contain 1 element if no + * type was specified. + * + * @return string[] + */ + public function getArguments() + { + if (empty($this->arguments)) { + return array(); + } + + $arguments = explode(',', $this->arguments); + foreach ($arguments as $key => $value) { + $arguments[$key] = explode(' ', trim($value)); + } + + return $arguments; + } + + /** + * Checks whether the method tag describes a static method or not. + * + * @return bool TRUE if the method declaration is for a static method, FALSE + * otherwise. + */ + public function isStatic() + { + return $this->isStatic; + } + + /** + * Sets a new value for whether the method is static or not. + * + * @param bool $isStatic The new value to set. + * + * @return $this + */ + public function setIsStatic($isStatic) + { + $this->isStatic = $isStatic; + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ParamTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ParamTag.php new file mode 100644 index 00000000..60c41692 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ParamTag.php @@ -0,0 +1,119 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @param tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ParamTag extends ReturnTag +{ + /** @var string */ + protected $variableName = ''; + + /** @var bool determines whether this is a variadic argument */ + protected $isVariadic = false; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $this->content + = "{$this->type} {$this->variableName} {$this->description}"; + } + return $this->content; + } + /** + * {@inheritdoc} + */ + public function setContent($content) + { + Tag::setContent($content); + $parts = preg_split( + '/(\s+)/Su', + $this->description, + 3, + PREG_SPLIT_DELIM_CAPTURE + ); + + // if the first item that is encountered is not a variable; it is a type + if (isset($parts[0]) + && (strlen($parts[0]) > 0) + && ($parts[0][0] !== '$') + ) { + $this->type = array_shift($parts); + array_shift($parts); + } + + // if the next item starts with a $ or ...$ it must be the variable name + if (isset($parts[0]) + && (strlen($parts[0]) > 0) + && ($parts[0][0] == '$' || substr($parts[0], 0, 4) === '...$') + ) { + $this->variableName = array_shift($parts); + array_shift($parts); + + if (substr($this->variableName, 0, 3) === '...') { + $this->isVariadic = true; + $this->variableName = substr($this->variableName, 3); + } + } + + $this->setDescription(implode('', $parts)); + + $this->content = $content; + return $this; + } + + /** + * Returns the variable's name. + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * Sets the variable's name. + * + * @param string $name The new name for this variable. + * + * @return $this + */ + public function setVariableName($name) + { + $this->variableName = $name; + + $this->content = null; + return $this; + } + + /** + * Returns whether this tag is variadic. + * + * @return boolean + */ + public function isVariadic() + { + return $this->isVariadic; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyReadTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyReadTag.php new file mode 100644 index 00000000..ee7bd930 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyReadTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @property-read tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class PropertyReadTag extends PropertyTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyTag.php new file mode 100644 index 00000000..59a4f400 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @property tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class PropertyTag extends ParamTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyWriteTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyWriteTag.php new file mode 100644 index 00000000..c92e735d --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/PropertyWriteTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @property-write tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class PropertyWriteTag extends PropertyTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ReturnTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ReturnTag.php new file mode 100644 index 00000000..0ce5c34e --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ReturnTag.php @@ -0,0 +1,99 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; +use Mpociot\Reflection\DocBlock\Type\Collection; + +/** + * Reflection class for a @return tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ReturnTag extends Tag +{ + /** @var string The raw type component. */ + protected $type = ''; + + /** @var Collection The parsed type component. */ + protected $types = null; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $this->content = "{$this->type} {$this->description}"; + } + + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + parent::setContent($content); + + $parts = preg_split('/\s+/Su', $this->description, 2); + + // any output is considered a type + $this->type = $parts[0]; + $this->types = null; + + $this->setDescription(isset($parts[1]) ? $parts[1] : ''); + + $this->content = $content; + return $this; + } + + /** + * Returns the unique types of the variable. + * + * @return string[] + */ + public function getTypes() + { + return $this->getTypesCollection()->getArrayCopy(); + } + + /** + * Returns the type section of the variable. + * + * @return string + */ + public function getType() + { + return (string) $this->getTypesCollection(); + } + + /** + * Returns the type collection. + * + * @return void + */ + protected function getTypesCollection() + { + if (null === $this->types) { + $this->types = new Collection( + array($this->type), + $this->docblock ? $this->docblock->getContext() : null + ); + } + return $this->types; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SeeTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SeeTag.php new file mode 100644 index 00000000..bd73f316 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SeeTag.php @@ -0,0 +1,81 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @see tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class SeeTag extends Tag +{ + /** @var string */ + protected $refers = null; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $this->content = "{$this->refers} {$this->description}"; + } + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + parent::setContent($content); + $parts = preg_split('/\s+/Su', $this->description, 2); + + // any output is considered a type + $this->refers = $parts[0]; + + $this->setDescription(isset($parts[1]) ? $parts[1] : ''); + + $this->content = $content; + return $this; + } + + /** + * Gets the structural element this tag refers to. + * + * @return string + */ + public function getReference() + { + return $this->refers; + } + + /** + * Sets the structural element this tag refers to. + * + * @param string $refers The new type this tag refers to. + * + * @return $this + */ + public function setReference($refers) + { + $this->refers = $refers; + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SinceTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SinceTag.php new file mode 100644 index 00000000..a306193f --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SinceTag.php @@ -0,0 +1,26 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag\VersionTag; + +/** + * Reflection class for a @since tag in a Docblock. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class SinceTag extends VersionTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SourceTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SourceTag.php new file mode 100644 index 00000000..43312579 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/SourceTag.php @@ -0,0 +1,137 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @source tag in a Docblock. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class SourceTag extends Tag +{ + /** + * @var int The starting line, relative to the structural element's + * location. + */ + protected $startingLine = 1; + + /** + * @var int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + protected $lineCount = null; + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->content) { + $this->content + = "{$this->startingLine} {$this->lineCount} {$this->description}"; + } + + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + parent::setContent($content); + if (preg_match( + '/^ + # Starting line + ([1-9]\d*) + \s* + # Number of lines + (?: + ((?1)) + \s+ + )? + # Description + (.*) + $/sux', + $this->description, + $matches + )) { + $this->startingLine = (int)$matches[1]; + if (isset($matches[2]) && '' !== $matches[2]) { + $this->lineCount = (int)$matches[2]; + } + $this->setDescription($matches[3]); + $this->content = $content; + } + + return $this; + } + + /** + * Gets the starting line. + * + * @return int The starting line, relative to the structural element's + * location. + */ + public function getStartingLine() + { + return $this->startingLine; + } + + /** + * Sets the starting line. + * + * @param int $startingLine The new starting line, relative to the + * structural element's location. + * + * @return $this + */ + public function setStartingLine($startingLine) + { + $this->startingLine = $startingLine; + + $this->content = null; + return $this; + } + + /** + * Returns the number of lines. + * + * @return int|null The number of lines, relative to the starting line. NULL + * means "to the end". + */ + public function getLineCount() + { + return $this->lineCount; + } + + /** + * Sets the number of lines. + * + * @param int|null $lineCount The new number of lines, relative to the + * starting line. NULL means "to the end". + * + * @return $this + */ + public function setLineCount($lineCount) + { + $this->lineCount = $lineCount; + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ThrowsTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ThrowsTag.php new file mode 100644 index 00000000..b378d5f7 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/ThrowsTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @throws tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ThrowsTag extends ReturnTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/UsesTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/UsesTag.php new file mode 100644 index 00000000..0ea384d4 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/UsesTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @uses tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class UsesTag extends SeeTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VarTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VarTag.php new file mode 100644 index 00000000..64653cc0 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VarTag.php @@ -0,0 +1,24 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @var tag in a Docblock. + * + * @author Mike van Riel + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class VarTag extends ParamTag +{ +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VersionTag.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VersionTag.php new file mode 100644 index 00000000..c46140c7 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Tag/VersionTag.php @@ -0,0 +1,108 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +use Mpociot\Reflection\DocBlock\Tag; + +/** + * Reflection class for a @version tag in a Docblock. + * + * @author Vasil Rangelov + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class VersionTag extends Tag +{ + /** + * PCRE regular expression matching a version vector. + * Assumes the "x" modifier. + */ + const REGEX_VECTOR = '(?: + # Normal release vectors. + \d\S* + | + # VCS version vectors. Per PHPCS, they are expected to + # follow the form of the VCS name, followed by ":", followed + # by the version vector itself. + # By convention, popular VCSes like CVS, SVN and GIT use "$" + # around the actual version vector. + [^\s\:]+\:\s*\$[^\$]+\$ + )'; + + /** @var string The version vector. */ + protected $version = ''; + + public function getContent() + { + if (null === $this->content) { + $this->content = "{$this->version} {$this->description}"; + } + + return $this->content; + } + + /** + * {@inheritdoc} + */ + public function setContent($content) + { + parent::setContent($content); + + if (preg_match( + '/^ + # The version vector + (' . self::REGEX_VECTOR . ') + \s* + # The description + (.+)? + $/sux', + $this->description, + $matches + )) { + $this->version = $matches[1]; + $this->setDescription(isset($matches[2]) ? $matches[2] : ''); + $this->content = $content; + } + + return $this; + } + + /** + * Gets the version section of the tag. + * + * @return string The version section of the tag. + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the version section of the tag. + * + * @param string $version The new version section of the tag. + * An invalid value will set an empty string. + * + * @return $this + */ + public function setVersion($version) + { + $this->version + = preg_match('/^' . self::REGEX_VECTOR . '$/ux', $version) + ? $version + : ''; + + $this->content = null; + return $this; + } +} diff --git a/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Type/Collection.php b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Type/Collection.php new file mode 100644 index 00000000..9686f783 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/src/Mpociot/Reflection/DocBlock/Type/Collection.php @@ -0,0 +1,228 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Type; + +use Mpociot\Reflection\DocBlock\Context; + +/** + * Collection + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class Collection extends \ArrayObject +{ + /** @var string Definition of the OR operator for types */ + const OPERATOR_OR = '|'; + + /** @var string Definition of the ARRAY operator for types */ + const OPERATOR_ARRAY = '[]'; + + /** @var string Definition of the NAMESPACE operator in PHP */ + const OPERATOR_NAMESPACE = '\\'; + + /** @var string[] List of recognized keywords */ + protected static $keywords = array( + 'string', 'int', 'integer', 'bool', 'boolean', 'float', 'double', + 'object', 'mixed', 'array', 'resource', 'void', 'null', 'scalar', + 'callback', 'callable', 'false', 'true', 'self', '$this', 'static' + ); + + /** + * Current invoking location. + * + * This is used to prepend to type with a relative location. + * May also be 'default' or 'global', in which case they are ignored. + * + * @var Context + */ + protected $context = null; + + /** + * Registers the namespace and aliases; uses that to add and expand the + * given types. + * + * @param string[] $types Array containing a list of types to add to this + * container. + * @param Context $location The current invoking location. + */ + public function __construct( + array $types = array(), + Context $context = null + ) { + $this->context = null === $context ? new Context() : $context; + + foreach ($types as $type) { + $this->add($type); + } + } + + /** + * Returns the current invoking location. + * + * @return Context + */ + public function getContext() + { + return $this->context; + } + + /** + * Adds a new type to the collection and expands it if it contains a + * relative namespace. + * + * If a class in the type contains a relative namespace than this collection + * will try to expand that into a FQCN. + * + * @param string $type A 'Type' as defined in the phpDocumentor + * documentation. + * + * @throws \InvalidArgumentException if a non-string argument is passed. + * + * @see http://phpdoc.org/docs/latest/for-users/types.html for the + * definition of a type. + * + * @return void + */ + public function add($type) + { + if (!is_string($type)) { + throw new \InvalidArgumentException( + 'A type should be represented by a string, received: ' + .var_export($type, true) + ); + } + + // separate the type by the OR operator + $type_parts = explode(self::OPERATOR_OR, $type); + foreach ($type_parts as $part) { + $expanded_type = $this->expand($part); + if ($expanded_type) { + $this[] = $expanded_type; + } + } + } + + /** + * Returns a string representation of the collection. + * + * @return string The resolved types across the collection, separated with + * {@link self::OPERATOR_OR}. + */ + public function __toString() + { + return implode(self::OPERATOR_OR, $this->getArrayCopy()); + } + + /** + * Analyzes the given type and returns the FQCN variant. + * + * When a type is provided this method checks whether it is not a keyword or + * Fully Qualified Class Name. If so it will use the given namespace and + * aliases to expand the type to a FQCN representation. + * + * This method only works as expected if the namespace and aliases are set; + * no dynamic reflection is being performed here. + * + * @param string $type The relative or absolute type. + * + * @uses getNamespace to determine with what to prefix the type name. + * @uses getNamespaceAliases to check whether the first part of the relative + * type name should not be replaced with another namespace. + * + * @return string + */ + protected function expand($type) + { + $type = trim($type); + if (!$type) { + return ''; + } + + if ($this->isTypeAnArray($type)) { + return $this->expand(substr($type, 0, -2)) . self::OPERATOR_ARRAY; + } + + if ($this->isRelativeType($type) && !$this->isTypeAKeyword($type)) { + $type_parts = explode(self::OPERATOR_NAMESPACE, $type, 2); + + $namespace_aliases = $this->context->getNamespaceAliases(); + // if the first segment is not an alias; prepend namespace name and + // return + if (!isset($namespace_aliases[$type_parts[0]]) && + !isset($namespace_aliases[strstr($type_parts[0], '::', true)])) { + $namespace = $this->context->getNamespace(); + if ('' !== $namespace) { + $namespace .= self::OPERATOR_NAMESPACE; + } + return self::OPERATOR_NAMESPACE . $namespace . $type; + } + + if (strpos($type_parts[0], '::')) { + $type_parts[] = strstr($type_parts[0], '::'); + $type_parts[0] = $namespace_aliases[strstr($type_parts[0], '::', true)]; + return implode('', $type_parts); + } + + $type_parts[0] = $namespace_aliases[$type_parts[0]]; + $type = implode(self::OPERATOR_NAMESPACE, $type_parts); + } + + return $type; + } + + /** + * Detects whether the given type represents an array. + * + * @param string $type A relative or absolute type as defined in the + * phpDocumentor documentation. + * + * @return bool + */ + protected function isTypeAnArray($type) + { + return substr($type, -2) === self::OPERATOR_ARRAY; + } + + /** + * Detects whether the given type represents a PHPDoc keyword. + * + * @param string $type A relative or absolute type as defined in the + * phpDocumentor documentation. + * + * @return bool + */ + protected function isTypeAKeyword($type) + { + return in_array(strtolower($type), static::$keywords, true); + } + + /** + * Detects whether the given type represents a relative or absolute path. + * + * This method will detect keywords as being absolute; even though they are + * not preceeded by a namespace separator. + * + * @param string $type A relative or absolute type as defined in the + * phpDocumentor documentation. + * + * @return bool + */ + protected function isRelativeType($type) + { + return ($type[0] !== self::OPERATOR_NAMESPACE) + || $this->isTypeAKeyword($type); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/DescriptionTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/DescriptionTest.php new file mode 100644 index 00000000..cfa4f39a --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/DescriptionTest.php @@ -0,0 +1,245 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Description + * + * @author Vasil Rangelov + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class DescriptionTest extends \PHPUnit_Framework_TestCase +{ + public function testConstruct() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(1, $parsedContents); + $this->assertSame($fixture, $parsedContents[0]); + } + + public function testInlineTagParsing() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(3, $parsedContents); + $this->assertSame('This is text for a ', $parsedContents[0]); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag\LinkTag', + $parsedContents[1] + ); + $this->assertSame( + ' that uses inline +tags.', + $parsedContents[2] + ); + } + + public function testInlineTagAtStartParsing() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(3, $parsedContents); + + $this->assertSame('', $parsedContents[0]); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag\LinkTag', + $parsedContents[1] + ); + $this->assertSame( + ' is text for a description that uses inline +tags.', + $parsedContents[2] + ); + } + + public function testNestedInlineTagParsing() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(3, $parsedContents); + + $this->assertSame( + 'This is text for a description with ', + $parsedContents[0] + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $parsedContents[1] + ); + $this->assertSame('.', $parsedContents[2]); + + $parsedDescription = $parsedContents[1]->getParsedDescription(); + $this->assertCount(3, $parsedDescription); + $this->assertSame("inline tag with\n", $parsedDescription[0]); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag\LinkTag', + $parsedDescription[1] + ); + $this->assertSame(' in it', $parsedDescription[2]); + } + + public function testLiteralOpeningDelimiter() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(1, $parsedContents); + $this->assertSame($fixture, $parsedContents[0]); + } + + public function testNestedLiteralOpeningDelimiter() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(3, $parsedContents); + $this->assertSame( + 'This is text for a description containing ', + $parsedContents[0] + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $parsedContents[1] + ); + $this->assertSame('.', $parsedContents[2]); + + $this->assertSame( + array('inline tag that has { that +is literal'), + $parsedContents[1]->getParsedDescription() + ); + } + + public function testLiteralClosingDelimiter() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(1, $parsedContents); + $this->assertSame( + 'This is text for a description with } that is not a tag.', + $parsedContents[0] + ); + } + + public function testNestedLiteralClosingDelimiter() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(3, $parsedContents); + $this->assertSame( + 'This is text for a description with ', + $parsedContents[0] + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $parsedContents[1] + ); + $this->assertSame('.', $parsedContents[2]); + + $this->assertSame( + array('inline tag with } that is not an +inline tag'), + $parsedContents[1]->getParsedDescription() + ); + } + + public function testInlineTagEscapingSequence() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(1, $parsedContents); + $this->assertSame( + 'This is text for a description with literal {@link}.', + $parsedContents[0] + ); + } + + public function testNestedInlineTagEscapingSequence() + { + $fixture = <<assertSame($fixture, $object->getContents()); + + $parsedContents = $object->getParsedContents(); + $this->assertCount(3, $parsedContents); + $this->assertSame( + 'This is text for a description with an ', + $parsedContents[0] + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $parsedContents[1] + ); + $this->assertSame('.', $parsedContents[2]); + + $this->assertSame( + array('inline tag with literal +{@link} in it'), + $parsedContents[1]->getParsedDescription() + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/CoversTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/CoversTagTest.php new file mode 100644 index 00000000..a2edfed6 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/CoversTagTest.php @@ -0,0 +1,86 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\CoversTag + * + * @author Daniel O'Connor + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class CoversTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\CoversTag can create + * a link for the covers doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exReference + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\CoversTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exReference + ) { + $tag = new CoversTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exReference, $tag->getReference()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exReference + return array( + array( + 'covers', + 'Foo::bar()', + 'Foo::bar()', + '', + 'Foo::bar()' + ), + array( + 'covers', + 'Foo::bar() Testing', + 'Foo::bar() Testing', + 'Testing', + 'Foo::bar()', + ), + array( + 'covers', + 'Foo::bar() Testing comments', + 'Foo::bar() Testing comments', + 'Testing comments', + 'Foo::bar()', + ), + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/DeprecatedTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/DeprecatedTagTest.php new file mode 100644 index 00000000..9cd4a24d --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/DeprecatedTagTest.php @@ -0,0 +1,115 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\DeprecatedTag + * + * @author Vasil Rangelov + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class DeprecatedTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\LinkTag can create + * a link for the @deprecated doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exDescription + * @param string $exVersion + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\DeprecatedTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exVersion + ) { + $tag = new DeprecatedTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exVersion, $tag->getVersion()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exVersion + return array( + array( + 'deprecated', + '1.0 First release.', + '1.0 First release.', + 'First release.', + '1.0' + ), + array( + 'deprecated', + "1.0\nFirst release.", + "1.0\nFirst release.", + 'First release.', + '1.0' + ), + array( + 'deprecated', + "1.0\nFirst\nrelease.", + "1.0\nFirst\nrelease.", + "First\nrelease.", + '1.0' + ), + array( + 'deprecated', + 'Unfinished release', + 'Unfinished release', + 'Unfinished release', + '' + ), + array( + 'deprecated', + '1.0', + '1.0', + '', + '1.0' + ), + array( + 'deprecated', + 'GIT: $Id$', + 'GIT: $Id$', + '', + 'GIT: $Id$' + ), + array( + 'deprecated', + 'GIT: $Id$ Dev build', + 'GIT: $Id$ Dev build', + 'Dev build', + 'GIT: $Id$' + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ExampleTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ExampleTagTest.php new file mode 100644 index 00000000..97ebad78 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ExampleTagTest.php @@ -0,0 +1,203 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\ExampleTag + * + * @author Vasil Rangelov + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ExampleTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\SourceTag can + * understand the @source DocBlock. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exStartingLine + * @param string $exLineCount + * @param string $exFilepath + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\ExampleTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exStartingLine, + $exLineCount, + $exFilePath + ) { + $tag = new ExampleTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exStartingLine, $tag->getStartingLine()); + $this->assertEquals($exLineCount, $tag->getLineCount()); + $this->assertEquals($exFilePath, $tag->getFilePath()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, + // $content, + // $exContent, + // $exDescription, + // $exStartingLine, + // $exLineCount, + // $exFilePath + return array( + array( + 'example', + 'file.php', + 'file.php', + '', + 1, + null, + 'file.php' + ), + array( + 'example', + 'Testing comments', + 'Testing comments', + 'comments', + 1, + null, + 'Testing' + ), + array( + 'example', + 'file.php 2 Testing', + 'file.php 2 Testing', + 'Testing', + 2, + null, + 'file.php' + ), + array( + 'example', + 'file.php 2 3 Testing comments', + 'file.php 2 3 Testing comments', + 'Testing comments', + 2, + 3, + 'file.php' + ), + array( + 'example', + 'file.php 2 -1 Testing comments', + 'file.php 2 -1 Testing comments', + '-1 Testing comments', + 2, + null, + 'file.php' + ), + array( + 'example', + 'file.php -1 1 Testing comments', + 'file.php -1 1 Testing comments', + '-1 1 Testing comments', + 1, + null, + 'file.php' + ), + array( + 'example', + '"file with spaces.php" Testing comments', + '"file with spaces.php" Testing comments', + 'Testing comments', + 1, + null, + 'file with spaces.php' + ), + array( + 'example', + '"file with spaces.php" 2 Testing comments', + '"file with spaces.php" 2 Testing comments', + 'Testing comments', + 2, + null, + 'file with spaces.php' + ), + array( + 'example', + '"file with spaces.php" 2 3 Testing comments', + '"file with spaces.php" 2 3 Testing comments', + 'Testing comments', + 2, + 3, + 'file with spaces.php' + ), + array( + 'example', + '"file with spaces.php" 2 -3 Testing comments', + '"file with spaces.php" 2 -3 Testing comments', + '-3 Testing comments', + 2, + null, + 'file with spaces.php' + ), + array( + 'example', + '"file with spaces.php" -2 3 Testing comments', + '"file with spaces.php" -2 3 Testing comments', + '-2 3 Testing comments', + 1, + null, + 'file with spaces.php' + ), + array( + 'example', + 'file%20with%20spaces.php Testing comments', + 'file%20with%20spaces.php Testing comments', + 'Testing comments', + 1, + null, + 'file with spaces.php' + ), + array( + 'example', + 'folder/file%20with%20spaces.php Testing comments', + 'folder/file%20with%20spaces.php Testing comments', + 'Testing comments', + 1, + null, + 'folder/file with spaces.php' + ), + array( + 'example', + 'http://example.com/file%20with%20spaces.php Testing comments', + 'http://example.com/file%20with%20spaces.php Testing comments', + 'Testing comments', + 1, + null, + 'http://example.com/file%20with%20spaces.php' + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/LinkTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/LinkTagTest.php new file mode 100644 index 00000000..b56836fd --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/LinkTagTest.php @@ -0,0 +1,87 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\LinkTag + * + * @author Ben Selby + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class LinkTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\LinkTag can create + * a link for the @link doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exDescription + * @param string $exLink + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\LinkTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exLink + ) { + $tag = new LinkTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exLink, $tag->getLink()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exLink + return array( + array( + 'link', + 'http://www.phpdoc.org/', + 'http://www.phpdoc.org/', + 'http://www.phpdoc.org/', + 'http://www.phpdoc.org/' + ), + array( + 'link', + 'http://www.phpdoc.org/ Testing', + 'http://www.phpdoc.org/ Testing', + 'Testing', + 'http://www.phpdoc.org/' + ), + array( + 'link', + 'http://www.phpdoc.org/ Testing comments', + 'http://www.phpdoc.org/ Testing comments', + 'Testing comments', + 'http://www.phpdoc.org/' + ), + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/MethodTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/MethodTagTest.php new file mode 100644 index 00000000..10c6d053 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/MethodTagTest.php @@ -0,0 +1,146 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\MethodTag + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class MethodTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @param string $signature The signature to test. + * @param bool $valid Whether the given signature is expected to + * be valid. + * @param string $expected_name The method name that is expected from this + * signature. + * @param string $expected_return The return type that is expected from this + * signature. + * @param bool $paramCount Number of parameters in the signature. + * @param string $description The short description mentioned in the + * signature. + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\MethodTag + * @dataProvider getTestSignatures + * + * @return void + */ + public function testConstruct( + $signature, + $valid, + $expected_name, + $expected_return, + $expected_isStatic, + $paramCount, + $description + ) { + ob_start(); + $tag = new MethodTag('method', $signature); + $stdout = ob_get_clean(); + + $this->assertSame( + $valid, + empty($stdout), + 'No error should have been output if the signature is valid' + ); + + if (!$valid) { + return; + } + + $this->assertEquals($expected_name, $tag->getMethodName()); + $this->assertEquals($expected_return, $tag->getType()); + $this->assertEquals($description, $tag->getDescription()); + $this->assertEquals($expected_isStatic, $tag->isStatic()); + $this->assertCount($paramCount, $tag->getArguments()); + } + + public function getTestSignatures() + { + return array( + // TODO: Verify this case +// array( +// 'foo', +// false, 'foo', '', false, 0, '' +// ), + array( + 'foo()', + true, 'foo', 'void', false, 0, '' + ), + array( + 'foo() description', + true, 'foo', 'void', false, 0, 'description' + ), + array( + 'int foo()', + true, 'foo', 'int', false, 0, '' + ), + array( + 'int foo() description', + true, 'foo', 'int', false, 0, 'description' + ), + array( + 'int foo($a, $b)', + true, 'foo', 'int', false, 2, '' + ), + array( + 'int foo() foo(int $a, int $b)', + true, 'foo', 'int', false, 2, '' + ), + array( + 'int foo(int $a, int $b)', + true, 'foo', 'int', false, 2, '' + ), + array( + 'null|int foo(int $a, int $b)', + true, 'foo', 'null|int', false, 2, '' + ), + array( + 'int foo(null|int $a, int $b)', + true, 'foo', 'int', false, 2, '' + ), + array( + '\Exception foo() foo(Exception $a, Exception $b)', + true, 'foo', '\Exception', false, 2, '' + ), + array( + 'int foo() foo(Exception $a, Exception $b) description', + true, 'foo', 'int', false, 2, 'description' + ), + array( + 'int foo() foo(\Exception $a, \Exception $b) description', + true, 'foo', 'int', false, 2, 'description' + ), + array( + 'void()', + true, 'void', 'void', false, 0, '' + ), + array( + 'static foo()', + true, 'foo', 'static', false, 0, '' + ), + array( + 'static void foo()', + true, 'foo', 'void', true, 0, '' + ), + array( + 'static static foo()', + true, 'foo', 'static', true, 0, '' + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ParamTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ParamTagTest.php new file mode 100644 index 00000000..eca89299 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ParamTagTest.php @@ -0,0 +1,118 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\ParamTag + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ParamTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\ParamTag can + * understand the @param DocBlock. + * + * @param string $type + * @param string $content + * @param string $extractedType + * @param string $extractedTypes + * @param string $extractedVarName + * @param string $extractedDescription + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\ParamTag + * @dataProvider provideDataForConstructor + * + * @return void + */ + public function testConstructorParsesInputsIntoCorrectFields( + $type, + $content, + $extractedType, + $extractedTypes, + $extractedVarName, + $extractedDescription + ) { + $tag = new ParamTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($extractedType, $tag->getType()); + $this->assertEquals($extractedTypes, $tag->getTypes()); + $this->assertEquals($extractedVarName, $tag->getVariableName()); + $this->assertEquals($extractedDescription, $tag->getDescription()); + } + + /** + * Data provider for testConstructorParsesInputsIntoCorrectFields() + * + * @return array + */ + public function provideDataForConstructor() + { + return array( + array('param', 'int', 'int', array('int'), '', ''), + array('param', '$bob', '', array(), '$bob', ''), + array( + 'param', + 'int Number of bobs', + 'int', + array('int'), + '', + 'Number of bobs' + ), + array( + 'param', + 'int $bob', + 'int', + array('int'), + '$bob', + '' + ), + array( + 'param', + 'int $bob Number of bobs', + 'int', + array('int'), + '$bob', + 'Number of bobs' + ), + array( + 'param', + "int Description \n on multiple lines", + 'int', + array('int'), + '', + "Description \n on multiple lines" + ), + array( + 'param', + "int \n\$bob Variable name on a new line", + 'int', + array('int'), + '$bob', + "Variable name on a new line" + ), + array( + 'param', + "\nint \$bob Type on a new line", + 'int', + array('int'), + '$bob', + "Type on a new line" + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ReturnTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ReturnTagTest.php new file mode 100644 index 00000000..88282250 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ReturnTagTest.php @@ -0,0 +1,102 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\ReturnTag + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ReturnTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\ReturnTag can + * understand the @return DocBlock. + * + * @param string $type + * @param string $content + * @param string $extractedType + * @param string $extractedTypes + * @param string $extractedDescription + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\ReturnTag + * @dataProvider provideDataForConstructor + * + * @return void + */ + public function testConstructorParsesInputsIntoCorrectFields( + $type, + $content, + $extractedType, + $extractedTypes, + $extractedDescription + ) { + $tag = new ReturnTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($extractedType, $tag->getType()); + $this->assertEquals($extractedTypes, $tag->getTypes()); + $this->assertEquals($extractedDescription, $tag->getDescription()); + } + + /** + * Data provider for testConstructorParsesInputsIntoCorrectFields() + * + * @return array + */ + public function provideDataForConstructor() + { + return array( + array('return', '', '', array(), ''), + array('return', 'int', 'int', array('int'), ''), + array( + 'return', + 'int Number of Bobs', + 'int', + array('int'), + 'Number of Bobs' + ), + array( + 'return', + 'int|double Number of Bobs', + 'int|double', + array('int', 'double'), + 'Number of Bobs' + ), + array( + 'return', + "int Number of \n Bobs", + 'int', + array('int'), + "Number of \n Bobs" + ), + array( + 'return', + " int Number of Bobs", + 'int', + array('int'), + "Number of Bobs" + ), + array( + 'return', + "int\nNumber of Bobs", + 'int', + array('int'), + "Number of Bobs" + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SeeTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SeeTagTest.php new file mode 100644 index 00000000..9e503855 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SeeTagTest.php @@ -0,0 +1,86 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\SeeTag + * + * @author Daniel O'Connor + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class SeeTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the phpDocumentor_Reflection_DocBlock_Tag_See can create a link + * for the @see doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exReference + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\SeeTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exReference + ) { + $tag = new SeeTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exReference, $tag->getReference()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exReference + return array( + array( + 'see', + 'Foo::bar()', + 'Foo::bar()', + '', + 'Foo::bar()' + ), + array( + 'see', + 'Foo::bar() Testing', + 'Foo::bar() Testing', + 'Testing', + 'Foo::bar()', + ), + array( + 'see', + 'Foo::bar() Testing comments', + 'Foo::bar() Testing comments', + 'Testing comments', + 'Foo::bar()', + ), + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SinceTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SinceTagTest.php new file mode 100644 index 00000000..cfd09549 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SinceTagTest.php @@ -0,0 +1,115 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\SinceTag + * + * @author Vasil Rangelov + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class SinceTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\LinkTag can create + * a link for the @since doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exDescription + * @param string $exVersion + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\SinceTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exVersion + ) { + $tag = new SinceTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exVersion, $tag->getVersion()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exVersion + return array( + array( + 'since', + '1.0 First release.', + '1.0 First release.', + 'First release.', + '1.0' + ), + array( + 'since', + "1.0\nFirst release.", + "1.0\nFirst release.", + 'First release.', + '1.0' + ), + array( + 'since', + "1.0\nFirst\nrelease.", + "1.0\nFirst\nrelease.", + "First\nrelease.", + '1.0' + ), + array( + 'since', + 'Unfinished release', + 'Unfinished release', + 'Unfinished release', + '' + ), + array( + 'since', + '1.0', + '1.0', + '', + '1.0' + ), + array( + 'since', + 'GIT: $Id$', + 'GIT: $Id$', + '', + 'GIT: $Id$' + ), + array( + 'since', + 'GIT: $Id$ Dev build', + 'GIT: $Id$ Dev build', + 'Dev build', + 'GIT: $Id$' + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SourceTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SourceTagTest.php new file mode 100644 index 00000000..d4ec47c6 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/SourceTagTest.php @@ -0,0 +1,116 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\SourceTag + * + * @author Vasil Rangelov + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class SourceTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\SourceTag can + * understand the @source DocBlock. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exStartingLine + * @param string $exLineCount + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\SourceTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exStartingLine, + $exLineCount + ) { + $tag = new SourceTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exStartingLine, $tag->getStartingLine()); + $this->assertEquals($exLineCount, $tag->getLineCount()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exStartingLine, $exLineCount + return array( + array( + 'source', + '2', + '2', + '', + 2, + null + ), + array( + 'source', + 'Testing', + 'Testing', + 'Testing', + 1, + null + ), + array( + 'source', + '2 Testing', + '2 Testing', + 'Testing', + 2, + null + ), + array( + 'source', + '2 3 Testing comments', + '2 3 Testing comments', + 'Testing comments', + 2, + 3 + ), + array( + 'source', + '2 -1 Testing comments', + '2 -1 Testing comments', + '-1 Testing comments', + 2, + null + ), + array( + 'source', + '-1 1 Testing comments', + '-1 1 Testing comments', + '-1 1 Testing comments', + 1, + null + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ThrowsTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ThrowsTagTest.php new file mode 100644 index 00000000..1957fe01 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/ThrowsTagTest.php @@ -0,0 +1,102 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\ThrowsTag + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class ThrowsTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag can + * understand the @throws DocBlock. + * + * @param string $type + * @param string $content + * @param string $extractedType + * @param string $extractedTypes + * @param string $extractedDescription + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\ThrowsTag + * @dataProvider provideDataForConstructor + * + * @return void + */ + public function testConstructorParsesInputsIntoCorrectFields( + $type, + $content, + $extractedType, + $extractedTypes, + $extractedDescription + ) { + $tag = new ThrowsTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($extractedType, $tag->getType()); + $this->assertEquals($extractedTypes, $tag->getTypes()); + $this->assertEquals($extractedDescription, $tag->getDescription()); + } + + /** + * Data provider for testConstructorParsesInputsIntoCorrectFields() + * + * @return array + */ + public function provideDataForConstructor() + { + return array( + array('throws', '', '', array(), ''), + array('throws', 'int', 'int', array('int'), ''), + array( + 'throws', + 'int Number of Bobs', + 'int', + array('int'), + 'Number of Bobs' + ), + array( + 'throws', + 'int|double Number of Bobs', + 'int|double', + array('int', 'double'), + 'Number of Bobs' + ), + array( + 'throws', + "int Number of \n Bobs", + 'int', + array('int'), + "Number of \n Bobs" + ), + array( + 'throws', + " int Number of Bobs", + 'int', + array('int'), + "Number of Bobs" + ), + array( + 'throws', + "int\nNumber of Bobs", + 'int', + array('int'), + "Number of Bobs" + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/UsesTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/UsesTagTest.php new file mode 100644 index 00000000..c6110564 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/UsesTagTest.php @@ -0,0 +1,86 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\UsesTag + * + * @author Daniel O'Connor + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class UsesTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\UsesTag can create + * a link for the @uses doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exReference + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\UsesTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exReference + ) { + $tag = new UsesTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exReference, $tag->getReference()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exReference + return array( + array( + 'uses', + 'Foo::bar()', + 'Foo::bar()', + '', + 'Foo::bar()' + ), + array( + 'uses', + 'Foo::bar() Testing', + 'Foo::bar() Testing', + 'Testing', + 'Foo::bar()', + ), + array( + 'uses', + 'Foo::bar() Testing comments', + 'Foo::bar() Testing comments', + 'Testing comments', + 'Foo::bar()', + ), + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VarTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VarTagTest.php new file mode 100644 index 00000000..a830b583 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VarTagTest.php @@ -0,0 +1,94 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\VarTag + * + * @author Daniel O'Connor + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class VarTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\VarTag can + * understand the @var doc block. + * + * @param string $type + * @param string $content + * @param string $exType + * @param string $exVariable + * @param string $exDescription + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\VarTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exType, + $exVariable, + $exDescription + ) { + $tag = new VarTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exType, $tag->getType()); + $this->assertEquals($exVariable, $tag->getVariableName()); + $this->assertEquals($exDescription, $tag->getDescription()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exType, $exVariable, $exDescription + return array( + array( + 'var', + 'int', + 'int', + '', + '' + ), + array( + 'var', + 'int $bob', + 'int', + '$bob', + '' + ), + array( + 'var', + 'int $bob Number of bobs', + 'int', + '$bob', + 'Number of bobs' + ), + array( + 'var', + '', + '', + '', + '' + ), + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VersionTagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VersionTagTest.php new file mode 100644 index 00000000..607d5610 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Tag/VersionTagTest.php @@ -0,0 +1,115 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Tag; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\VersionTag + * + * @author Vasil Rangelov + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class VersionTagTest extends \PHPUnit_Framework_TestCase +{ + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\LinkTag can create + * a link for the @version doc block. + * + * @param string $type + * @param string $content + * @param string $exContent + * @param string $exDescription + * @param string $exVersion + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag\VersionTag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exContent, + $exDescription, + $exVersion + ) { + $tag = new VersionTag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($exContent, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + $this->assertEquals($exVersion, $tag->getVersion()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exContent, $exDescription, $exVersion + return array( + array( + 'version', + '1.0 First release.', + '1.0 First release.', + 'First release.', + '1.0' + ), + array( + 'version', + "1.0\nFirst release.", + "1.0\nFirst release.", + 'First release.', + '1.0' + ), + array( + 'version', + "1.0\nFirst\nrelease.", + "1.0\nFirst\nrelease.", + "First\nrelease.", + '1.0' + ), + array( + 'version', + 'Unfinished release', + 'Unfinished release', + 'Unfinished release', + '' + ), + array( + 'version', + '1.0', + '1.0', + '', + '1.0' + ), + array( + 'version', + 'GIT: $Id$', + 'GIT: $Id$', + '', + 'GIT: $Id$' + ), + array( + 'version', + 'GIT: $Id$ Dev build', + 'GIT: $Id$ Dev build', + 'Dev build', + 'GIT: $Id$' + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/TagTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/TagTest.php new file mode 100644 index 00000000..56193728 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/TagTest.php @@ -0,0 +1,313 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock; + +use Mpociot\Reflection\DocBlock; +use Mpociot\Reflection\DocBlock\Context; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Tag\VarTag + * + * @author Daniel O'Connor + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class TagTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @expectedException \InvalidArgumentException + * + * @return void + */ + public function testInvalidTagLine() + { + Tag::createInstance('Invalid tag line'); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock\Tag::registerTagHandler + * + * @return void + */ + public function testTagHandlerUnregistration() + { + $currentHandler = __NAMESPACE__ . '\Tag\VarTag'; + $tagPreUnreg = Tag::createInstance('@var mixed'); + $this->assertInstanceOf( + $currentHandler, + $tagPreUnreg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPreUnreg + ); + + Tag::registerTagHandler('var', null); + + $tagPostUnreg = Tag::createInstance('@var mixed'); + $this->assertNotInstanceOf( + $currentHandler, + $tagPostUnreg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPostUnreg + ); + + Tag::registerTagHandler('var', $currentHandler); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock\Tag::registerTagHandler + * + * @return void + */ + public function testTagHandlerCorrectRegistration() + { + if (0 == ini_get('allow_url_include')) { + $this->markTestSkipped('"data" URIs for includes are required.'); + } + $currentHandler = __NAMESPACE__ . '\Tag\VarTag'; + $tagPreReg = Tag::createInstance('@var mixed'); + $this->assertInstanceOf( + $currentHandler, + $tagPreReg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPreReg + ); + + include 'data:text/plain;base64,'. base64_encode( +<<assertTrue(Tag::registerTagHandler('var', '\MyTagHandler')); + + $tagPostReg = Tag::createInstance('@var mixed'); + $this->assertNotInstanceOf( + $currentHandler, + $tagPostReg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPostReg + ); + $this->assertInstanceOf( + '\MyTagHandler', + $tagPostReg + ); + + $this->assertTrue(Tag::registerTagHandler('var', $currentHandler)); + } + + /** + * @depends testTagHandlerCorrectRegistration + * @covers \phpDocumentor\Reflection\DocBlock\Tag::registerTagHandler + * @covers \phpDocumentor\Reflection\DocBlock\Tag::createInstance + * + * @return void + */ + public function testNamespacedTagHandlerCorrectRegistration() + { + $tagPreReg = Tag::createInstance('@T something'); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPreReg + ); + $this->assertNotInstanceOf( + '\MyTagHandler', + $tagPreReg + ); + + $this->assertTrue( + Tag::registerTagHandler('\MyNamespace\MyTag', '\MyTagHandler') + ); + + $tagPostReg = Tag::createInstance( + '@T something', + new DocBlock( + '', + new Context('', array('T' => '\MyNamespace\MyTag')) + ) + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPostReg + ); + $this->assertInstanceOf( + '\MyTagHandler', + $tagPostReg + ); + + $this->assertTrue( + Tag::registerTagHandler('\MyNamespace\MyTag', null) + ); + } + + /** + * @depends testTagHandlerCorrectRegistration + * @covers \phpDocumentor\Reflection\DocBlock\Tag::registerTagHandler + * @covers \phpDocumentor\Reflection\DocBlock\Tag::createInstance + * + * @return void + */ + public function testNamespacedTagHandlerIncorrectRegistration() + { + $tagPreReg = Tag::createInstance('@T something'); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPreReg + ); + $this->assertNotInstanceOf( + '\MyTagHandler', + $tagPreReg + ); + + $this->assertFalse( + Tag::registerTagHandler('MyNamespace\MyTag', '\MyTagHandler') + ); + + $tagPostReg = Tag::createInstance( + '@T something', + new DocBlock( + '', + new Context('', array('T' => '\MyNamespace\MyTag')) + ) + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPostReg + ); + $this->assertNotInstanceOf( + '\MyTagHandler', + $tagPostReg + ); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock\Tag::registerTagHandler + * + * @return void + */ + public function testNonExistentTagHandlerRegistration() + { + $currentHandler = __NAMESPACE__ . '\Tag\VarTag'; + $tagPreReg = Tag::createInstance('@var mixed'); + $this->assertInstanceOf( + $currentHandler, + $tagPreReg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPreReg + ); + + $this->assertFalse(Tag::registerTagHandler('var', 'Non existent')); + + $tagPostReg = Tag::createInstance('@var mixed'); + $this->assertInstanceOf( + $currentHandler, + $tagPostReg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPostReg + ); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock\Tag::registerTagHandler + * + * @return void + */ + public function testIncompatibleTagHandlerRegistration() + { + $currentHandler = __NAMESPACE__ . '\Tag\VarTag'; + $tagPreReg = Tag::createInstance('@var mixed'); + $this->assertInstanceOf( + $currentHandler, + $tagPreReg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPreReg + ); + + $this->assertFalse( + Tag::registerTagHandler('var', __NAMESPACE__ . '\TagTest') + ); + + $tagPostReg = Tag::createInstance('@var mixed'); + $this->assertInstanceOf( + $currentHandler, + $tagPostReg + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\Tag', + $tagPostReg + ); + } + + /** + * Test that the \phpDocumentor\Reflection\DocBlock\Tag\VarTag can + * understand the @var doc block. + * + * @param string $type + * @param string $content + * @param string $exDescription + * + * @covers \phpDocumentor\Reflection\DocBlock\Tag + * @dataProvider provideDataForConstuctor + * + * @return void + */ + public function testConstructorParesInputsIntoCorrectFields( + $type, + $content, + $exDescription + ) { + $tag = new Tag($type, $content); + + $this->assertEquals($type, $tag->getName()); + $this->assertEquals($content, $tag->getContent()); + $this->assertEquals($exDescription, $tag->getDescription()); + } + + /** + * Data provider for testConstructorParesInputsIntoCorrectFields + * + * @return array + */ + public function provideDataForConstuctor() + { + // $type, $content, $exDescription + return array( + array( + 'unknown', + 'some content', + 'some content', + ), + array( + 'unknown', + '', + '', + ) + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Type/CollectionTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Type/CollectionTest.php new file mode 100644 index 00000000..364fc974 --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlock/Type/CollectionTest.php @@ -0,0 +1,253 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection\DocBlock\Type; + +use Mpociot\Reflection\DocBlock\Context; + +/** + * Test class for \phpDocumentor\Reflection\DocBlock\Type\Collection + * + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class CollectionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::__construct + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::getContext + * + * @return void + */ + public function testConstruct() + { + $collection = new Collection(); + $this->assertCount(0, $collection); + $this->assertEquals('', $collection->getContext()->getNamespace()); + $this->assertCount(0, $collection->getContext()->getNamespaceAliases()); + } + + /** + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::__construct + * + * @return void + */ + public function testConstructWithTypes() + { + $collection = new Collection(array('integer', 'string')); + $this->assertCount(2, $collection); + } + + /** + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::__construct + * + * @return void + */ + public function testConstructWithNamespace() + { + $collection = new Collection(array(), new Context('\My\Space')); + $this->assertEquals('My\Space', $collection->getContext()->getNamespace()); + + $collection = new Collection(array(), new Context('My\Space')); + $this->assertEquals('My\Space', $collection->getContext()->getNamespace()); + + $collection = new Collection(array(), null); + $this->assertEquals('', $collection->getContext()->getNamespace()); + } + + /** + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::__construct + * + * @return void + */ + public function testConstructWithNamespaceAliases() + { + $fixture = array('a' => 'b'); + $collection = new Collection(array(), new Context(null, $fixture)); + $this->assertEquals( + array('a' => '\b'), + $collection->getContext()->getNamespaceAliases() + ); + } + + /** + * @param string $fixture + * @param array $expected + * + * @dataProvider provideTypesToExpand + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::add + * + * @return void + */ + public function testAdd($fixture, $expected) + { + $collection = new Collection( + array(), + new Context('\My\Space', array('Alias' => '\My\Space\Aliasing')) + ); + $collection->add($fixture); + + $this->assertSame($expected, $collection->getArrayCopy()); + } + + /** + * @param string $fixture + * @param array $expected + * + * @dataProvider provideTypesToExpandWithoutNamespace + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::add + * + * @return void + */ + public function testAddWithoutNamespace($fixture, $expected) + { + $collection = new Collection( + array(), + new Context(null, array('Alias' => '\My\Space\Aliasing')) + ); + $collection->add($fixture); + + $this->assertSame($expected, $collection->getArrayCopy()); + } + + /** + * @param string $fixture + * @param array $expected + * + * @dataProvider provideTypesToExpandWithPropertyOrMethod + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::add + * + * @return void + */ + public function testAddMethodsAndProperties($fixture, $expected) + { + $collection = new Collection( + array(), + new Context(null, array('LinkDescriptor' => '\phpDocumentor\LinkDescriptor')) + ); + $collection->add($fixture); + + $this->assertSame($expected, $collection->getArrayCopy()); + } + + /** + * @covers phpDocumentor\Reflection\DocBlock\Type\Collection::add + * @expectedException InvalidArgumentException + * + * @return void + */ + public function testAddWithInvalidArgument() + { + $collection = new Collection(); + $collection->add(array()); + } + + /** + * Returns the types and their expected values to test the retrieval of + * types. + * + * @param string $method Name of the method consuming this data provider. + * @param string $namespace Name of the namespace to user as basis. + * + * @return string[] + */ + public function provideTypesToExpand($method, $namespace = '\My\Space\\') + { + return array( + array('', array()), + array(' ', array()), + array('int', array('int')), + array('int ', array('int')), + array('string', array('string')), + array('DocBlock', array($namespace.'DocBlock')), + array('DocBlock[]', array($namespace.'DocBlock[]')), + array(' DocBlock ', array($namespace.'DocBlock')), + array('\My\Space\DocBlock', array('\My\Space\DocBlock')), + array('Alias\DocBlock', array('\My\Space\Aliasing\DocBlock')), + array( + 'DocBlock|Tag', + array($namespace .'DocBlock', $namespace .'Tag') + ), + array( + 'DocBlock|null', + array($namespace.'DocBlock', 'null') + ), + array( + '\My\Space\DocBlock|Tag', + array('\My\Space\DocBlock', $namespace.'Tag') + ), + array( + 'DocBlock[]|null', + array($namespace.'DocBlock[]', 'null') + ), + array( + 'DocBlock[]|int[]', + array($namespace.'DocBlock[]', 'int[]') + ), + array( + 'LinkDescriptor::setLink()', + array($namespace.'LinkDescriptor::setLink()') + ), + array( + 'Alias\LinkDescriptor::setLink()', + array('\My\Space\Aliasing\LinkDescriptor::setLink()') + ), + ); + } + + /** + * Returns the types and their expected values to test the retrieval of + * types when no namespace is available. + * + * @param string $method Name of the method consuming this data provider. + * + * @return string[] + */ + public function provideTypesToExpandWithoutNamespace($method) + { + return $this->provideTypesToExpand($method, '\\'); + } + + /** + * Returns the method and property types and their expected values to test + * the retrieval of types. + * + * @param string $method Name of the method consuming this data provider. + * + * @return string[] + */ + public function provideTypesToExpandWithPropertyOrMethod($method) + { + return array( + array( + 'LinkDescriptor::setLink()', + array('\phpDocumentor\LinkDescriptor::setLink()') + ), + array( + 'phpDocumentor\LinkDescriptor::setLink()', + array('\phpDocumentor\LinkDescriptor::setLink()') + ), + array( + 'LinkDescriptor::$link', + array('\phpDocumentor\LinkDescriptor::$link') + ), + array( + 'phpDocumentor\LinkDescriptor::$link', + array('\phpDocumentor\LinkDescriptor::$link') + ), + ); + } +} diff --git a/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlockTest.php b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlockTest.php new file mode 100644 index 00000000..821b245e --- /dev/null +++ b/vendor/mpociot/reflection-docblock/tests/phpDocumentor/Reflection/DocBlockTest.php @@ -0,0 +1,337 @@ + + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Mpociot\Reflection; + +use Mpociot\Reflection\DocBlock\Context; +use Mpociot\Reflection\DocBlock\Location; +use Mpociot\Reflection\DocBlock\Tag\ReturnTag; + +/** + * Test class for phpDocumentor\Reflection\DocBlock + * + * @author Mike van Riel + * @copyright 2010-2011 Mike van Riel / Naenius. (http://www.naenius.com) + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ +class DocBlockTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers \phpDocumentor\Reflection\DocBlock + * + * @return void + */ + public function testConstruct() + { + $fixture = << '\phpDocumentor')), + new Location(2) + ); + $this->assertEquals( + 'This is a short description', + $object->getShortDescription() + ); + $this->assertEquals( + 'This is a long description', + $object->getLongDescription()->getContents() + ); + $this->assertCount(2, $object->getTags()); + $this->assertTrue($object->hasTag('see')); + $this->assertTrue($object->hasTag('return')); + $this->assertFalse($object->hasTag('category')); + + $this->assertSame('MyNamespace', $object->getContext()->getNamespace()); + $this->assertSame( + array('PHPDoc' => '\phpDocumentor'), + $object->getContext()->getNamespaceAliases() + ); + $this->assertSame(2, $object->getLocation()->getLineNumber()); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock::splitDocBlock + * + * @return void + */ + public function testConstructWithTagsOnly() + { + $fixture = <<assertEquals('', $object->getShortDescription()); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertCount(2, $object->getTags()); + $this->assertTrue($object->hasTag('see')); + $this->assertTrue($object->hasTag('return')); + $this->assertFalse($object->hasTag('category')); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock::isTemplateStart + */ + public function testIfStartOfTemplateIsDiscovered() + { + $fixture = <<assertEquals('', $object->getShortDescription()); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertCount(2, $object->getTags()); + $this->assertTrue($object->hasTag('see')); + $this->assertTrue($object->hasTag('return')); + $this->assertFalse($object->hasTag('category')); + $this->assertTrue($object->isTemplateStart()); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock::isTemplateEnd + */ + public function testIfEndOfTemplateIsDiscovered() + { + $fixture = <<assertEquals('', $object->getShortDescription()); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertTrue($object->isTemplateEnd()); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock::cleanInput + * + * @return void + */ + public function testConstructOneLiner() + { + $fixture = '/** Short description and nothing more. */'; + $object = new DocBlock($fixture); + $this->assertEquals( + 'Short description and nothing more.', + $object->getShortDescription() + ); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertCount(0, $object->getTags()); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock::__construct + * + * @return void + */ + public function testConstructFromReflector() + { + $object = new DocBlock(new \ReflectionClass($this)); + $this->assertEquals( + 'Test class for phpDocumentor\Reflection\DocBlock', + $object->getShortDescription() + ); + $this->assertEquals('', $object->getLongDescription()->getContents()); + $this->assertCount(4, $object->getTags()); + $this->assertTrue($object->hasTag('author')); + $this->assertTrue($object->hasTag('copyright')); + $this->assertTrue($object->hasTag('license')); + $this->assertTrue($object->hasTag('link')); + $this->assertFalse($object->hasTag('category')); + } + + /** + * @expectedException \InvalidArgumentException + * + * @return void + */ + public function testExceptionOnInvalidObject() + { + new DocBlock($this); + } + + public function testDotSeperation() + { + $fixture = <<assertEquals( + 'This is a short description.', + $object->getShortDescription() + ); + $this->assertEquals( + "This is a long description.\nThis is a continuation of the long " + ."description.", + $object->getLongDescription()->getContents() + ); + } + + /** + * @covers \phpDocumentor\Reflection\DocBlock::parseTags + * @expectedException \LogicException + * + * @return void + */ + public function testInvalidTagBlock() + { + if (0 == ini_get('allow_url_include')) { + $this->markTestSkipped('"data" URIs for includes are required.'); + } + + include 'data:text/plain;base64,'. base64_encode( + <<assertEquals( + 'This is a short description.', + $object->getShortDescription() + ); + $this->assertEquals( + 'This is a long description.', + $object->getLongDescription()->getContents() + ); + $tags = $object->getTags(); + $this->assertCount(2, $tags); + $this->assertTrue($object->hasTag('method')); + $this->assertTrue($object->hasTag('Method')); + $this->assertInstanceOf( + __NAMESPACE__ . '\DocBlock\Tag\MethodTag', + $tags[0] + ); + $this->assertInstanceOf( + __NAMESPACE__ . '\DocBlock\Tag', + $tags[1] + ); + $this->assertNotInstanceOf( + __NAMESPACE__ . '\DocBlock\Tag\MethodTag', + $tags[1] + ); + } + + /** + * @depends testConstructFromReflector + * @covers \phpDocumentor\Reflection\DocBlock::getTagsByName + * + * @return void + */ + public function testGetTagsByNameZeroAndOneMatch() + { + $object = new DocBlock(new \ReflectionClass($this)); + $this->assertEmpty($object->getTagsByName('category')); + $this->assertCount(1, $object->getTagsByName('author')); + } + + /** + * @depends testConstructWithTagsOnly + * @covers \phpDocumentor\Reflection\DocBlock::parseTags + * + * @return void + */ + public function testParseMultilineTag() + { + $fixture = <<assertCount(1, $object->getTags()); + } + + /** + * @depends testConstructWithTagsOnly + * @covers \phpDocumentor\Reflection\DocBlock::parseTags + * + * @return void + */ + public function testParseMultilineTagWithLineBreaks() + { + $fixture = <<assertCount(1, $tags = $object->getTags()); + /** @var ReturnTag $tag */ + $tag = reset($tags); + $this->assertEquals("Content on\n multiple lines.\n\n One more, after the break.", $tag->getDescription()); + } + + /** + * @depends testConstructWithTagsOnly + * @covers \phpDocumentor\Reflection\DocBlock::getTagsByName + * + * @return void + */ + public function testGetTagsByNameMultipleMatch() + { + $fixture = <<assertEmpty($object->getTagsByName('category')); + $this->assertCount(1, $object->getTagsByName('return')); + $this->assertCount(2, $object->getTagsByName('param')); + } +} diff --git a/vendor/parsedown/parsedown/.github/workflows/unit-tests.yaml b/vendor/parsedown/parsedown/.github/workflows/unit-tests.yaml new file mode 100644 index 00000000..c00148df --- /dev/null +++ b/vendor/parsedown/parsedown/.github/workflows/unit-tests.yaml @@ -0,0 +1,37 @@ +on: + - push + - pull_request + +jobs: + phpunit: + runs-on: ubuntu-latest + + strategy: + matrix: + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + - '8.4' + + steps: + - name: Checkout the source code + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '${{ matrix.php }}' + + - name: Install dependencies + run: composer install + + - name: Run tests + run: | + vendor/bin/phpunit + vendor/bin/phpunit test/CommonMarkTestWeak.php || true diff --git a/vendor/parsedown/parsedown/LICENSE.txt b/vendor/parsedown/parsedown/LICENSE.txt new file mode 100644 index 00000000..8e7c764d --- /dev/null +++ b/vendor/parsedown/parsedown/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2018 Emanuil Rusev, erusev.com + +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/vendor/parsedown/parsedown/Parsedown.php b/vendor/parsedown/parsedown/Parsedown.php new file mode 100644 index 00000000..2f01d5c3 --- /dev/null +++ b/vendor/parsedown/parsedown/Parsedown.php @@ -0,0 +1,1995 @@ +textElements($text); + + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + protected function textElements($text) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + return $this->linesElements($lines); + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + function setStrictMode($strictMode) + { + $this->strictMode = (bool) $strictMode; + + return $this; + } + + protected $strictMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'tel:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + return $this->elements($this->linesElements($lines)); + } + + protected function linesElements(array $lines) + { + $Elements = array(); + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + continue; + } + + while (($beforeTab = strstr($line, "\t", true)) !== false) + { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1) + ; + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') + { + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) + { + $CurrentBlock = $Block; + } + else + { + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) + { + $Elements[] = $this->extractElement($CurrentBlock); + } + + # ~ + + return $Elements; + } + + protected function extractElement(array $Component) + { + if ( ! isset($Component['element'])) + { + if (isset($Component['markup'])) + { + $Component['element'] = array('rawHtml' => $Component['markup']); + } + elseif (isset($Component['hidden'])) + { + $Component['element'] = array(); + } + } + + return $Component['element']; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block' . $Type . 'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block' . $Type . 'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + $Block['element']['element']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['element']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (strpos($Line['text'], '') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + if (strpos($Line['text'], '-->') !== false) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + $marker = $Line['text'][0]; + + $openerLength = strspn($Line['text'], $marker); + + if ($openerLength < 3) + { + return; + } + + $infostring = trim(substr($Line['text'], $openerLength), "\t "); + + if (strpos($infostring, '`') !== false) + { + return; + } + + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if ($infostring !== '') + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r")); + + $Element['attributes'] = array('class' => "language-$language"); + } + + $Block = array( + 'char' => $marker, + 'openerLength' => $openerLength, + 'element' => array( + 'name' => 'pre', + 'element' => $Element, + ), + ); + + return $Block; + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); + + unset($Block['interrupted']); + } + + if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength'] + and chop(substr($Line['text'], $len), ' ') === '' + ) { + $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['element']['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + $level = strspn($Line['text'], '#'); + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '#'); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') + { + return; + } + + $text = trim($text, ' '); + + $Block = array( + 'element' => array( + 'name' => 'h' . $level, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + + # + # List + + protected function blockList($Line, ?array $CurrentBlock = null) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); + + if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) + { + $contentIndent = strlen($matches[2]); + + if ($contentIndent >= 5) + { + $contentIndent -= 1; + $matches[1] = substr($matches[1], 0, -$contentIndent); + $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; + } + elseif ($contentIndent === 0) + { + $matches[1] .= ' '; + } + + $markerWithoutWhitespace = strstr($matches[1], ' ', true); + + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'data' => array( + 'type' => $name, + 'marker' => $matches[1], + 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)), + ), + 'element' => array( + 'name' => $name, + 'elements' => array(), + ), + ); + $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); + + if ($name === 'ol') + { + $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; + + if ($listStart !== '1') + { + if ( + isset($CurrentBlock) + and $CurrentBlock['type'] === 'Paragraph' + and ! isset($CurrentBlock['interrupted']) + ) { + return; + } + + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => !empty($matches[3]) ? array($matches[3]) : array(), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) + { + return null; + } + + $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker'])); + + if ($Line['indent'] < $requiredIndent + and ( + ( + $Block['data']['type'] === 'ol' + and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) or ( + $Block['data']['type'] === 'ul' + and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches) + ) + ) + ) { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['indent'] = $Line['indent']; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => array( + 'function' => 'li', + 'argument' => array($text), + 'destination' => 'elements' + ) + ); + + $Block['element']['elements'] []= & $Block['li']; + + return $Block; + } + elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) + { + return null; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ($Line['indent'] >= $requiredIndent) + { + if (isset($Block['interrupted'])) + { + $Block['li']['handler']['argument'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + $text = substr($Line['body'], $requiredIndent); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); + + $Block['li']['handler']['argument'] []= $text; + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['elements'] as &$li) + { + if (end($li['handler']['argument']) !== '') + { + $li['handler']['argument'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $matches[1], + 'destination' => 'elements', + ) + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) + { + $Block['element']['handler']['argument'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['handler']['argument'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + $marker = $Line['text'][0]; + + if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') + { + $Block = array( + 'element' => array( + 'name' => 'hr', + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, ?array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'element' => array( + 'rawHtml' => $Line['text'], + 'autobreak' => true, + ), + ); + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed']) or isset($Block['interrupted'])) + { + return; + } + + $Block['element']['rawHtml'] .= "\n" . $Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (strpos($Line['text'], ']') !== false + and preg_match('/^\[(.+?)\]:[ ]*+?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches) + ) { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => isset($matches[3]) ? $matches[3] : null, + ); + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'element' => array(), + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, ?array $Block = null) + { + if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) + { + return; + } + + if ( + strpos($Block['element']['handler']['argument'], '|') === false + and strpos($Line['text'], '|') === false + and strpos($Line['text'], ':') === false + or strpos($Block['element']['handler']['argument'], "\n") !== false + ) { + return; + } + + if (chop($Line['text'], ' -:|') !== '') + { + return; + } + + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + return; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['handler']['argument']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + if (count($headerCells) !== count($alignments)) + { + return; + } + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $headerCell, + 'destination' => 'elements', + ) + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => "text-align: $alignment;", + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'elements' => array(), + ), + ); + + $Block['element']['elements'] []= array( + 'name' => 'thead', + ); + + $Block['element']['elements'] []= array( + 'name' => 'tbody', + 'elements' => array(), + ); + + $Block['element']['elements'][0]['elements'] []= array( + 'name' => 'tr', + 'elements' => $HeaderElements, + ); + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'elements' => $Elements, + ); + + $Block['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + return array( + 'type' => 'Paragraph', + 'element' => array( + 'name' => 'p', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $Line['text'], + 'destination' => 'elements', + ), + ), + ); + } + + protected function paragraphContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + $Block['element']['handler']['argument'] .= "\n".$Line['text']; + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!*_&[:<`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables = array()) + { + return $this->elements($this->lineElements($text, $nonNestables)); + } + + protected function lineElements($text, $nonNestables = array()) + { + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + $Elements = array(); + + $nonNestables = (empty($nonNestables) + ? array() + : array_combine($nonNestables, $nonNestables) + ); + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strlen($text) - strlen($excerpt); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if (isset($nonNestables[$inlineType])) + { + continue; + } + + $Inline = $this->{"inline$inlineType"}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + + $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables']) + ? array_merge($Inline['element']['nonNestables'], $nonNestables) + : $nonNestables + ; + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + # compile the inline + $Elements[] = $this->extractElement($Inline); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $InlineText = $this->inlineText($unmarkedText); + $Elements[] = $InlineText['element']; + + $text = substr($text, $markerPosition + 1); + } + + $InlineText = $this->inlineText($text); + $Elements[] = $InlineText['element']; + + foreach ($Elements as &$Element) + { + if ( ! isset($Element['autobreak'])) + { + $Element['autobreak'] = false; + } + } + + return $Elements; + } + + # + # ~ + # + + protected function inlineText($text) + { + $Inline = array( + 'extent' => strlen($text), + 'element' => array(), + ); + + $Inline['element']['elements'] = self::pregReplaceElements( + $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/', + array( + array('name' => 'br'), + array('text' => "\n"), + ), + $text + ); + + return $Inline; + } + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?'; + + $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@' + . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*'; + + if (strpos($Excerpt['text'], '>') !== false + and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) + ){ + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = "mailto:$url"; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'element' => array('rawHtml' => $Excerpt['text'][1]), + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['handler']['argument'], + ), + 'autobreak' => true, + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'element' => array('rawHtml' => $matches[0]), + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false + and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches) + ) { + return array( + 'element' => array('rawHtml' => '&' . $matches[1] . ';'), + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $matches[1], + 'destination' => 'elements', + ) + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (strpos($Excerpt['context'], 'http') !== false + and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE) + ) { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + $Inline = $this->inlineText($text); + return $this->element($Inline['element']); + } + + # + # Handlers + # + + protected function handle(array $Element) + { + if (isset($Element['handler'])) + { + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (is_string($Element['handler'])) + { + $function = $Element['handler']; + $argument = $Element['text']; + unset($Element['text']); + $destination = 'rawHtml'; + } + else + { + $function = $Element['handler']['function']; + $argument = $Element['handler']['argument']; + $destination = $Element['handler']['destination']; + } + + $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); + + if ($destination === 'handler') + { + $Element = $this->handle($Element); + } + + unset($Element['handler']); + } + + return $Element; + } + + protected function handleElementRecursive(array $Element) + { + return $this->elementApplyRecursive(array($this, 'handle'), $Element); + } + + protected function handleElementsRecursive(array $Elements) + { + return $this->elementsApplyRecursive(array($this, 'handle'), $Elements); + } + + protected function elementApplyRecursive($closure, array $Element) + { + $Element = call_user_func($closure, $Element); + + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); + } + + return $Element; + } + + protected function elementApplyRecursiveDepthFirst($closure, array $Element) + { + if (isset($Element['elements'])) + { + $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); + } + elseif (isset($Element['element'])) + { + $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); + } + + $Element = call_user_func($closure, $Element); + + return $Element; + } + + protected function elementsApplyRecursive($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursive($closure, $Element); + } + + return $Elements; + } + + protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) + { + foreach ($Elements as &$Element) + { + $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); + } + + return $Elements; + } + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + # identity map if element has no handler + $Element = $this->handle($Element); + + $hasName = isset($Element['name']); + + $markup = ''; + + if ($hasName) + { + $markup .= '<' . $Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= " $name=\"".self::escape($value).'"'; + } + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); + + if ($hasContent) + { + $markup .= $hasName ? '>' : ''; + + if (isset($Element['elements'])) + { + $markup .= $this->elements($Element['elements']); + } + elseif (isset($Element['element'])) + { + $markup .= $this->element($Element['element']); + } + else + { + if (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + } + + $markup .= $hasName ? '' : ''; + } + elseif ($hasName) + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + $autoBreak = true; + + foreach ($Elements as $Element) + { + if (empty($Element)) + { + continue; + } + + $autoBreakNext = (isset($Element['autobreak']) + ? $Element['autobreak'] : isset($Element['name']) + ); + // (autobreak === false) covers both sides of an element + $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; + + $markup .= ($autoBreak ? "\n" : '') . $this->element($Element); + $autoBreak = $autoBreakNext; + } + + $markup .= $autoBreak ? "\n" : ''; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $Elements = $this->linesElements($lines); + + if ( ! in_array('', $lines) + and isset($Elements[0]) and isset($Elements[0]['name']) + and $Elements[0]['name'] === 'p' + ) { + unset($Elements[0]['name']); + } + + return $Elements; + } + + # + # AST Convenience + # + + /** + * Replace occurrences $regexp with $Elements in $text. Return an array of + * elements representing the replacement. + */ + protected static function pregReplaceElements($regexp, $Elements, $text) + { + $newElements = array(); + + while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) + { + $offset = $matches[0][1]; + $before = substr($text, 0, $offset); + $after = substr($text, $offset + strlen($matches[0][0])); + + $newElements[] = array('text' => $before); + + foreach ($Elements as $Element) + { + $newElements[] = $Element; + } + + $text = $after; + } + + $newElements[] = array('text' => $text); + + return $newElements; + } + + # + # Deprecated Methods + # + + /** + * @deprecated use text() instead + */ + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if ( ! isset($Element['name'])) + { + unset($Element['attributes']); + return $Element; + } + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~' + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} diff --git a/vendor/parsedown/parsedown/README.md b/vendor/parsedown/parsedown/README.md new file mode 100644 index 00000000..b64950c9 --- /dev/null +++ b/vendor/parsedown/parsedown/README.md @@ -0,0 +1,98 @@ +# Parsedown + +[![Total Downloads](https://poser.pugx.org/erusev/parsedown/d/total.svg)](https://packagist.org/packages/erusev/parsedown) +[![Version](https://poser.pugx.org/erusev/parsedown/v/stable.svg)](https://packagist.org/packages/erusev/parsedown) +[![License](https://poser.pugx.org/erusev/parsedown/license.svg)](https://packagist.org/packages/erusev/parsedown) + +Better Markdown Parser in PHP — demo + +## Features + +- One file +- No dependencies +- [Super fast](http://parsedown.org/speed) +- Extensible +- [GitHub flavored](https://github.github.com/gfm) +- [Tested](http://parsedown.org/tests/) in PHP 7.1+ +- [Markdown Extra extension](https://github.com/erusev/parsedown-extra) + +## Installation + +Install the [composer package]: + +```sh +composer require erusev/parsedown +``` + +Or download the [latest release] and include `Parsedown.php` + +[composer package]: https://packagist.org/packages/erusev/parsedown "The Parsedown package on packagist.org" +[latest release]: https://github.com/erusev/parsedown/releases/latest "The latest release of Parsedown" + +## Example + +```php +$Parsedown = new Parsedown(); + +echo $Parsedown->text('Hello _Parsedown_!'); # prints:

Hello Parsedown!

+``` + +You can also parse inline markdown only: + +```php +echo $Parsedown->line('Hello _Parsedown_!'); # prints: Hello Parsedown! +``` + +More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI). + +## Security + +Parsedown is capable of escaping user-input within the HTML that it generates. Additionally Parsedown will apply sanitisation to additional scripting vectors (such as scripting link destinations) that are introduced by the markdown syntax itself. + +To tell Parsedown that it is processing untrusted user-input, use the following: + +```php +$Parsedown->setSafeMode(true); +``` + +If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/). + +In both cases you should strongly consider employing defence-in-depth measures, like [deploying a Content-Security-Policy](https://scotthelme.co.uk/content-security-policy-an-introduction/) (a browser security feature) so that your page is likely to be safe even if an attacker finds a vulnerability in one of the first lines of defence above. + +Safe mode does not necessarily yield safe results when using extensions to Parsedown. Extensions should be evaluated on their own to determine their specific safety against XSS. + +## Escaping HTML + +> WARNING: This method is not safe from XSS! + +If you wish to escape HTML in trusted input, you can use the following: + +```php +$Parsedown->setMarkupEscaped(true); +``` + +Beware that this still allows users to insert unsafe scripting vectors, ex: `[xss](javascript:alert%281%29)`. + +## Questions + +**How does Parsedown work?** + +It tries to read Markdown like a human. First, it looks at the lines. It’s interested in how the lines start. This helps it recognise blocks. It knows, for example, that if a line starts with a `-` then perhaps it belongs to a list. Once it recognises the blocks, it continues to the content. As it reads, it watches out for special characters. This helps it recognise inline elements (or inlines). + +We call this approach "line based". We believe that Parsedown is the first Markdown parser to use it. Since the release of Parsedown, other developers have used the same approach to develop other Markdown parsers in PHP and in other languages. + +**Is it compliant with CommonMark?** + +It passes most of the CommonMark tests. Most of the tests that don't pass deal with cases that are quite uncommon. Still, as CommonMark matures, compliance should improve. + +**Who uses it?** + +[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [Statamic CMS](http://www.statamic.com/), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony Demo](https://github.com/symfony/demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents). + +**How can I help?** + +Use it, star it, share it and if you feel generous, [donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2). + +**What else should I know?** + +I also make [Nota](https://nota.md/) — a notes app designed for local Markdown files. diff --git a/vendor/parsedown/parsedown/composer.json b/vendor/parsedown/parsedown/composer.json new file mode 100644 index 00000000..b145680a --- /dev/null +++ b/vendor/parsedown/parsedown/composer.json @@ -0,0 +1,33 @@ +{ + "name": "erusev/parsedown", + "description": "Parser for Markdown.", + "keywords": ["markdown", "parser"], + "homepage": "http://parsedown.org", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "require": { + "php": ">=7.1", + "ext-mbstring": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.6" + }, + "autoload": { + "psr-0": {"Parsedown": ""} + }, + "autoload-dev": { + "psr-0": { + "TestParsedown": "test/", + "ParsedownTest": "test/", + "CommonMarkTest": "test/", + "CommonMarkTestWeak": "test/" + } + } +} diff --git a/vendor/shalvah/upgrader/LICENSE b/vendor/shalvah/upgrader/LICENSE new file mode 100644 index 00000000..e5666e1d --- /dev/null +++ b/vendor/shalvah/upgrader/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Shalvah + +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/vendor/shalvah/upgrader/README.md b/vendor/shalvah/upgrader/README.md new file mode 100644 index 00000000..920e1a25 --- /dev/null +++ b/vendor/shalvah/upgrader/README.md @@ -0,0 +1,54 @@ +# Upgrader + +[![Latest Stable Version](https://poser.pugx.org/shalvah/upgrader/v/stable)](https://packagist.org/packages/shalvah/upgrader) [![Total Downloads](https://poser.pugx.org/shalvah/upgrader/downloads)](https://packagist.org/packages/shalvah/upgrader) + +Releasing a new version of your PHP library with changes to the config file? Use this tool to offer an automated upgrade process to your users. Used for implementing automated upgrades in [Scribe](https://scribe.knuckles.wtf/laravel/migrating-v4) — just run `php artisan scribe:upgrade`. + +Give `Upgrader` a sample of your new config file, and the path to the user's old config file, and it'll figure out what's been added or removed in the new version. You can also tell it to move/rename certain fields or ignore others. + +```php +// Create a CLI `upgrade` command, where you call Upgrader + +// Relative path to the config file in the user's project +$userOldConfigFile = 'config/my_library.php'; +// Absolute path to a sample of the new config in your project +$sampleNewConfigFile = __DIR__ . '/../../config/my_library.php'; + +$upgrader = Upgrader::ofConfigFile($userOldConfigFile, $sampleNewConfigFile) + ->move('path', 'static.path') + ->dontTouch('ip_addresses'); + + // If this is a dry run, print the expected changes + if ($this->option('dry-run')) { + $changes = $upgrader->dryRun(); + if (empty($changes)) { + $this->info("No changes needed! Looks like you're all set."); + return; + } + + $this->info('The following changes will be made to your config file:'); + + foreach ($changes as $change) { + $this->info($change["description"]); + } + + return; +} + +// Otherwise, run the upgrade 🚀 +$upgrader->upgrade(); +``` + +Upgrader: +- Comes with "dry run" functionality, so you can review expected changes. +- Will back up the user's old config file to `{$file}.bak` so you can revert if you need to. +- Supports keys as dot notation + +Upgrader is still early days (0.x), with more robust features and docs planned. Read how I built it [here](https://blog.shalvah.me/posts/implementing-programmatic-file-transformations-in-php). + +## Installation +PHP 8+ is required. + +```bash +composer require shalvah/upgrader +``` diff --git a/vendor/shalvah/upgrader/composer.json b/vendor/shalvah/upgrader/composer.json new file mode 100644 index 00000000..5ee99610 --- /dev/null +++ b/vendor/shalvah/upgrader/composer.json @@ -0,0 +1,55 @@ +{ + "name": "shalvah/upgrader", + "license": "MIT", + "description": "Create automatic upgrades for your package.", + "keywords": [ + "upgrade" + ], + "homepage": "http://github.com/shalvah/upgrader", + "authors": [ + { + "name": "Shalvah", + "email": "hello@shalvah.me" + } + ], + "require": { + "php": ">=8.0", + "illuminate/support": ">=8.0", + "nikic/php-parser": "^5.0" + }, + "require-dev": { + "dms/phpunit-arraysubset-asserts": "^0.2.0", + "pestphp/pest": "^1.21", + "phpstan/phpstan": "^1.0", + "spatie/ray": "^1.33" + }, + "autoload": { + "psr-4": { + "Shalvah\\Upgrader\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Shalvah\\Upgrader\\Tests\\": "tests/" + } + }, + "scripts": { + "lint": "phpstan analyse -c ./phpstan.neon src --memory-limit 1G", + "test": "pest --stop-on-failure --coverage --min=90", + "test-ci": "pest" + }, + "config": { + "preferred-install": "dist", + "sort-packages": true, + "process-timeout": 600, + "allow-plugins": { + "pestphp/pest-plugin": true + } + }, + "funding": [ + { + "type": "patreon", + "url": "https://patreon.com/shalvah" + } + ] +} diff --git a/vendor/shalvah/upgrader/phpstan.neon b/vendor/shalvah/upgrader/phpstan.neon new file mode 100644 index 00000000..de3686d7 --- /dev/null +++ b/vendor/shalvah/upgrader/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + level: 5 + reportUnmatchedIgnoredErrors: true + inferPrivatePropertyTypeFromConstructor: true + ignoreErrors: + - '#Method .+ should return .+\|null but return statement is missing.#' \ No newline at end of file diff --git a/vendor/shalvah/upgrader/src/ComparesAstNodes.php b/vendor/shalvah/upgrader/src/ComparesAstNodes.php new file mode 100644 index 00000000..161e4e13 --- /dev/null +++ b/vendor/shalvah/upgrader/src/ComparesAstNodes.php @@ -0,0 +1,92 @@ + ($node->key->value ?? null) === $key + ); + } + + /** + * @param Expr\ArrayItem[] $arrayItems + * @param string $key + * + * @return bool + */ + protected function hasItem(array $arrayItems, string $key): bool + { + return boolval($this->getItem($arrayItems, $key)); + } + + protected function expressionNodeIsArray(Expr $expressionNode): bool + { + return $expressionNode instanceof Expr\Array_; + } + + /** + * @param Expr\ArrayItem[] $arrayItems + * + * @return bool + */ + protected function arrayIsList(array $arrayItems): bool + { + // List arrays, like ['a', 'b', 'c'] have all `key`s as null when parsed + return isset($arrayItems[0]) && $arrayItems[0]->key === null; + } + + /** + * Get values in $list that are not in $otherList. + * Replaces array_diff($list, $otherList) + * + * @template T of Node + * @param T[] $list + * @param T[] $otherList + * + * @return array + */ + protected function subtractOtherListFromList(array $list, array $otherList): array + { + $diff = []; + // There's no easy way to compare two AST nodes for equality + // So we'll just convert them to strings and check if they're equal + $otherListWithItemsAsText = array_map( + fn($item) => $this->convertAstNodesToText($item), $otherList + ); + foreach ($list as $item) { + $itemAsText = $this->convertAstNodesToText($item); + if (!in_array($itemAsText, $otherListWithItemsAsText)) { + $diff[] = ['ast' => $item, 'text' => $itemAsText]; + } + } + + return $diff; + } + + /** + * @param Node[]|Node $nodes + * + * @return string + */ + protected function convertAstNodesToText($nodes): string + { + return (new NodeDumper())->dump($nodes); + } +} diff --git a/vendor/shalvah/upgrader/src/ModifiesAsts.php b/vendor/shalvah/upgrader/src/ModifiesAsts.php new file mode 100644 index 00000000..cf6b975a --- /dev/null +++ b/vendor/shalvah/upgrader/src/ModifiesAsts.php @@ -0,0 +1,80 @@ +findInnerArrayByKey($allArrayItems, $keySegments, function (array $searchArray) use ($childKey, $newValue) { + $item = Arr::first( + // @phpstan-ignore-next-line PHPStan doesn't yet support ?? + $searchArray, fn(ArrayItem $node) => ($node->key->value ?? null) === $childKey + ); + $item->value = $newValue; + }); + } + + protected function addKey(array &$allArrayItems, string $dotPath, $newItem) + { + $keySegments = explode('.', $dotPath); + array_pop($keySegments); + $this->findInnerArrayByKey($allArrayItems, $keySegments, function (array &$searchArray) use ($newItem) { + $searchArray[] = $newItem; + }); + } + + protected function deleteKey(array &$allArrayItems, string $dotPath) + { + $keySegments = explode('.', $dotPath); + $childKey = array_pop($keySegments); + $this->findInnerArrayByKey($allArrayItems, $keySegments, function (array &$searchArray) use ($childKey) { + foreach ($searchArray as $index => $item) { + if ($item->key->value === $childKey) { + unset($searchArray[$index]); + } + } + $searchArray = array_values($searchArray); + }); + } + + protected function pushItemOntoList(array &$allArrayItems, string $listDotPath, $newValue) + { + $keySegments = explode('.', $listDotPath); + $this->findInnerArrayByKey($allArrayItems, $keySegments, function (array &$list) use ($newValue) { + $list[] = new Expr\ArrayItem($newValue, null); + }); + } + + /** + * @param Expr\ArrayItem[] $arrayItems + * @param array $keySegments The dot notation key, split into an array + * @param callable $callback The operation to be performed on the found array + */ + protected function findInnerArrayByKey(array &$arrayItems, array $keySegments, callable $callback) + { + $searchArray =& $arrayItems; + while (count($keySegments)) { + $nextKeySegment = array_shift($keySegments); + foreach ($searchArray as $item) { + if ( + ($item->key instanceof Node\Scalar\String_ + || $item->key instanceof Node\Scalar\LNumber) + && $item->key->value === $nextKeySegment + ) { + $searchArray =& $item->value->items; + break; + } + } + } + + $callback($searchArray); + } +} diff --git a/vendor/shalvah/upgrader/src/ReadsAndWritesAsts.php b/vendor/shalvah/upgrader/src/ReadsAndWritesAsts.php new file mode 100644 index 00000000..035bbf6a --- /dev/null +++ b/vendor/shalvah/upgrader/src/ReadsAndWritesAsts.php @@ -0,0 +1,84 @@ +createForHostVersion(); + $ast = $parser->parse($sourceCode); + $traverser = new NodeTraverser(); + $traverser->addVisitor(new NodeVisitor\NameResolver(null, [ + 'preserveOriginalNames' => true + ])); + return $traverser->traverse($ast); + } + + protected function parseFilePreservingFormat(string $filePath): array + { + $sourceCode = file_get_contents($filePath); + + // Doing this because we need to preserve the formatting when printing later + $parser = (new ParserFactory())->createForHostVersion(); + $this->originalAst = $parser->parse($sourceCode); + $this->originalTokens = $parser->getTokens(); + $traverser = new NodeTraverser(); + $traverser->addVisitor(new NodeVisitor\CloningVisitor()); + $traverser->addVisitor(new NodeVisitor\NameResolver(null, [ + 'preserveOriginalNames' => true + ])); + + return $traverser->traverse($this->originalAst); + } + + /** + * Print out the changes into the user's config file (saving the old one as a backup) + */ + protected function writeAstToFile(array $newConfigAst, string $configFilePath) + { + $newConfigAst = $this->cleanUpAstForPrinting($newConfigAst); + + $prettyPrinter = new PrettyPrinter\Standard(['shortArraySyntax' => true]); + $astAsText = $prettyPrinter->printFormatPreserving($newConfigAst, $this->originalAst, $this->originalTokens); + + rename($configFilePath, "$configFilePath.bak"); + file_put_contents($configFilePath, $astAsText); + } + + /** + * Shorten namespaces back before printing and add any missing use statements + */ + protected function cleanUpAstForPrinting(array $ast): array + { + $traverser = new NodeTraverser(); + $traverser->addVisitor(new UnresolveNamespaces); + $ast = $traverser->traverse($ast); + + $sampleConfigAst = $this->getSampleNewConfigFileAsAst(); + $newUseStatements = Arr::where( + $sampleConfigAst, fn(Node $node) => $node instanceof Stmt\Use_ + ); + $alreadyPresentUseStatements = Arr::where( + $ast, fn(Node $node) => $node instanceof Stmt\Use_ + ); + $newUseStatements = $this->subtractOtherListFromList($newUseStatements, $alreadyPresentUseStatements); + foreach ($newUseStatements as $newUseStatement) { + array_unshift($ast, $newUseStatement['ast']); + } + return $ast; + } +} diff --git a/vendor/shalvah/upgrader/src/UnresolveNamespaces.php b/vendor/shalvah/upgrader/src/UnresolveNamespaces.php new file mode 100644 index 00000000..200b454a --- /dev/null +++ b/vendor/shalvah/upgrader/src/UnresolveNamespaces.php @@ -0,0 +1,21 @@ +getAttribute('originalName', $node); + } + } + + public function beforeTraverse(array $nodes) {} + + public function enterNode(Node $node) {} + + public function afterTraverse(array $nodes) {} +} \ No newline at end of file diff --git a/vendor/shalvah/upgrader/src/Upgrader.php b/vendor/shalvah/upgrader/src/Upgrader.php new file mode 100644 index 00000000..3bfce9a5 --- /dev/null +++ b/vendor/shalvah/upgrader/src/Upgrader.php @@ -0,0 +1,277 @@ +configFiles['user_old'] = $userOldConfigRelativePath; + $this->configFiles['sample_new'] = $sampleNewConfigAbsolutePath; + } + + public static function ofConfigFile(string $userOldConfigRelativePath, string $sampleNewConfigAbsolutePath): self + { + return new self($userOldConfigRelativePath, $sampleNewConfigAbsolutePath); + } + + public function move(string $oldKey, string $newKey): self + { + $this->movedKeys[$oldKey] = $newKey; + return $this; + } + + /** + * "Don't touch" these config items. + * Useful if they contain arrays with keys specified by the user, + * or lists with values provided entirely by the user + */ + public function dontTouch(string ...$keys): self + { + $this->dontTouchKeys = [...$this->dontTouchKeys, ...$keys]; + return $this; + } + + public function dryRun(): array + { + return $this->fetchChanges(); + } + + public function upgrade() + { + $this->fetchChanges(); + $upgradedConfig = $this->applyChanges(); + $this->writeAstToFile($upgradedConfig, $this->configFiles['user_old']); + } + + protected function fetchChanges(): array + { + if (!empty($this->changes)) { + return $this->changes; + } + + [$userCurrentConfigFile, $sampleNewConfigFile] = $this->parseConfigFiles(); + + $userCurrentConfigArray = Arr::first( + $userCurrentConfigFile, fn(Node $node) => $node instanceof Stmt\Return_ + )->expr->items; + $sampleNewConfigArray = Arr::first( + $sampleNewConfigFile, fn(Node $node) => $node instanceof Stmt\Return_ + )->expr->items; + $this->fetchAddedItems($userCurrentConfigArray, $sampleNewConfigArray); + $this->fetchRemovedAndMovedItems($userCurrentConfigArray, $sampleNewConfigArray); + return $this->changes; + } + + /** + * @param Expr\ArrayItem[] $userCurrentConfig + * @param Expr\ArrayItem[] $incomingConfig + */ + protected function fetchAddedItems( + array $userCurrentConfig, array $incomingConfig, string $rootKey = '' + ) + { + if ($this->arrayIsList($incomingConfig)) { + // We're dealing with a list of items (numeric array) + $diff = $this->subtractOtherListFromList($incomingConfig, $userCurrentConfig); + foreach ($diff as $item) { + $this->changes[] = [ + 'type' => self::CHANGE_LIST_ITEM_ADDED, + 'key' => $rootKey, + 'value' => $item['ast']->value, + 'description' => "- '{$item['text']}' will be added to `$rootKey`.", + ]; + } + return; + } + + // TODO handle cases of mixed assoc- and list array + foreach ($incomingConfig as $arrayItem) { + // @phpstan-ignore-next-line Not yet sure how to handle mixed + $key = $arrayItem->key->value; + $value = $arrayItem->value; + + $fullKey = $this->getFullKey($key, $rootKey); + if ($this->shouldntTouch($fullKey)) { + continue; + } + + // Key is in new, but not in old + if (!$this->hasItem($userCurrentConfig, $key)) { + $this->changes[] = [ + 'type' => self::CHANGE_ADDED, + 'key' => $fullKey, + 'description' => "- `{$fullKey}` will be added.", + 'value' => $value, + 'item' => $this->getItem($incomingConfig, $key), + ]; + } else { + if ($this->expressionNodeIsArray($value)) { + // Key is in both old and new; recurse into array and compare the inner items + $this->fetchAddedItems( + $this->getItem($userCurrentConfig, $key)->value->items ?? [], $value->items ?? [], $fullKey + ); + } + } + + } + } + + /** + * @param Expr\ArrayItem[] $userCurrentConfig + * @param Expr\ArrayItem[]|null $incomingConfig + */ + protected function fetchRemovedAndMovedItems( + array $userCurrentConfig, $incomingConfig, string $rootKey = '' + ) + { + if ($this->arrayIsList($incomingConfig)) { + // A list of items (numeric array) + // We only add, not remove. + return; + } + + // Loop over the old config + // TODO handle cases of mixed assoc- and list array + foreach ($userCurrentConfig as $arrayItem) { + // @phpstan-ignore-next-line Not yet sure how to handle mixed + $key = $arrayItem->key->value; + $value = $arrayItem->value; + + $fullKey = $this->getFullKey($key, $rootKey); + + // Key is in old, but was moved somewhere else in new + if ($this->wasKeyMoved($fullKey)) { + $this->changes[] = [ + 'type' => self::CHANGE_MOVED, + 'key' => $fullKey, + 'new_key' => $this->movedKeys[$fullKey], + 'description' => "- `$fullKey` will be moved to `{$this->movedKeys[$fullKey]}`.", + 'new_value' => $value, + ]; + continue; + } + + // Key is in old, but not in new + if (!$this->hasItem($incomingConfig, $key)) { + $this->changes[] = [ + 'type' => self::CHANGE_REMOVED, + 'key' => $fullKey, + 'description' => "- `$fullKey` will be removed.", + ]; + continue; + } + + if (!$this->shouldntTouch($fullKey) && $this->expressionNodeIsArray($value)) { + // Key is in both old and new; recurse into array and compare the inner items + $this->fetchRemovedAndMovedItems( + $value->items ?? [], $this->getItem($incomingConfig, $key)->value->items ?? [], $fullKey + ); + } + } + } + + protected function wasKeyMoved(string $oldKey): bool + { + return array_key_exists($oldKey, $this->movedKeys); + } + + protected function shouldntTouch(string $key): bool + { + return in_array($key, $this->dontTouchKeys); + } + + /** + * Resolve config item key with dot notation + */ + private function getFullKey(string $key, string $rootKey = ''): string + { + if (empty($rootKey)) { + return $key; + } + + return "$rootKey.$key"; + } + + public function parseConfigFiles(): array + { + $userCurrentConfig = $this->getUserOldConfigFileAsAst(); + $incomingConfig = $this->getSampleNewConfigFileAsAst(); + + return [$userCurrentConfig, $incomingConfig]; + } + + protected function getUserOldConfigFileAsAst(): array + { + if (!empty($this->userOldConfigFileAst)) { + return $this->userOldConfigFileAst; + } + + $this->userOldConfigFileAst = $this->parseFilePreservingFormat($this->configFiles['user_old']); + return $this->userOldConfigFileAst; + } + + protected function getSampleNewConfigFileAsAst(): array + { + if (!empty($this->sampleNewConfigFileAst)) { + return $this->sampleNewConfigFileAst; + } + + $this->sampleNewConfigFileAst = $this->parseFile($this->configFiles['sample_new']); + return $this->sampleNewConfigFileAst; + } + + protected function applyChanges(): array + { + $userConfigAst = $this->getUserOldConfigFileAsAst(); + $configArray =& Arr::first( + $userConfigAst, fn(Node $node) => $node instanceof Stmt\Return_ + )->expr->items; + + foreach ($this->changes as $change) { + switch ($change['type']) { + case self::CHANGE_ADDED: + $this->addKey($configArray, $change['key'], $change['item']); + break; + case self::CHANGE_REMOVED: + $this->deleteKey($configArray, $change['key']); + break; + case self::CHANGE_MOVED: + // Move old value to new key + $this->setValue($configArray, $change['new_key'], $change['new_value']); + // Then delete old key + $this->deleteKey($configArray, $change['key']); + break; + case self::CHANGE_LIST_ITEM_ADDED: + $this->pushItemOntoList($configArray, $change['key'], $change['value']); + break; + } + } + + return $userConfigAst; + } +} diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md new file mode 100644 index 00000000..2697e861 --- /dev/null +++ b/vendor/symfony/var-exporter/CHANGELOG.md @@ -0,0 +1,49 @@ +CHANGELOG +========= + +8.0 +--- + + * Restrict `ProxyHelper::generateLazyProxy()` to generating abstraction-based lazy decorators; use native lazy proxies otherwise + * Remove `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead + * Remove `ProxyHelper::generateLazyGhost()`, use native lazy objects instead + +7.4 +--- + + * Add support for exporting named closures + +7.3 +--- + + * Deprecate using `ProxyHelper::generateLazyProxy()` when native lazy proxies can be used - the method should be used to generate abstraction-based lazy decorators only + * Deprecate `LazyGhostTrait` and `LazyProxyTrait`, use native lazy objects instead + * Deprecate `ProxyHelper::generateLazyGhost()`, use native lazy objects instead + +7.2 +--- + + * Allow reinitializing lazy objects with a new initializer + +6.4 +--- + + * Deprecate per-property lazy-initializers + +6.2 +--- + + * Add support for lazy ghost objects and virtual proxies + * Add `Hydrator::hydrate()` + * Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()` + * Add support for hydrating from native (array) casts + +5.1.0 +----- + + * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values + +4.2.0 +----- + + * added the component diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php new file mode 100644 index 00000000..2acecc47 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class ClassNotFoundException extends \Exception implements ExceptionInterface +{ + public function __construct(string $class, ?\Throwable $previous = null) + { + parent::__construct(\sprintf('Class "%s" not found.', $class), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Exception/ExceptionInterface.php b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php new file mode 100644 index 00000000..adfaed47 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/vendor/symfony/var-exporter/Exception/LogicException.php b/vendor/symfony/var-exporter/Exception/LogicException.php new file mode 100644 index 00000000..619d0559 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php new file mode 100644 index 00000000..bc2bcaa2 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class NotInstantiableTypeException extends \Exception implements ExceptionInterface +{ + public function __construct(string $type, ?\Throwable $previous = null) + { + parent::__construct(\sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + } +} diff --git a/vendor/symfony/var-exporter/Hydrator.php b/vendor/symfony/var-exporter/Hydrator.php new file mode 100644 index 00000000..b718921d --- /dev/null +++ b/vendor/symfony/var-exporter/Hydrator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator; + +/** + * Utility class to hydrate the properties of an object. + * + * @author Nicolas Grekas + */ +final class Hydrator +{ + /** + * Sets the properties of an object, including private and protected ones. + * + * For example: + * + * // Sets the public or protected $object->propertyName property + * Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + * + * // Sets a private property defined on its parent Bar class: + * Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + * + * // Alternative way to set the private $object->privateBarProperty property + * Hydrator::hydrate($object, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated + * by using the special "\0" property name to define their internal value: + * + * // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc. + * Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // Hydrates an ArrayObject populated with $inputArray + * Hydrator::hydrate($object, ["\0" => [$inputArray]]); + * + * @template T of object + * + * @param T $instance The object to hydrate + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + */ + public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object + { + if ($properties) { + $class = $instance::class; + $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class); + + foreach ($properties as $name => &$value) { + [$scope, $name, $writeScope] = $propertyScopes[$name] ?? [$class, $name, $class]; + $scopedProperties[$writeScope ?? $scope][$name] = &$value; + } + unset($value); + } + + foreach ($scopedProperties as $scope => $properties) { + if ($properties) { + (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance); + } + } + + return $instance; + } +} diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php new file mode 100644 index 00000000..10200c00 --- /dev/null +++ b/vendor/symfony/var-exporter/Instantiator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Registry; + +/** + * A utility class to create objects without calling their constructor. + * + * @author Nicolas Grekas + */ +final class Instantiator +{ + /** + * Creates an object and sets its properties without calling its constructor nor any other methods. + * + * @see Hydrator::hydrate() for examples + * + * @template T of object + * + * @param class-string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + * + * @throws ExceptionInterface When the instance cannot be created + */ + public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object + { + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + + if (Registry::$cloneable[$class]) { + $instance = clone Registry::$prototypes[$class]; + } elseif (Registry::$instantiableWithoutConstructor[$class]) { + $instance = $reflector->newInstanceWithoutConstructor(); + } elseif (null === Registry::$prototypes[$class]) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) { + $instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}'); + } else { + $instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}'); + } + + return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php new file mode 100644 index 00000000..ff9db4c9 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Exporter.php @@ -0,0 +1,434 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Exporter +{ + /** + * Prepares an array of values for VarExporter. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @return array + * + * @throws NotInstantiableTypeException When a value cannot be serialized + */ + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new NotInstantiableTypeException(get_resource_type($value).' resource'); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + ++$value->count; + continue; + } + $refsPool[] = [&$refs[$k], $value, &$value]; + $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); + } + + if (\is_array($value)) { + if ($value) { + $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) || $value instanceof \UnitEnum) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + if ($value instanceof \Closure && !($r = new \ReflectionFunction($value))->isAnonymous()) { + $callable = [$r->getClosureThis() ?? $r->getClosureCalledClass()?->name, $r->name]; + $r = $callable[0] ? new \ReflectionMethod(...$callable) : null; + $value = new NamedClosure(self::prepare($callable, $objectsPool, $refsPool, $objectsCount, $valueIsStatic), $r); + + goto handle_value; + } + + $class = $value::class; + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); + $properties = []; + $sleep = null; + $proto = Registry::$prototypes[$class]; + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($arrayValue = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + if ($reflector->hasMethod('__unserialize')) { + $properties = $arrayValue; + goto prepare_value; + } + } elseif (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); + + // populates Registry::$prototypes[$class] with a new instance + Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); + } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { + // By implementing Serializable, SplObjectStorage breaks + // internal references; let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = ['SplObjectStorage' => ["\0" => $properties]]; + $arrayValue = (array) $value; + } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class) { + ++$objectsCount; + $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; + $value = new Reference($id); + goto handle_value; + } else { + if (method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE); + $value = null; + goto handle_value; + } + $sleep = array_flip($sleep); + } + + $arrayValue = (array) $value; + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $i = 0; + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $parent = $reflector; + do { + $p = $parent->hasProperty($n) ? $parent->getProperty($n) : null; + } while (!$p && $parent = $parent->getParentClass()); + + $c = $p && (!$p->isPublic() || $p->isProtectedSet() || $p->isPrivateSet()) ? $p->class : 'stdClass'; + } elseif ('*' === $n[1]) { + $n = substr($n, 3); + $c = $reflector->getProperty($n)->class; + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null !== $sleep) { + if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) { + unset($arrayValue[$name]); + continue; + } + unset($sleep[$name], $sleep[$n]); + } + if ("\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + $properties[$c][$n] = $v; + } elseif (!\array_key_exists($name, $proto) || $proto[$name] !== $v) { + $properties[match ($c) { + 'Error' => 'TypeError', + 'Exception' => 'ErrorException', + default => $c, + }][$n] = $v; + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + } + } + if (method_exists($class, '__unserialize')) { + $properties = $arrayValue; + } + + prepare_value: + $objectsPool[$value] = [$id = \count($objectsPool)]; + $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)]; + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + public static function export($value, $indent = '') + { + switch (true) { + case \is_int($value) || \is_float($value): return var_export($value, true); + case [] === $value: return '[]'; + case false === $value: return 'false'; + case true === $value: return 'true'; + case null === $value: return 'null'; + case '' === $value: return "''"; + case $value instanceof \UnitEnum: return '\\'.ltrim(var_export($value, true), '\\'); + } + + if ($value instanceof Reference) { + if (0 <= $value->id) { + return '$o['.$value->id.']'; + } + if (!$value->count) { + return self::export($value->value, $indent); + } + $value = -$value->id; + + return '&$r['.$value.']'; + } + $subIndent = $indent.' '; + + if ($value instanceof NamedClosure) { + if ($value->method?->isPublic() ?? true) { + return match (true) { + null === $value->callable[0] => '\\'.$value->callable[1], + \is_string($value->callable[0]) => '\\'.$value->callable[0].'::'.$value->callable[1], + \is_object($value->callable[0]) => self::export($value->callable[0], $subIndent).'->'.$value->callable[1], + }.'(...)'; + } + + return 'new \ReflectionMethod(\\'.$value->method->class.'::class, '.self::export($value->callable[1]).')' + .'->getClosure('.(\is_object($value->callable[0]) ? self::export($value->callable[0]) : '').')'; + } + + if (\is_string($value)) { + $code = \sprintf("'%s'", addcslashes($value, "'\\")); + + $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { + $m[1] = \sprintf('\'."%s".\'', str_replace( + ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], + ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], + $m[1] + )); + + if ("'" === $m[2]) { + return substr($m[1], 0, -2); + } + + if (str_ends_with($m[1], 'n".\'')) { + return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); + } + + return $m[1].$m[2]; + }, $code, -1, $count); + + if ($count && str_starts_with($code, "''.")) { + $code = substr($code, 3); + } + + return $code; + } + + if (\is_array($value)) { + $j = -1; + $code = ''; + foreach ($value as $k => $v) { + $code .= $subIndent; + if (!\is_int($k) || 1 !== $k - $j) { + $code .= self::export($k, $subIndent).' => '; + } + if (\is_int($k) && $k > $j) { + $j = $k; + } + $code .= self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Values) { + $code = $subIndent."\$r = [],\n"; + foreach ($value->values as $k => $v) { + $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Registry) { + return self::exportRegistry($value, $indent, $subIndent); + } + + if ($value instanceof Hydrator) { + return self::exportHydrator($value, $indent, $subIndent); + } + + throw new \UnexpectedValueException(\sprintf('Cannot export value of type "%s".', get_debug_type($value))); + } + + private static function exportRegistry(Registry $value, string $indent, string $subIndent): string + { + $code = ''; + $serializables = []; + $seen = []; + $prototypesAccess = 0; + $factoriesAccess = 0; + $r = '\\'.Registry::class; + $j = -1; + + foreach ($value->classes as $k => $class) { + if (':' === ($class[1] ?? null)) { + $serializables[$k] = $class; + continue; + } + if (!Registry::$instantiableWithoutConstructor[$class]) { + if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) { + $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}'; + } else { + $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}'; + } + if (is_subclass_of($class, 'Throwable')) { + $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0"; + $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4); + } + continue; + } + $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : ''); + $j = $k; + $eol = ",\n"; + $c = '['.self::export($class).']'; + + if ($seen[$class] ?? false) { + if (Registry::$cloneable[$class]) { + ++$prototypesAccess; + $code .= 'clone $p'.$c; + } else { + ++$factoriesAccess; + $code .= '$f'.$c.'()'; + } + } else { + $seen[$class] = true; + if (Registry::$cloneable[$class]) { + $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p'; + } else { + $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f'; + $eol = '()'.$eol; + } + $code .= '('.substr($c, 1, -1).'))'; + } + $code .= $eol; + } + + if (1 === $prototypesAccess) { + $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code); + } + if (1 === $factoriesAccess) { + $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code); + } + if ('' !== $code) { + $code = "\n".$code.$indent; + } + + if ($serializables) { + $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')'; + } else { + $code = '['.$code.']'; + } + + return '$o = '.$code; + } + + private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string + { + $code = ''; + foreach ($value->properties as $class => $properties) { + $code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n"; + } + + $code = [ + self::export($value->registry, $subIndent), + self::export($value->values, $subIndent), + '' !== $code ? "[\n".$code.$subIndent.']' : '[]', + self::export($value->value, $subIndent), + self::export($value->wakeups, $subIndent), + ]; + + return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + } + + /** + * @param \ArrayIterator|\ArrayObject $value + * @param \ArrayIterator|\ArrayObject $proto + */ + private static function getArrayObjectProperties($value, $proto): array + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector); + + $properties = [ + $arrayValue = (array) $value, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ]; + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if ([[], 0, 'ArrayIterator'] === $properties) { + $properties = []; + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = [$reflector->class => ["\0" => $properties]]; + } + + return [$arrayValue, $properties]; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php new file mode 100644 index 00000000..0f249345 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -0,0 +1,324 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Hydrator +{ + public const PROPERTY_HAS_HOOKS = 1; + public const PROPERTY_NOT_BY_REF = 2; + + public static array $hydrators = []; + public static array $simpleHydrators = []; + public static array $propertyScopes = []; + + public function __construct( + public readonly Registry $registry, + public readonly ?Values $values, + public readonly array $properties, + public readonly mixed $value, + public readonly array $wakeups, + ) { + } + + public static function hydrate($objects, $values, $properties, $value, $wakeups) + { + foreach ($properties as $class => $vars) { + (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects); + } + foreach ($wakeups as $k => $v) { + if (\is_array($v)) { + $objects[-$k]->__unserialize($v); + } else { + $objects[$v]->__wakeup(); + } + } + + return $value; + } + + public static function getHydrator($class) + { + $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(null, new class extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(null, new class extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $objects) { + foreach ($properties as $name => $values) { + if ("\0" === $name) { + foreach ($values as $i => $v) { + for ($j = 0; $j < \count($v); ++$j) { + $objects[$i][$v[$j]] = $v[++$j]; + } + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $objects) use ($constructor) { + foreach ($properties as $name => $values) { + if ("\0" !== $name) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + } + foreach ($properties["\0"] ?? [] as $i => $v) { + $constructor($objects[$i], $v); + } + }; + } + + if (!$classReflector->isInternal()) { + return $baseHydrator->bindTo(null, $class); + } + + if ($classReflector->name !== $class) { + return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $objects) use ($propertySetters) { + foreach ($properties as $name => $values) { + if ($setValue = $propertySetters[$name] ?? null) { + foreach ($values as $i => $v) { + $setValue($objects[$i], $v); + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + public static function getSimpleHydrator($class) + { + $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { + $notByRef = (array) $this; + + foreach ($properties as $name => &$value) { + if (!$noRef = $notByRef[$name] ?? false) { + $object->$name = $value; + $object->$name = &$value; + } elseif (true !== $noRef) { + $noRef($object, $value); + } else { + $object->$name = $value; + } + } + })->bindTo(new \stdClass()); + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(new \stdClass(), new class extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(new \stdClass(), new class extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $object) { + foreach ($properties as $name => &$value) { + if ("\0" !== $name) { + $object->$name = $value; + $object->$name = &$value; + continue; + } + for ($i = 0; $i < \count($value); ++$i) { + $object[$value[$i]] = $value[++$i]; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $object) use ($constructor) { + foreach ($properties as $name => &$value) { + if ("\0" === $name) { + $constructor($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + if (!$classReflector->isInternal()) { + $notByRef = new \stdClass(); + foreach ($classReflector->getProperties() as $propertyReflector) { + if ($propertyReflector->isStatic()) { + continue; + } + if (!$propertyReflector->isAbstract() && $propertyReflector->getHooks()) { + $notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...); + } elseif ($propertyReflector->isReadOnly()) { + $notByRef->{$propertyReflector->name} = static function ($object, $value) use ($propertyReflector) { + if (!$propertyReflector->isInitialized($object)) { + $propertyReflector->setValue($object, $value); + } + }; + } + } + + return $baseHydrator->bindTo($notByRef, $class); + } + + if ($classReflector->name !== $class) { + return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $object) use ($propertySetters) { + foreach ($properties as $name => &$value) { + if ($setValue = $propertySetters[$name] ?? null) { + $setValue($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + public static function getPropertyScopes($class): array + { + $propertyScopes = []; + $r = new \ReflectionClass($class); + + foreach ($r->getProperties() as $property) { + $flags = $property->getModifiers(); + + if (\ReflectionProperty::IS_STATIC & $flags) { + continue; + } + $name = $property->name; + $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0); + + if (!$property->isAbstract() && $h = $property->getHooks()) { + $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0); + } + + if (\ReflectionProperty::IS_PRIVATE & $flags) { + $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, null, $access, $property]; + + continue; + } + + $propertyScopes[$name] = [$class, $name, null, $access, $property]; + + if ($flags & \ReflectionProperty::IS_PRIVATE_SET) { + $propertyScopes[$name][2] = $property->class; + } + + if (\ReflectionProperty::IS_PROTECTED & $flags) { + $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; + } + } + + while ($r = $r->getParentClass()) { + $class = $r->name; + + foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { + $flags = $property->getModifiers(); + + if (\ReflectionProperty::IS_STATIC & $flags) { + continue; + } + $name = $property->name; + $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0); + + if ($h = $property->getHooks()) { + $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0); + } + + $propertyScopes["\0$class\0$name"] = [$class, $name, null, $access, $property]; + $propertyScopes[$name] ??= $propertyScopes["\0$class\0$name"]; + } + } + + return $propertyScopes; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyDecoratorTrait.php b/vendor/symfony/var-exporter/Internal/LazyDecoratorTrait.php new file mode 100644 index 00000000..bd8e4d14 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyDecoratorTrait.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; + +/** + * @internal + */ +trait LazyDecoratorTrait +{ + #[Ignore] + private readonly LazyObjectState $lazyObjectState; + + /** + * Creates a lazy-loading decorator. + * + * @param \Closure():object $initializer Returns the proxied object + * @param static|null $instance + */ + public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static + { + $class = $instance ? $instance::class : static::class; + + if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + + $instance ??= (Registry::$classReflectors[$class] ??= ($r = new \ReflectionClass($class))->hasProperty('lazyObjectState') + ? $r + : throw new \LogicException('Cannot create a lazy proxy for a non-decorator object.') + )->newInstanceWithoutConstructor(); + + $state = $instance->lazyObjectState ??= new LazyObjectState(); + $state->initializer = null; + unset($state->realInstance); + + foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { + $reset($instance, []); + } + $state->initializer = $initializer; + + return $instance; + } + + public function __construct(...$args) + { + self::createLazyProxy(static fn () => new parent(...$args), $this); + } + + public function __destruct() + { + } + + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + return isset($this->lazyObjectState->realInstance); + } + + public function initializeLazyObject(): parent + { + return $this->lazyObjectState->realInstance; + } + + public function resetLazyObject(): bool + { + if (!isset($this->lazyObjectState->initializer)) { + return false; + } + unset($this->lazyObjectState->realInstance); + + return true; + } + + public function &__get($name): mixed + { + $instance = $this->lazyObjectState->realInstance; + $class = $this::class; + + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + $notByRef = 0; + + if ([, , , $access] = $propertyScopes[$name] ?? null) { + $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF || ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET; + } + + if ($notByRef || 2 !== ((Registry::$parentGet[$class] ??= Registry::getParentGet($class)) ?: 2)) { + $value = $instance->$name; + + return $value; + } + + try { + return $instance->$name; + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + $instance->$name = []; + + return $instance->$name; + } catch (\Error) { + if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) { + throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious()); + } + + throw $e; + } + } + } + + public function __set($name, $value): void + { + $this->lazyObjectState->realInstance->$name = $value; + } + + public function __isset($name): bool + { + return isset($this->lazyObjectState->realInstance->$name); + } + + public function __unset($name): void + { + if ($this->lazyObjectState->initializer) { + unset($this->lazyObjectState->realInstance->$name); + } + } + + public function __serialize(): array + { + return [$this->lazyObjectState->realInstance]; + } + + public function __unserialize($data): void + { + $this->lazyObjectState = new LazyObjectState(); + $this->lazyObjectState->realInstance = $data[0]; + } + + public function __clone(): void + { + $this->lazyObjectState->realInstance; // initialize lazy object + $this->lazyObjectState = clone $this->lazyObjectState; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php new file mode 100644 index 00000000..75b182a1 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * Stores the state of lazy objects and caches related reflection information. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectRegistry +{ + /** + * @var array + */ + public static array $classReflectors = []; + + /** + * @var array> + */ + public static array $defaultProperties = []; + + /** + * @var array> + */ + public static array $classResetters = []; + + /** + * @var array + */ + public static array $classAccessors = []; + + /** + * @var array + */ + public static array $parentGet = []; + + public static function getClassResetters($class) + { + $classProperties = []; + $hookedProperties = []; + + if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { + $propertyScopes = []; + } else { + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + } + + foreach ($propertyScopes as $key => [$scope, $name, $writeScope, $access]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k !== $key || "\0$class\0lazyObjectState" === $k) { + continue; + } + + if ($access & Hydrator::PROPERTY_HAS_HOOKS) { + $hookedProperties[$k] = true; + } else { + $classProperties[$writeScope ?? $scope][$name] = $key; + } + } + + $resetters = []; + foreach ($classProperties as $scope => $properties) { + $resetters[] = \Closure::bind(static function ($instance, $skippedProperties) use ($properties) { + foreach ($properties as $name => $key) { + if (!\array_key_exists($key, $skippedProperties)) { + unset($instance->$name); + } + } + }, null, $scope); + } + + return $resetters; + } + + public static function getParentGet($class): int + { + $parent = get_parent_class($class); + + if (!$parent || !method_exists($parent, '__get')) { + return 0; + } + + $m = new \ReflectionMethod($parent, '__get'); + + return !$m->isAbstract() && !$m->isPrivate() ? ($m->returnsReference() ? 2 : 1) : 0; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectState.php b/vendor/symfony/var-exporter/Internal/LazyObjectState.php new file mode 100644 index 00000000..2ef3edc7 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectState.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * Keeps the state of lazy objects. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectState +{ + public ?\Closure $initializer = null; + public object $realInstance; + public object $cloneInstance; + + public function __clone() + { + if (isset($this->cloneInstance)) { + try { + $this->realInstance = $this->cloneInstance; + } finally { + unset($this->cloneInstance); + } + } elseif (isset($this->realInstance)) { + $this->realInstance = clone $this->realInstance; + } + } + + public function __get($name) + { + if ('realInstance' !== $name) { + throw new \BadMethodCallException(\sprintf('No such property "%s::$%s"', self::class, $name)); + } + + return $this->realInstance = ($this->initializer)(); + } +} diff --git a/vendor/symfony/var-exporter/Internal/NamedClosure.php b/vendor/symfony/var-exporter/Internal/NamedClosure.php new file mode 100644 index 00000000..fbd39e86 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/NamedClosure.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class NamedClosure +{ + public function __construct( + public readonly array $callable, + public readonly ?\ReflectionMethod $method = null, + ) { + } +} diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php new file mode 100644 index 00000000..2c7bd7b8 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Reference.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Reference +{ + public int $count = 0; + + public function __construct( + public readonly int $id, + public readonly mixed $value = null, + ) { + } +} diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php new file mode 100644 index 00000000..f057b745 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Registry.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Registry +{ + public static array $reflectors = []; + public static array $prototypes = []; + public static array $factories = []; + public static array $cloneable = []; + public static array $instantiableWithoutConstructor = []; + + public function __construct( + public readonly array $classes, + ) { + } + + public static function unserialize($objects, $serializables) + { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + + try { + foreach ($serializables as $k => $v) { + $objects[$k] = unserialize($v); + } + } finally { + ini_set('unserialize_callback_func', $unserializeCallback); + } + + return $objects; + } + + public static function p($class) + { + self::getClassReflector($class, true, true); + + return self::$prototypes[$class]; + } + + public static function f($class) + { + $reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false); + + return self::$factories[$class] = $reflector->newInstanceWithoutConstructor(...); + } + + public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) + { + if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $reflector = new \ReflectionClass($class); + + if ($instantiableWithoutConstructor) { + $proto = $reflector->newInstanceWithoutConstructor(); + } elseif (!$isClass || $reflector->isAbstract()) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->name !== $class) { + $reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable); + self::$cloneable[$class] = self::$cloneable[$name]; + self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; + self::$prototypes[$class] = self::$prototypes[$name]; + + return $reflector; + } else { + try { + $proto = $reflector->newInstanceWithoutConstructor(); + $instantiableWithoutConstructor = true; + } catch (\ReflectionException) { + $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; + if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { + $proto = null; + } else { + try { + $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}'); + } catch (\Exception $e) { + if (__FILE__ !== $e->getFile()) { + throw $e; + } + throw new NotInstantiableTypeException($class, $e); + } + if (false === $proto) { + throw new NotInstantiableTypeException($class); + } + } + } + if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && !method_exists($class, '__serialize')) { + try { + serialize($proto); + } catch (\Exception $e) { + throw new NotInstantiableTypeException($class, $e); + } + } + } + + if (null === $cloneable) { + if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && !method_exists($class, '__unserialize'))) { + throw new NotInstantiableTypeException($class); + } + + $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); + } + + self::$cloneable[$class] = $cloneable; + self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; + self::$prototypes[$class] = $proto; + + if ($proto instanceof \Throwable) { + static $setTrace; + + if (null === $setTrace) { + $setTrace = [ + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ]; + $setTrace[0] = $setTrace[0]->setValue(...); + $setTrace[1] = $setTrace[1]->setValue(...); + } + + $setTrace[$proto instanceof \Exception]($proto, []); + } + + return $reflector; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Values.php b/vendor/symfony/var-exporter/Internal/Values.php new file mode 100644 index 00000000..4f20a821 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/Values.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public function __construct( + public readonly array $values, + ) { + } +} diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE new file mode 100644 index 00000000..7536caea --- /dev/null +++ b/vendor/symfony/var-exporter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier + +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/vendor/symfony/var-exporter/LazyObjectInterface.php b/vendor/symfony/var-exporter/LazyObjectInterface.php new file mode 100644 index 00000000..3422dc6c --- /dev/null +++ b/vendor/symfony/var-exporter/LazyObjectInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +interface LazyObjectInterface +{ + /** + * Returns whether the object is initialized. + * + * @param bool $partial Whether partially initialized objects should be considered as initialized + */ + public function isLazyObjectInitialized(bool $partial = false): bool; + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): object; + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool; +} diff --git a/vendor/symfony/var-exporter/ProxyHelper.php b/vendor/symfony/var-exporter/ProxyHelper.php new file mode 100644 index 00000000..0297dac2 --- /dev/null +++ b/vendor/symfony/var-exporter/ProxyHelper.php @@ -0,0 +1,452 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\LogicException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyDecoratorTrait; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; + +/** + * @author Nicolas Grekas + */ +final class ProxyHelper +{ + /** + * Helps generate lazy-loading decorators. + * + * @param \ReflectionClass[] $interfaces + * + * @throws LogicException When the class is incompatible with virtual proxies + */ + public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string + { + if (!class_exists($class?->name ?? \stdClass::class, false)) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name)); + } + if ($class?->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); + } + + if ($class && !$class->isAbstract()) { + $parent = $class; + do { + $extendsInternalClass = $parent->isInternal(); + } while (!$extendsInternalClass && $parent = $parent->getParentClass()); + + if (!$extendsInternalClass) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: leverage native lazy objects instead for class "%s".', $class->name)); + } + } + + $propertyScopes = $class ? Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name) : []; + $abstractProperties = []; + $hookedProperties = []; + foreach ($propertyScopes as $key => [$scope, $name, , $access]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + $flags = $access >> 2; + + if ($k !== $key || $flags & \ReflectionProperty::IS_PRIVATE) { + continue; + } + + if ($flags & \ReflectionProperty::IS_ABSTRACT) { + $abstractProperties[$name] = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); + continue; + } + $abstractProperties[$name] = false; + + if (!($access & Hydrator::PROPERTY_HAS_HOOKS)) { + continue; + } + + if ($flags & \ReflectionProperty::IS_FINAL) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: property "%s::$%s" is final.', $class->name, $name)); + } + + $p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); + $hookedProperties[$name] = [$p, $p->getHooks()]; + } + + $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; + foreach ($interfaces as $interface) { + if (!$interface->isInterface()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name)); + } + $methodReflectors[] = $interface->getMethods(); + + foreach ($interface->getProperties() as $p) { + $abstractProperties[$p->name] ??= $p; + $hookedProperties[$p->name] ??= [$p, []]; + $hookedProperties[$p->name][1] += $p->getHooks(); + } + } + + $hooks = ''; + + foreach (array_filter($abstractProperties) as $name => $p) { + $type = self::exportType($p); + $hooks .= "\n " + .($p->isProtected() ? 'protected' : 'public') + .($p->isProtectedSet() ? ' protected(set)' : '') + ." {$type} \${$name};\n"; + } + + foreach ($hookedProperties as $name => [$p, $methods]) { + if ($abstractProperties[$p->name] ?? false) { + continue; + } + $type = self::exportType($p); + $hooks .= "\n " + .($p->isProtected() ? 'protected' : 'public') + .($p->isProtectedSet() ? ' protected(set)' : '') + ." {$type} \${$name} {\n"; + + foreach ($methods as $hook => $method) { + if ('get' === $hook) { + $ref = ($method->returnsReference() ? '&' : ''); + $hooks .= <<lazyObjectState->realInstance->{$p->name}; + } + + EOPHP; + } elseif ('set' === $hook) { + $parameters = self::exportParameters($method, true); + $arg = '$'.$method->getParameters()[0]->name; + $hooks .= <<lazyObjectState->realInstance->{$p->name} = {$arg}; + } + + EOPHP; + } else { + throw new LogicException(\sprintf('Cannot generate lazy proxy: hook "%s::%s()" is not supported.', $class->name, $method->name)); + } + } + + $hooks .= " }\n"; + } + + $methods = []; + $methodReflectors = array_merge(...$methodReflectors); + + foreach ($methodReflectors as $method) { + if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) { + continue; + } + $trait = new \ReflectionMethod(LazyDecoratorTrait::class, '__get'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): mixed', '): '.$type, $body[0]); + $methods['__get'] = strtr(implode('', $body).' }', [ + 'Hydrator' => '\\'.Hydrator::class, + 'Registry' => '\\'.LazyObjectRegistry::class, + ]); + break; + } + + foreach ($methodReflectors as $method) { + if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) { + continue; + } + if ($method->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name)); + } + if (method_exists(LazyDecoratorTrait::class, $method->name)) { + continue; + } + + $signature = self::exportSignature($method, true, $args); + + if ($method->isStatic()) { + $body = " throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".');"; + } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { + $body = <<lazyObjectState->realInstance->{$method->name}({$args}); + EOPHP; + } else { + $mayReturnThis = false; + foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) { + if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) { + $mayReturnThis = true; + break; + } + foreach ([$class, ...$interfaces] as $r) { + if ($r && is_a($r->name, $type, true)) { + $mayReturnThis = true; + break 2; + } + } + } + + if ($method->returnsReference() || !$mayReturnThis) { + $body = <<lazyObjectState->realInstance->{$method->name}({$args}); + EOPHP; + } else { + $body = <<lazyObjectState->realInstance; + \${1} = \${0}->{$method->name}({$args}); + + return match (true) { + \${1} === \${0} => \$this, + !\${1} instanceof \${0} || !\${0} instanceof \${1} => \${1}, + null !== \$this->lazyObjectState->cloneInstance =& \${1} => clone \$this, + }; + EOPHP; + } + } + $methods[$lcName] = " {$signature}\n {\n{$body}\n }"; + } + + $types = $interfaces = array_unique(array_column($interfaces, 'name')); + $interfaces[] = LazyObjectInterface::class; + $interfaces = implode(', \\', $interfaces); + $parent = $class ? ' extends \\'.$class->name : ''; + array_unshift($types, $class ? 'parent' : ''); + $type = ltrim(implode('&\\', $types), '&'); + + if (!$class) { + $trait = new \ReflectionMethod(LazyDecoratorTrait::class, 'initializeLazyObject'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): parent', '): '.$type, $body[0]); + $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; + } + $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; + $propertyScopes = $class ? self::exportPropertyScopes($class->name, $propertyScopes) : '[]'; + $lazyProxyTraitStatement = []; + + if ( + $class?->hasMethod('__unserialize') + && !$class->getMethod('__unserialize')->getParameters()[0]->getType() + ) { + // fix contravariance type problem when $class declares a `__unserialize()` method without typehint. + $lazyProxyTraitStatement[] = '__unserialize as private __doUnserialize;'; + + $body .= <<__doUnserialize(\$data); + } + + EOPHP; + } + + if ($lazyProxyTraitStatement) { + $lazyProxyTraitStatement = implode("\n ", $lazyProxyTraitStatement); + $lazyProxyTraitStatement = <<class : $function->getNamespaceName().'\\'; + $namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0); + foreach ($function->getParameters() as $param) { + $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '') + .($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '') + .($param->isPassedByReference() ? '&' : '') + .($param->isVariadic() ? '...' : '').'$'.$param->name + .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : ''); + if ($param->isPassedByReference()) { + $byRefIndex = 1 + $param->getPosition(); + } + $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', '; + } + + if (!$param || !$byRefIndex) { + $args = '...\func_get_args()'; + } elseif ($param->isVariadic()) { + $args = substr($args, 0, -2); + } else { + $args = explode(', ', $args, 1 + $byRefIndex); + $args[$byRefIndex] = \sprintf('...\array_slice(\func_get_args(), %d)', $byRefIndex); + $args = implode(', ', $args); + } + + return implode(', ', $parameters); + } + + public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string + { + $parameters = self::exportParameters($function, $withParameterTypes, $args); + + $signature = 'function '.($function->returnsReference() ? '&' : '') + .($function->isClosure() ? '' : $function->name).'('.$parameters.')'; + + if ($function instanceof \ReflectionMethod) { + $signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private ')) + .($function->isStatic() ? 'static ' : '').$signature; + } + if ($function->hasReturnType()) { + $signature .= ': '.self::exportType($function); + } + + static $getPrototype; + $getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...); + + while ($function) { + if ($function->hasTentativeReturnType()) { + return '#[\ReturnTypeWillChange] '.$signature; + } + + try { + $function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function); + } catch (\ReflectionException) { + break; + } + } + + return $signature; + } + + public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string + { + if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) { + return null; + } + $class = null; + $types = []; + if ($type instanceof \ReflectionUnionType) { + $reflectionTypes = $type->getTypes(); + $glue = '|'; + } elseif ($type instanceof \ReflectionIntersectionType) { + $reflectionTypes = $type->getTypes(); + $glue = '&'; + } else { + $reflectionTypes = [$type]; + $glue = null; + } + + foreach ($reflectionTypes as $type) { + if ($type instanceof \ReflectionIntersectionType) { + if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') { + $types[] = $name; + } + continue; + } + $name = $type->getName(); + + if ($noBuiltin && $type->isBuiltin()) { + continue; + } + if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) { + $name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name; + } + + $types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name; + } + + if (!$types) { + return ''; + } + if (null === $glue) { + $defaultNull = $owner instanceof \ReflectionParameter && 'NULL' === rtrim(substr(explode('$'.$owner->name.' = ', (string) $owner, 2)[1] ?? '', 0, -2)); + + return (!$noBuiltin && ($type->allowsNull() || $defaultNull) && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0]; + } + sort($types); + + return implode($glue, $types); + } + + private static function exportPropertyScopes(string $parent, array $propertyScopes): string + { + uksort($propertyScopes, 'strnatcmp'); + foreach ($propertyScopes as $k => $v) { + unset($propertyScopes[$k][4]); + } + $propertyScopes = VarExporter::export($propertyScopes); + $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes); + $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes); + + return str_replace("\n", "\n ", $propertyScopes); + } + + private static function exportDefault(\ReflectionParameter $param, $namespace): string + { + $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2)); + + if (\in_array($default, ['', 'NULL'], true)) { + return 'null'; + } + if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) { + return VarExporter::export($param->getDefaultValue()); + } + + $regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/"; + $parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + + $regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/'; + $callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass()) + ? fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null' => $m[2], + 'NULL' => 'null', + 'self' => '\\'.$class->name, + 'namespace\\parent', + 'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3] + : fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null', 'self', 'parent' => $m[2], + 'NULL' => 'null', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3]; + + return implode('', array_map(fn ($part) => match ($part[0]) { + '"' => $part, // for internal classes only + "'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part, + default => preg_replace_callback($regexp, $callback, $part), + }, $parts)); + } + + private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string + { + if (!$mightBeRootConst + || false === ($ns = strrpos($symbol, '\\')) + || substr($symbol, 0, $ns) !== $namespace + || \defined($symbol) + || !\defined(substr($symbol, $ns + 1)) + ) { + return '\\'.$symbol; + } + + return '\\'.substr($symbol, $ns + 1); + } +} diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md new file mode 100644 index 00000000..e470bbb9 --- /dev/null +++ b/vendor/symfony/var-exporter/README.md @@ -0,0 +1,94 @@ +VarExporter Component +===================== + +The VarExporter component provides various tools to deal with the internal state +of objects: + +- `VarExporter::export()` allows exporting any serializable PHP data structure to + plain PHP code. While doing so, it preserves all the semantics associated with + the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, + `__serialize`, `__unserialize`); +- `Instantiator::instantiate()` creates an object and sets its properties without + calling its constructor nor any other methods; +- `Hydrator::hydrate()` can set the properties of an existing object; +- `Lazy*Trait` can make a class behave as a lazy-loading ghost or virtual proxy. + +VarExporter::export() +--------------------- + +The reason to use `VarExporter::export()` *vs* `serialize()` or +[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to +OPcache, the resulting code is significantly faster and more memory efficient +than using `unserialize()` or `igbinary_unserialize()`. + +Unlike `var_export()`, this works on any serializable PHP value. + +It also provides a few improvements over `var_export()`/`serialize()`: + + * the output is PSR-2 compatible; + * the output can be re-indented without messing up with `\r` or `\n` in the data; + * missing classes throw a `ClassNotFoundException` instead of being unserialized + to `PHP_Incomplete_Class` objects; + * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` + instances are preserved; + * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes + throw an exception when being serialized (their unserialized version is broken + anyway, see https://bugs.php.net/76737). + +Instantiator and Hydrator +------------------------- + +`Instantiator::instantiate($class)` creates an object of the given class without +calling its constructor nor any other methods. + +`Hydrator::hydrate()` sets the properties of an existing object, including +private and protected ones. For example: + +```php +// Sets the public or protected $object->propertyName property +Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + +// Sets a private property defined on its parent Bar class: +Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + +// Alternative way to set the private $object->privateBarProperty property +Hydrator::hydrate($object, [], [ + Bar::class => ['privateBarProperty' => $propertyValue], +]); +``` + +Lazy Proxies +------------ + +Since version 8.4, PHP provides support for lazy objects via the reflection API. +This native API works with concrete classes. It doesn't with abstracts nor with +internal ones. + +This components provides helpers to generate lazy objects using the decorator +pattern, which works with abstract or internal classes and with interfaces: + +```php +$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(AbstractFoo::class)); +// $proxyCode should be dumped into a file in production envs +eval('class FooLazyProxy'.$proxyCode); + +$foo = FooLazyProxy::createLazyProxy(initializer: function (): AbstractFoo { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance = new Foo(...$dependencies); + // [...] Call setters, etc. if needed + + return $instance; +}); +// $foo is now a lazy-loading decorator object. The initializer will +// be called only when and if a *method* is called. +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_exporter.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php new file mode 100644 index 00000000..606a9fd5 --- /dev/null +++ b/vendor/symfony/var-exporter/VarExporter.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Internal\Exporter; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Internal\Values; + +/** + * Exports serializable PHP values to PHP code. + * + * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) + * while preserving all the semantics associated with serialize() (unlike var_export()). + * + * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). + * + * @author Nicolas Grekas + */ +final class VarExporter +{ + /** + * Exports a serializable PHP value to PHP code. + * + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param array &$foundClasses Classes found in the value are added to this list as both keys and values + * + * @throws ExceptionInterface When the provided value cannot be serialized + */ + public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string + { + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) { + return Exporter::export($value); + } + + $objectsPool = new \SplObjectStorage(); + $refsPool = []; + $objectsCount = 0; + + try { + $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = []; + foreach ($refsPool as $i => $v) { + if ($v[0]->count) { + $references[1 + $i] = $v[2]; + } + $v[0] = $v[1]; + } + } + + if ($isStaticValue) { + return Exporter::export($value); + } + + $classes = []; + $values = []; + $states = []; + foreach ($objectsPool as $i => $v) { + [, $class, $values[], $wakeup] = $objectsPool[$v]; + $foundClasses[$class] = $classes[] = $class; + + if (0 < $wakeup) { + $states[$wakeup] = $i; + } elseif (0 > $wakeup) { + $states[-$wakeup] = [$i, array_pop($values)]; + $values[] = []; + } + } + ksort($states); + + $wakeups = [null]; + foreach ($states as $v) { + if (\is_array($v)) { + $wakeups[-$v[0]] = $v[1]; + } else { + $wakeups[] = $v; + } + } + + if (null === $wakeups[0]) { + unset($wakeups[0]); + } + + $properties = []; + foreach ($values as $i => $vars) { + foreach ($vars as $class => $values) { + foreach ($values as $name => $v) { + $properties[$class][$name][$i] = $v; + } + } + } + + if ($classes || $references) { + $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); + } else { + $isStaticValue = true; + } + + return Exporter::export($value); + } +} diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json new file mode 100644 index 00000000..a3ced35d --- /dev/null +++ b/vendor/symfony/var-exporter/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/var-exporter", + "type": "library", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.4" + }, + "require-dev": { + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +}